Compare commits
No commits in common. "0948371f838cb0bba494381cdfae9c42bd2e0710" and "1b42cd781693947a128baf56997de4244f7f91ef" have entirely different histories.
0948371f83
...
1b42cd7816
|
@ -37,12 +37,7 @@ struct HTMLConverter {
|
||||||
mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines)
|
mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines)
|
||||||
mutAttrString.collapseWhitespace()
|
mutAttrString.collapseWhitespace()
|
||||||
|
|
||||||
// Wait until the end and then fill in the unset paragraph styles, to avoid clobbering the list style.
|
mutAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: mutAttrString.fullRange)
|
||||||
mutAttrString.enumerateAttribute(.paragraphStyle, in: mutAttrString.fullRange, options: .longestEffectiveRangeNotRequired) { value, range, stop in
|
|
||||||
if value == nil {
|
|
||||||
mutAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mutAttrString
|
return mutAttrString
|
||||||
} else {
|
} else {
|
||||||
|
@ -61,10 +56,6 @@ struct HTMLConverter {
|
||||||
}
|
}
|
||||||
return NSAttributedString(string: text, attributes: [.font: font, .foregroundColor: color])
|
return NSAttributedString(string: text, attributes: [.font: font, .foregroundColor: color])
|
||||||
case let node as Element:
|
case let node as Element:
|
||||||
if node.tagName() == "ol" || node.tagName() == "ul" {
|
|
||||||
return attributedTextForList(node, usePreformattedText: usePreformattedText)
|
|
||||||
}
|
|
||||||
|
|
||||||
let attributed = NSMutableAttributedString(string: "", attributes: [.font: font, .foregroundColor: color])
|
let attributed = NSMutableAttributedString(string: "", attributes: [.font: font, .foregroundColor: color])
|
||||||
for child in node.getChildNodes() {
|
for child in node.getChildNodes() {
|
||||||
var appendEllipsis = false
|
var appendEllipsis = false
|
||||||
|
@ -124,6 +115,25 @@ struct HTMLConverter {
|
||||||
case "pre":
|
case "pre":
|
||||||
attributed.append(NSAttributedString(string: "\n\n"))
|
attributed.append(NSAttributedString(string: "\n\n"))
|
||||||
attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange)
|
attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange)
|
||||||
|
case "ol", "ul":
|
||||||
|
attributed.append(NSAttributedString(string: "\n\n"))
|
||||||
|
attributed.trimLeadingCharactersInSet(.whitespacesAndNewlines)
|
||||||
|
case "li":
|
||||||
|
let parentEl = node.parent()!
|
||||||
|
let parentTag = parentEl.tagName()
|
||||||
|
let bullet: NSAttributedString
|
||||||
|
if parentTag == "ol" {
|
||||||
|
let index = (try? node.elementSiblingIndex()) ?? 0
|
||||||
|
// we use the monospace digit font so that the periods of all the list items line up
|
||||||
|
// TODO: this probably breaks with dynamic type
|
||||||
|
bullet = NSAttributedString(string: "\(index + 1).\t", attributes: [.font: monospaceFont, .foregroundColor: color])
|
||||||
|
} else if parentTag == "ul" {
|
||||||
|
bullet = NSAttributedString(string: "\u{2022}\t", attributes: [.font: font, .foregroundColor: color])
|
||||||
|
} else {
|
||||||
|
bullet = NSAttributedString()
|
||||||
|
}
|
||||||
|
attributed.insert(bullet, at: 0)
|
||||||
|
attributed.append(NSAttributedString(string: "\n", attributes: [.font: font]))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -134,37 +144,5 @@ struct HTMLConverter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func attributedTextForList(_ element: Element, usePreformattedText: Bool) -> NSAttributedString {
|
|
||||||
let list = element.tagName() == "ol" ? OrderedNumberTextList(markerFormat: .decimal, options: 0) : NSTextList(markerFormat: .disc, options: 0)
|
|
||||||
let paragraphStyle = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
|
|
||||||
// I don't like that I can't just use paragraphStyle.textLists, because it makes the list markers
|
|
||||||
// not use the monospace digit font (it seems to just use whatever font attribute is set for the whole thing),
|
|
||||||
// and it doesn't right align the list markers.
|
|
||||||
// Unfortunately, doing it manually means the list markers are incldued in the selectable text.
|
|
||||||
paragraphStyle.headIndent = 32
|
|
||||||
paragraphStyle.firstLineHeadIndent = 0
|
|
||||||
// Use 2 tab stops, one for the list marker, the second for the content.
|
|
||||||
paragraphStyle.tabStops = [NSTextTab(textAlignment: .right, location: 28), NSTextTab(textAlignment: .natural, location: 32)]
|
|
||||||
let str = NSMutableAttributedString(string: "")
|
|
||||||
var item = 1
|
|
||||||
for child in element.children() where child.tagName() == "li" {
|
|
||||||
if let childStr = attributedTextForHTMLNode(child, usePreformattedText: usePreformattedText) {
|
|
||||||
str.append(NSAttributedString(string: "\t\(list.marker(forItemNumber: item))\t", attributes: [
|
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .monospacedDigitSystemFont(ofSize: 17, weight: .regular)),
|
|
||||||
]))
|
|
||||||
str.append(childStr)
|
|
||||||
str.append(NSAttributedString(string: "\n"))
|
|
||||||
item += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str.addAttribute(.paragraphStyle, value: paragraphStyle, range: str.fullRange)
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class OrderedNumberTextList: NSTextList {
|
|
||||||
override func marker(forItemNumber itemNumber: Int) -> String {
|
|
||||||
"\(super.marker(forItemNumber: itemNumber))."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,8 +25,6 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
||||||
private(set) var collectionView: UICollectionView!
|
private(set) var collectionView: UICollectionView!
|
||||||
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||||
|
|
||||||
var reconfigureVisibleItemsOnEndDecelerating: Bool = false
|
|
||||||
|
|
||||||
private var newer: RequestRange?
|
private var newer: RequestRange?
|
||||||
private var older: RequestRange?
|
private var older: RequestRange?
|
||||||
|
|
||||||
|
@ -664,13 +662,6 @@ extension NotificationsCollectionViewController: UICollectionViewDelegate {
|
||||||
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
||||||
if reconfigureVisibleItemsOnEndDecelerating {
|
|
||||||
reconfigureVisibleItemsOnEndDecelerating = false
|
|
||||||
reconfigureVisibleCells()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationsCollectionViewController: UICollectionViewDragDelegate {
|
extension NotificationsCollectionViewController: UICollectionViewDragDelegate {
|
||||||
|
|
|
@ -32,8 +32,6 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||||
private(set) var headerCell: ProfileHeaderCollectionViewCell?
|
private(set) var headerCell: ProfileHeaderCollectionViewCell?
|
||||||
|
|
||||||
var reconfigureVisibleItemsOnEndDecelerating: Bool = false
|
|
||||||
|
|
||||||
private(set) var state: State = .unloaded
|
private(set) var state: State = .unloaded
|
||||||
|
|
||||||
init(accountID: String?, kind: Kind, owner: ProfileViewController) {
|
init(accountID: String?, kind: Kind, owner: ProfileViewController) {
|
||||||
|
@ -629,13 +627,6 @@ extension ProfileStatusesViewController: UICollectionViewDelegate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
||||||
if reconfigureVisibleItemsOnEndDecelerating {
|
|
||||||
reconfigureVisibleItemsOnEndDecelerating = false
|
|
||||||
reconfigureVisibleCells()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileStatusesViewController: UICollectionViewDragDelegate {
|
extension ProfileStatusesViewController: UICollectionViewDragDelegate {
|
||||||
|
|
|
@ -42,8 +42,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
private(set) var collectionView: UICollectionView!
|
private(set) var collectionView: UICollectionView!
|
||||||
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||||
|
|
||||||
var reconfigureVisibleItemsOnEndDecelerating: Bool = false
|
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var userActivityNeedsUpdate = PassthroughSubject<Void, Never>()
|
private var userActivityNeedsUpdate = PassthroughSubject<Void, Never>()
|
||||||
// the last time this VC disappeared or the scene was backgrounded while it was active, used to decide if we want to check for present when reappearing
|
// the last time this VC disappeared or the scene was backgrounded while it was active, used to decide if we want to check for present when reappearing
|
||||||
|
@ -1319,11 +1317,6 @@ extension TimelineViewController: UICollectionViewDelegate {
|
||||||
|
|
||||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
userActivityNeedsUpdate.send()
|
userActivityNeedsUpdate.send()
|
||||||
|
|
||||||
if reconfigureVisibleItemsOnEndDecelerating {
|
|
||||||
reconfigureVisibleItemsOnEndDecelerating = false
|
|
||||||
reconfigureVisibleCells()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
|
|
@ -21,8 +21,6 @@ protocol TimelineLikeCollectionViewController: UIViewController, TimelineLikeCon
|
||||||
|
|
||||||
var collectionView: UICollectionView! { get }
|
var collectionView: UICollectionView! { get }
|
||||||
var dataSource: UICollectionViewDiffableDataSource<Section, Item>! { get }
|
var dataSource: UICollectionViewDiffableDataSource<Section, Item>! { get }
|
||||||
|
|
||||||
var reconfigureVisibleItemsOnEndDecelerating: Bool { get set }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol TimelineLikeCollectionViewSection: Hashable, Sendable {
|
protocol TimelineLikeCollectionViewSection: Hashable, Sendable {
|
||||||
|
@ -127,18 +125,6 @@ extension TimelineLikeCollectionViewController {
|
||||||
var config: ToastConfiguration
|
var config: ToastConfiguration
|
||||||
if let error = error as? Self.Error,
|
if let error = error as? Self.Error,
|
||||||
error == .allCaughtUp {
|
error == .allCaughtUp {
|
||||||
// Reconfigure visible items to update timestamps.
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
let isRefreshing = false
|
|
||||||
#else
|
|
||||||
let isRefreshing = collectionView.refreshControl?.isRefreshing ?? false
|
|
||||||
#endif
|
|
||||||
if isRefreshing {
|
|
||||||
reconfigureVisibleItemsOnEndDecelerating = true
|
|
||||||
} else {
|
|
||||||
reconfigureVisibleCells()
|
|
||||||
}
|
|
||||||
|
|
||||||
config = ToastConfiguration(title: "You're all caught up")
|
config = ToastConfiguration(title: "You're all caught up")
|
||||||
config.edge = .top
|
config.edge = .top
|
||||||
config.dismissAutomaticallyAfter = 2
|
config.dismissAutomaticallyAfter = 2
|
||||||
|
@ -218,16 +204,6 @@ extension TimelineLikeCollectionViewController {
|
||||||
await task.value
|
await task.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func reconfigureVisibleCells() {
|
|
||||||
let items = collectionView.indexPathsForVisibleItems.compactMap { dataSource.itemIdentifier(for: $0) }
|
|
||||||
if !items.isEmpty {
|
|
||||||
var snapshot = dataSource.snapshot()
|
|
||||||
snapshot.reconfigureItems(items)
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerTimelineLikeCells() {
|
func registerTimelineLikeCells() {
|
||||||
collectionView.register(LoadingCollectionViewCell.self, forCellWithReuseIdentifier: "loadingIndicator")
|
collectionView.register(LoadingCollectionViewCell.self, forCellWithReuseIdentifier: "loadingIndicator")
|
||||||
collectionView.register(ConfirmLoadMoreCollectionViewCell.self, forCellWithReuseIdentifier: "confirmLoadMore")
|
collectionView.register(ConfirmLoadMoreCollectionViewCell.self, forCellWithReuseIdentifier: "confirmLoadMore")
|
||||||
|
|
Loading…
Reference in New Issue