Compare commits
No commits in common. "4d183fe0b230369a481d6ed6e5266d9efde3da66" and "5a4323067ad63772fb9052a4e3aebe69d7305be3" have entirely different histories.
4d183fe0b2
...
5a4323067a
|
@ -32,7 +32,7 @@
|
|||
D608470F2A245D1F00C17380 /* ActiveInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D608470E2A245D1F00C17380 /* ActiveInstance.swift */; };
|
||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
|
||||
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */; };
|
||||
D60BB3942B30076F00DAEA65 /* HTMLStreamer in Frameworks */ = {isa = PBXBuildFile; productRef = D60BB3932B30076F00DAEA65 /* HTMLStreamer */; };
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
|
||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; };
|
||||
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; };
|
||||
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; };
|
||||
|
@ -303,6 +303,7 @@
|
|||
D6D79F262A0C8D2700AB2315 /* FetchStatusSourceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F252A0C8D2700AB2315 /* FetchStatusSourceService.swift */; };
|
||||
D6D79F292A0D596B00AB2315 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F282A0D596B00AB2315 /* StatusEditHistoryViewController.swift */; };
|
||||
D6D79F2B2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */; };
|
||||
D6D79F2D2A0D61B400AB2315 /* StatusEditContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */; };
|
||||
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
||||
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
||||
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
||||
|
@ -711,6 +712,7 @@
|
|||
D6D79F252A0C8D2700AB2315 /* FetchStatusSourceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchStatusSourceService.swift; sourceTree = "<group>"; };
|
||||
D6D79F282A0D596B00AB2315 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = "<group>"; };
|
||||
D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditContentTextView.swift; sourceTree = "<group>"; };
|
||||
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
||||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
||||
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
||||
|
@ -784,10 +786,10 @@
|
|||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */,
|
||||
D659F35E2953A212002D944A /* TTTKit in Frameworks */,
|
||||
D6B0026E29B5248800C70BE2 /* UserAccounts in Frameworks */,
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
|
||||
D60088EF2980D8B5005B4D00 /* StoreKit.framework in Frameworks */,
|
||||
D6CA6ED229EF6091003EC5DF /* TuskerPreferences in Frameworks */,
|
||||
D6552367289870790048A653 /* ScreenCorners in Frameworks */,
|
||||
D60BB3942B30076F00DAEA65 /* HTMLStreamer in Frameworks */,
|
||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
|
||||
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */,
|
||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */,
|
||||
|
@ -1572,6 +1574,7 @@
|
|||
children = (
|
||||
D6D79F282A0D596B00AB2315 /* StatusEditHistoryViewController.swift */,
|
||||
D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */,
|
||||
D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */,
|
||||
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */,
|
||||
);
|
||||
path = "Status Edit History";
|
||||
|
@ -1696,6 +1699,7 @@
|
|||
);
|
||||
name = Tusker;
|
||||
packageProductDependencies = (
|
||||
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
|
||||
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
|
||||
D674A50827F9128D00BA03AC /* Pachyderm */,
|
||||
D6552366289870790048A653 /* ScreenCorners */,
|
||||
|
@ -1707,7 +1711,6 @@
|
|||
D635237029B78A7D009ED5E7 /* TuskerComponents */,
|
||||
D6BD395829B64426005FFD2B /* ComposeUI */,
|
||||
D6CA6ED129EF6091003EC5DF /* TuskerPreferences */,
|
||||
D60BB3932B30076F00DAEA65 /* HTMLStreamer */,
|
||||
);
|
||||
productName = Tusker;
|
||||
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
||||
|
@ -1818,10 +1821,10 @@
|
|||
);
|
||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||
packageReferences = (
|
||||
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
|
||||
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */,
|
||||
D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
|
||||
D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */,
|
||||
);
|
||||
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -2178,6 +2181,7 @@
|
|||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
||||
D68A76F129539116001DA1B3 /* FlipView.swift in Sources */,
|
||||
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
|
||||
D6D79F2D2A0D61B400AB2315 /* StatusEditContentTextView.swift in Sources */,
|
||||
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */,
|
||||
D61F75B5293BD97400C0B37F /* DeleteFilterService.swift in Sources */,
|
||||
D6DFC6A0242C4CCC00ACC392 /* Weak.swift in Sources */,
|
||||
|
@ -2952,12 +2956,12 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */ = {
|
||||
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://git.shadowfacts.net/shadowfacts/HTMLStreamer.git";
|
||||
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
kind = upToNextMinorVersion;
|
||||
minimumVersion = 2.3.2;
|
||||
};
|
||||
};
|
||||
D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
|
||||
|
@ -2987,10 +2991,10 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D60BB3932B30076F00DAEA65 /* HTMLStreamer */ = {
|
||||
D60CFFDA24A290BA00D00083 /* SwiftSoup */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */;
|
||||
productName = HTMLStreamer;
|
||||
package = D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||
productName = SwiftSoup;
|
||||
};
|
||||
D61ABEFB28F105DE00B29151 /* Pachyderm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import LinkPresentation
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class StatusActivityItemSource: NSObject, UIActivityItemSource {
|
||||
let status: StatusMO
|
||||
|
@ -33,8 +33,8 @@ class StatusActivityItemSource: NSObject, UIActivityItemSource {
|
|||
let metadata = LPLinkMetadata()
|
||||
metadata.originalURL = status.url!
|
||||
metadata.url = status.url!
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
let content = converter.convert(html: status.content)
|
||||
let doc = try! SwiftSoup.parse(status.content)
|
||||
let content = try! doc.text()
|
||||
metadata.title = "\(status.account.displayName): \"\(content)\""
|
||||
if let avatar = status.account.avatar,
|
||||
let data = ImageCache.avatars.getData(avatar),
|
||||
|
|
|
@ -42,7 +42,7 @@ class Filterer {
|
|||
|
||||
var filtersChanged: ((Bool) -> Void)?
|
||||
|
||||
private var htmlConverter: HTMLConverter
|
||||
var htmlConverter = HTMLConverter()
|
||||
private var hasSetup = false
|
||||
private var matchers = [(NSRegularExpression, Result)]()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
@ -55,10 +55,9 @@ class Filterer {
|
|||
// are no longer valid, without needing to go through and update each of them
|
||||
private var generation = 0
|
||||
|
||||
init(mastodonController: MastodonController, context: FilterV1.Context, htmlConverter: HTMLConverter) {
|
||||
init(mastodonController: MastodonController, context: FilterV1.Context) {
|
||||
self.mastodonController = mastodonController
|
||||
self.context = context
|
||||
self.htmlConverter = htmlConverter
|
||||
self.hideReblogsInTimelines = Preferences.shared.hideReblogsInTimelines
|
||||
self.hideRepliesInTimelines = Preferences.shared.hideRepliesInTimelines
|
||||
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class HTMLConverter {
|
||||
struct HTMLConverter {
|
||||
|
||||
static let defaultFont = UIFont.systemFont(ofSize: 17)
|
||||
static let defaultMonospaceFont = UIFont.monospacedSystemFont(ofSize: 17, weight: .regular)
|
||||
|
@ -23,47 +23,150 @@ class HTMLConverter {
|
|||
return style
|
||||
}()
|
||||
|
||||
private var converter: AttributedStringConverter<Callbacks>
|
||||
|
||||
init(font: UIFont, monospaceFont: UIFont, color: UIColor, paragraphStyle: NSParagraphStyle) {
|
||||
let config = AttributedStringConverterConfiguration(font: font, monospaceFont: monospaceFont, color: color, paragraphStyle: paragraphStyle)
|
||||
self.converter = AttributedStringConverter(configuration: config)
|
||||
}
|
||||
var font: UIFont = defaultFont
|
||||
var monospaceFont: UIFont = defaultMonospaceFont
|
||||
var color: UIColor = defaultColor
|
||||
var paragraphStyle: NSParagraphStyle = defaultParagraphStyle
|
||||
|
||||
func convert(_ html: String) -> NSAttributedString {
|
||||
converter.convert(html: html)
|
||||
let doc = try! SwiftSoup.parseBodyFragment(html)
|
||||
let body = doc.body()!
|
||||
|
||||
if let attributedText = attributedTextForHTMLNode(body) {
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: attributedText)
|
||||
mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines)
|
||||
mutAttrString.collapseWhitespace()
|
||||
|
||||
// Wait until the end and then fill in the unset paragraph styles, to avoid clobbering the list style.
|
||||
mutAttrString.enumerateAttribute(.paragraphStyle, in: mutAttrString.fullRange, options: .longestEffectiveRangeNotRequired) { value, range, stop in
|
||||
if value == nil {
|
||||
mutAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
|
||||
}
|
||||
}
|
||||
|
||||
extension HTMLConverter {
|
||||
struct Callbacks: HTMLConversionCallbacks {
|
||||
static func makeURL(string: String) -> URL? {
|
||||
// Converting WebURL to URL is a small but non-trivial expense (since it works by
|
||||
// serializing the WebURL as a string and then having Foundation parse it again),
|
||||
// so, if available, use the system parser which doesn't require another round trip.
|
||||
if #available(iOS 16.0, macOS 13.0, *),
|
||||
let url = try? URL.ParseStrategy().parse(string) {
|
||||
url
|
||||
} else if let web = WebURL(string),
|
||||
let url = URL(web) {
|
||||
url
|
||||
return mutAttrString
|
||||
} else {
|
||||
URL(string: string)
|
||||
return NSAttributedString()
|
||||
}
|
||||
}
|
||||
|
||||
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
||||
guard name == "span" else {
|
||||
return .default
|
||||
}
|
||||
let clazz = attributes.attributeValue(for: "class")
|
||||
if clazz == "invisible" {
|
||||
return .skip
|
||||
} else if clazz == "ellipsis" {
|
||||
return .replace("…")
|
||||
private func attributedTextForHTMLNode(_ node: Node, usePreformattedText: Bool = false) -> NSAttributedString? {
|
||||
switch node {
|
||||
case let node as TextNode:
|
||||
let text: String
|
||||
if usePreformattedText {
|
||||
text = node.getWholeText()
|
||||
} else {
|
||||
return .default
|
||||
text = node.text()
|
||||
}
|
||||
return NSAttributedString(string: text, attributes: [.font: font, .foregroundColor: color])
|
||||
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])
|
||||
for child in node.getChildNodes() {
|
||||
var appendEllipsis = false
|
||||
if node.tagName() == "a",
|
||||
let el = child as? Element {
|
||||
if el.hasClass("invisible") {
|
||||
continue
|
||||
} else if el.hasClass("ellipsis") {
|
||||
appendEllipsis = true
|
||||
}
|
||||
}
|
||||
|
||||
if let childText = attributedTextForHTMLNode(child, usePreformattedText: usePreformattedText || node.tagName() == "pre") {
|
||||
attributed.append(childText)
|
||||
}
|
||||
|
||||
if appendEllipsis {
|
||||
attributed.append(NSAttributedString("…"))
|
||||
}
|
||||
}
|
||||
|
||||
lazy var currentFont = if attributed.length == 0 {
|
||||
font
|
||||
} else {
|
||||
attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? font
|
||||
}
|
||||
|
||||
switch node.tagName() {
|
||||
case "br":
|
||||
// need to specify defaultFont here b/c otherwise it uses the default 12pt Helvetica which
|
||||
// screws up its determination of the line height making multiple lines of emojis squash together
|
||||
attributed.append(NSAttributedString(string: "\n", attributes: [.font: font]))
|
||||
case "a":
|
||||
let href = try! node.attr("href")
|
||||
if let webURL = WebURL(href),
|
||||
let url = URL(webURL) {
|
||||
attributed.addAttribute(.link, value: url, range: attributed.fullRange)
|
||||
} else if let url = URL(string: href) {
|
||||
attributed.addAttribute(.link, value: url, range: attributed.fullRange)
|
||||
}
|
||||
case "p":
|
||||
attributed.append(NSAttributedString(string: "\n\n", attributes: [.font: font]))
|
||||
case "em", "i":
|
||||
attributed.addAttribute(.font, value: currentFont.withTraits(.traitItalic)!, range: attributed.fullRange)
|
||||
case "strong", "b":
|
||||
attributed.addAttribute(.font, value: currentFont.withTraits(.traitBold)!, range: attributed.fullRange)
|
||||
case "del":
|
||||
attributed.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: attributed.fullRange)
|
||||
case "code":
|
||||
attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange)
|
||||
case "pre":
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange)
|
||||
case "blockquote":
|
||||
let paragraphStyle = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
|
||||
paragraphStyle.headIndent = 32
|
||||
paragraphStyle.firstLineHeadIndent = 32
|
||||
attributed.addAttributes([
|
||||
.font: currentFont.withTraits(.traitItalic)!,
|
||||
.paragraphStyle: paragraphStyle,
|
||||
], range: attributed.fullRange)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return attributed
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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))."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,11 @@ struct ComposeReplyContentView: UIViewRepresentable {
|
|||
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
|
||||
view.adjustsFontForContentSizeCategory = true
|
||||
view.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||
view.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
|
||||
view.overrideMastodonController = mastodonController
|
||||
view.attributedText = TimelineStatusCollectionViewCell.htmlConverter.convert(status.content)
|
||||
view.setTextFrom(status: status)
|
||||
|
||||
return view
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
|||
displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
|
||||
displayNameLabel.adjustsFontForContentSizeCategory = true
|
||||
|
||||
noteTextView.defaultFont = .preferredFont(forTextStyle: .body)
|
||||
noteTextView.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
|
||||
noteTextView.adjustsFontForContentSizeCategory = true
|
||||
noteTextView.textContainer.lineBreakMode = .byTruncatingTail
|
||||
noteTextView.textContainerInset = UIEdgeInsets(top: 16, left: 4, bottom: 16, right: 4)
|
||||
|
@ -58,7 +60,7 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
|||
|
||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||
|
||||
noteTextView.setBodyTextFromHTML(account.note)
|
||||
noteTextView.setTextFromHtml(account.note)
|
||||
noteTextView.setEmojis(account.emojis, identifier: account.id)
|
||||
|
||||
avatarImageView.image = nil
|
||||
|
|
|
@ -54,6 +54,8 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
|||
usernameLabel.font = UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 15, weight: .light))
|
||||
usernameLabel.adjustsFontForContentSizeCategory = true
|
||||
|
||||
noteTextView.defaultFont = .preferredFont(forTextStyle: .body)
|
||||
noteTextView.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
|
||||
noteTextView.adjustsFontForContentSizeCategory = true
|
||||
noteTextView.textContainer.lineBreakMode = .byTruncatingTail
|
||||
|
||||
|
@ -84,7 +86,7 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
|||
avatarImageView.update(for: account.avatar)
|
||||
headerImageView.update(for: account.header)
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
noteTextView.setBodyTextFromHTML(account.note)
|
||||
noteTextView.setTextFromHtml(account.note)
|
||||
|
||||
var config = UIButton.Configuration.plain()
|
||||
config.image = source.image
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
|
@ -78,8 +78,8 @@ class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
|||
let provider = card.providerName!.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
providerLabel.text = provider
|
||||
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
descriptionLabel.text = converter.convert(html: card.description)
|
||||
let description = try! SwiftSoup.parseBodyFragment(card.description).text()
|
||||
descriptionLabel.text = description
|
||||
descriptionLabel.isHidden = description.isEmpty
|
||||
|
||||
let sorted = card.history!.sorted(by: { $0.day < $1.day })
|
||||
|
|
|
@ -23,7 +23,10 @@ class TrendingStatusesViewController: UIViewController, CollectionViewController
|
|||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .public, htmlConverter: TimelineStatusCollectionViewCell.htmlConverter)
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .public)
|
||||
self.filterer.htmlConverter.font = TimelineStatusCollectionViewCell.contentFont
|
||||
self.filterer.htmlConverter.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
self.filterer.htmlConverter.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
|
@ -161,8 +161,9 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
|
||||
actionLabel.setEmojis(pairs: people.map { ($0.displayOrUserName, $0.emojis) }, identifier: group.id)
|
||||
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
statusContentLabel.text = converter.convert(html: status.content)
|
||||
// todo: use htmlconverter
|
||||
let doc = try! SwiftSoup.parseBodyFragment(status.content)
|
||||
statusContentLabel.text = try! doc.text()
|
||||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
|
|
|
@ -34,7 +34,10 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
|||
self.allowedTypes = allowedTypes
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .notifications, htmlConverter: TimelineStatusCollectionViewCell.htmlConverter)
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .notifications)
|
||||
self.filterer.htmlConverter.font = TimelineStatusCollectionViewCell.contentFont
|
||||
self.filterer.htmlConverter.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
self.filterer.htmlConverter.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
|
@ -124,8 +124,9 @@ class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell {
|
|||
updateTimestamp()
|
||||
updateDisplayName(account: account)
|
||||
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
contentLabel.text = converter.convert(html: status.content)
|
||||
// todo: use htmlconverter
|
||||
let doc = try! SwiftSoup.parseBodyFragment(status.content)
|
||||
contentLabel.text = try! doc.text()
|
||||
|
||||
pollView.mastodonController = mastodonController
|
||||
pollView.delegate = delegate
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class StatusUpdatedNotificationCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
|
@ -120,8 +120,9 @@ class StatusUpdatedNotificationCollectionViewCell: UICollectionViewListCell {
|
|||
updateTimestamp()
|
||||
updateDisplayName(account: account)
|
||||
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
contentLabel.text = converter.convert(html: status.content)
|
||||
// todo: use htmlconverter
|
||||
let doc = try! SwiftSoup.parseBodyFragment(status.content)
|
||||
contentLabel.text = try! doc.text()
|
||||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
|
|
|
@ -60,6 +60,31 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
^[[SwiftSoup](https://github.com/scinfu/swiftsoup)](headingLevel: 2)
|
||||
Copyright (c) 2016 Nabil Chatbi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Symbols
|
||||
Symbol outline not available for this file
|
||||
To inspect a symbol, try clicking on the symbol directly in the code view.
|
||||
Code navigation supports a limited number of languages. See which languages are supported.
|
||||
|
||||
^[[swift-url](https://github.com/karwa/swift-url)](headingLevel: 2)
|
||||
|
||||
Apache License
|
||||
|
|
|
@ -41,7 +41,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
self.kind = kind
|
||||
self.owner = owner
|
||||
self.mastodonController = owner.mastodonController
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .account, htmlConverter: TimelineStatusCollectionViewCell.htmlConverter)
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .account)
|
||||
self.filterer.htmlConverter.font = TimelineStatusCollectionViewCell.contentFont
|
||||
self.filterer.htmlConverter.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
self.filterer.htmlConverter.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
|
|
|
@ -7,13 +7,9 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftSoup
|
||||
|
||||
private var converter = HTMLConverter(
|
||||
font: .preferredFont(forTextStyle: .body),
|
||||
monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
|
||||
color: .label,
|
||||
paragraphStyle: .default
|
||||
)
|
||||
private var converter = HTMLConverter()
|
||||
|
||||
struct ReportStatusView: View {
|
||||
let status: StatusMO
|
||||
|
|
|
@ -70,12 +70,15 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
|||
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||
}
|
||||
|
||||
private let contentTextView = ContentTextView().configure {
|
||||
private let contentTextView = StatusEditContentTextView().configure {
|
||||
$0.adjustsFontForContentSizeCategory = true
|
||||
$0.isScrollEnabled = false
|
||||
$0.backgroundColor = nil
|
||||
$0.isEditable = false
|
||||
$0.isSelectable = false
|
||||
$0.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||
$0.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
$0.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
}
|
||||
|
||||
private let cardView = StatusCardView().configure {
|
||||
|
@ -188,8 +191,7 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
|||
|
||||
timestampLabel.text = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: edit.createdAt)
|
||||
|
||||
contentTextView.attributedText = TimelineStatusCollectionViewCell.htmlConverter.convert(edit.content)
|
||||
contentTextView.setEmojis(edit.emojis, identifier: index)
|
||||
contentTextView.setTextFrom(edit: edit, index: index)
|
||||
contentTextView.navigationDelegate = delegate
|
||||
attachmentsView.delegate = self
|
||||
attachmentsView.updateUI(attachments: edit.attachments)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// StatusEditContentTextView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 5/11/23.
|
||||
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
|
||||
class StatusEditContentTextView: ContentTextView {
|
||||
|
||||
func setTextFrom(edit: StatusEdit, index: Int) {
|
||||
setTextFromHtml(edit.content)
|
||||
setEmojis(edit.emojis, identifier: index)
|
||||
}
|
||||
|
||||
// mention links aren't included in the edit content, nothing else to do
|
||||
|
||||
}
|
|
@ -30,7 +30,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
|
||||
let timeline: Timeline
|
||||
weak var mastodonController: MastodonController!
|
||||
private let filterer: Filterer
|
||||
let filterer: Filterer
|
||||
|
||||
var persistsState = false
|
||||
|
||||
|
@ -59,7 +59,10 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
default:
|
||||
filterContext = .public
|
||||
}
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: filterContext, htmlConverter: TimelineStatusCollectionViewCell.htmlConverter)
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: filterContext)
|
||||
self.filterer.htmlConverter.font = TimelineStatusCollectionViewCell.contentFont
|
||||
self.filterer.htmlConverter.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
self.filterer.htmlConverter.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class AccountCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
|
@ -134,8 +134,8 @@ class AccountCollectionViewCell: UICollectionViewListCell {
|
|||
displayNameLabel.setEmojis(account.emojis, identifier: account.id)
|
||||
}
|
||||
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
noteLabel.text = converter.convert(html: account.note)
|
||||
let doc = try! SwiftSoup.parseBodyFragment(account.note)
|
||||
noteLabel.text = try! doc.text()
|
||||
noteLabel.setEmojis(account.emojis, identifier: account.id)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,6 @@ import UIKit
|
|||
|
||||
class ConfirmReblogStatusPreviewView: UIView {
|
||||
|
||||
private static let htmlConverter = HTMLConverter(
|
||||
font: .preferredFont(forTextStyle: .caption2),
|
||||
monospaceFont: UIFontMetrics(forTextStyle: .caption2).scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
|
||||
color: .label,
|
||||
paragraphStyle: .default
|
||||
)
|
||||
|
||||
private var avatarTask: Task<Void, Error>?
|
||||
|
||||
init(status: StatusMO) {
|
||||
|
@ -67,13 +60,17 @@ class ConfirmReblogStatusPreviewView: UIView {
|
|||
vStack.addArrangedSubview(displayNameLabel)
|
||||
|
||||
let contentView = StatusContentTextView()
|
||||
contentView.defaultFont = .preferredFont(forTextStyle: .caption2)
|
||||
contentView.monospaceFont = UIFontMetrics(forTextStyle: .caption2).scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
|
||||
contentView.isUserInteractionEnabled = false
|
||||
contentView.isScrollEnabled = false
|
||||
contentView.backgroundColor = nil
|
||||
contentView.textContainerInset = .zero
|
||||
contentView.adjustsFontForContentSizeCategory = true
|
||||
// remove the extra line spacing applied by StatusContentTextView because, since we're using a smaller font, the regular 2pt looks big
|
||||
contentView.paragraphStyle = .default
|
||||
// TODO: line limit
|
||||
contentView.setTextFrom(status: status, content: ConfirmReblogStatusPreviewView.htmlConverter.convert(status.content))
|
||||
contentView.setTextFrom(status: status)
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
vStack.addArrangedSubview(contentView)
|
||||
NSLayoutConstraint.activate([
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftSoup
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
import WebURL
|
||||
|
@ -22,19 +23,30 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
weak var overrideMastodonController: MastodonController?
|
||||
var mastodonController: MastodonController? { overrideMastodonController ?? navigationDelegate?.apiController }
|
||||
|
||||
private static let defaultBodyHTMLConverter = HTMLConverter(
|
||||
font: .preferredFont(forTextStyle: .body),
|
||||
monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
|
||||
color: .label,
|
||||
paragraphStyle: .default
|
||||
)
|
||||
private(set) var htmlConverter = HTMLConverter()
|
||||
var defaultFont: UIFont {
|
||||
_read { yield htmlConverter.font }
|
||||
_modify { yield &htmlConverter.font }
|
||||
}
|
||||
var monospaceFont: UIFont {
|
||||
_read { yield htmlConverter.monospaceFont }
|
||||
_modify { yield &htmlConverter.monospaceFont }
|
||||
}
|
||||
var defaultColor: UIColor {
|
||||
_read { yield htmlConverter.color }
|
||||
_modify { yield &htmlConverter.color }
|
||||
}
|
||||
var paragraphStyle: NSParagraphStyle {
|
||||
_read { yield htmlConverter.paragraphStyle }
|
||||
_modify { yield &htmlConverter.paragraphStyle }
|
||||
}
|
||||
|
||||
private(set) var hasEmojis = false
|
||||
|
||||
var emojiIdentifier: AnyHashable?
|
||||
var emojiRequests: [ImageCache.Request] = []
|
||||
var emojiFont: UIFont = .preferredFont(forTextStyle: .body)
|
||||
var emojiTextColor: UIColor = .label
|
||||
var emojiFont: UIFont { defaultFont }
|
||||
var emojiTextColor: UIColor { defaultColor }
|
||||
|
||||
// The link range currently being previewed
|
||||
private var currentPreviewedLinkRange: NSRange?
|
||||
|
@ -108,8 +120,8 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
}
|
||||
|
||||
// MARK: - HTML Parsing
|
||||
func setBodyTextFromHTML(_ html: String) {
|
||||
self.attributedText = ContentTextView.defaultBodyHTMLConverter.convert(html)
|
||||
func setTextFromHtml(_ html: String) {
|
||||
self.attributedText = htmlConverter.convert(html)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
|
|
@ -34,6 +34,8 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
adultLabel.layer.masksToBounds = true
|
||||
adultLabel.layer.cornerRadius = 0.5 * adultLabel.bounds.height
|
||||
|
||||
descriptionTextView.defaultFont = .preferredFont(forTextStyle: .body)
|
||||
descriptionTextView.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
|
||||
descriptionTextView.adjustsFontForContentSizeCategory = true
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,7 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
|
||||
domainLabel.text = instance.domain
|
||||
adultLabel.isHidden = instance.category != "adult"
|
||||
descriptionTextView.setBodyTextFromHTML(instance.description)
|
||||
descriptionTextView.setTextFromHtml(instance.description)
|
||||
updateThumbnail(url: instance.proxiedThumbnailURL)
|
||||
}
|
||||
|
||||
|
@ -57,7 +59,7 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
|
||||
domainLabel.text = URLComponents(string: instance.uri)?.host ?? instance.uri
|
||||
adultLabel.isHidden = true
|
||||
descriptionTextView.setBodyTextFromHTML(instance.shortDescription ?? instance.description)
|
||||
descriptionTextView.setTextFromHtml(instance.shortDescription ?? instance.description)
|
||||
|
||||
if let thumbnail = instance.thumbnail {
|
||||
updateThumbnail(url: thumbnail)
|
||||
|
|
|
@ -14,12 +14,12 @@ import SafariServices
|
|||
class ProfileFieldValueView: UIView {
|
||||
weak var navigationDelegate: TuskerNavigationDelegate?
|
||||
|
||||
private static let converter = HTMLConverter(
|
||||
font: .preferredFont(forTextStyle: .body),
|
||||
monospaceFont: UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular)),
|
||||
color: .label,
|
||||
paragraphStyle: .default
|
||||
)
|
||||
private static let converter: HTMLConverter = {
|
||||
var converter = HTMLConverter()
|
||||
converter.font = .preferredFont(forTextStyle: .body)
|
||||
converter.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
|
||||
return converter
|
||||
}()
|
||||
|
||||
private let account: AccountMO
|
||||
private let field: Account.Field
|
||||
|
|
|
@ -95,6 +95,8 @@ class ProfileHeaderView: UIView {
|
|||
relationshipLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14))
|
||||
relationshipLabel.adjustsFontForContentSizeCategory = true
|
||||
|
||||
noteTextView.defaultFont = .preferredFont(forTextStyle: .body)
|
||||
noteTextView.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
|
||||
noteTextView.adjustsFontForContentSizeCategory = true
|
||||
|
||||
pagesSegmentedControl = ScrollingSegmentedControl(frame: .zero)
|
||||
|
@ -138,7 +140,7 @@ class ProfileHeaderView: UIView {
|
|||
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForProfile(accountID: accountID, source: .view(moreButton), fetchRelationship: false) ?? [])
|
||||
|
||||
noteTextView.navigationDelegate = delegate
|
||||
noteTextView.setBodyTextFromHTML(account.note)
|
||||
noteTextView.setTextFromHtml(account.note)
|
||||
noteTextView.setEmojis(account.emojis, identifier: account.id)
|
||||
|
||||
if accountID == mastodonController.account?.id {
|
||||
|
|
|
@ -17,13 +17,6 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
|||
static let contentParagraphStyle = HTMLConverter.defaultParagraphStyle
|
||||
static let metaFont = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 15))
|
||||
|
||||
private static let htmlConverter = HTMLConverter(
|
||||
font: ConversationMainStatusCollectionViewCell.contentFont,
|
||||
monospaceFont: ConversationMainStatusCollectionViewCell.monospaceFont,
|
||||
color: .label,
|
||||
paragraphStyle: ConversationMainStatusCollectionViewCell.contentParagraphStyle
|
||||
)
|
||||
|
||||
static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
|
@ -139,7 +132,9 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
|||
$0.backgroundColor = nil
|
||||
$0.isEditable = false
|
||||
$0.isSelectable = true
|
||||
$0.emojiFont = ConversationMainStatusCollectionViewCell.contentFont
|
||||
$0.defaultFont = ConversationMainStatusCollectionViewCell.contentFont
|
||||
$0.monospaceFont = ConversationMainStatusCollectionViewCell.monospaceFont
|
||||
$0.paragraphStyle = ConversationMainStatusCollectionViewCell.contentParagraphStyle
|
||||
$0.dataDetectorTypes = [.flightNumber, .address, .shipmentTrackingNumber, .phoneNumber]
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.dataDetectorTypes.formUnion([.money, .physicalValue])
|
||||
|
@ -386,9 +381,10 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
|||
self.statusID = statusID
|
||||
self.statusState = state
|
||||
|
||||
let html = translation?.content ?? status.content
|
||||
let attributedContent = ConversationMainStatusCollectionViewCell.htmlConverter.convert(html)
|
||||
doUpdateUI(status: status, content: attributedContent)
|
||||
let attributedTranslatedContent: NSAttributedString? = translation.map {
|
||||
contentTextView.htmlConverter.convert($0.content)
|
||||
}
|
||||
doUpdateUI(status: status, precomputedContent: attributedTranslatedContent)
|
||||
|
||||
if !status.spoilerText.isEmpty,
|
||||
let translated = translation?.spoilerText {
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import Pachyderm
|
||||
import SafariServices
|
||||
import WebURLFoundationExtras
|
||||
import HTMLStreamer
|
||||
import SwiftSoup
|
||||
|
||||
class StatusCardView: UIView {
|
||||
|
||||
|
@ -191,8 +191,7 @@ class StatusCardView: UIView {
|
|||
titleLabel.text = title
|
||||
titleLabel.isHidden = title.isEmpty
|
||||
|
||||
var converter = TextConverter(callbacks: HTMLConverter.Callbacks.self)
|
||||
let description = converter.convert(html: card.description)
|
||||
let description = try! SwiftSoup.parseBodyFragment(card.description).text()
|
||||
descriptionLabel.text = description
|
||||
descriptionLabel.isHidden = description.isEmpty
|
||||
|
||||
|
|
|
@ -88,13 +88,13 @@ extension StatusCollectionViewCell {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func doUpdateUI(status: StatusMO, content: NSAttributedString) {
|
||||
func doUpdateUI(status: StatusMO, precomputedContent: NSAttributedString? = nil) {
|
||||
statusID = status.id
|
||||
accountID = status.account.id
|
||||
|
||||
updateAccountUI(account: status.account)
|
||||
|
||||
contentTextView.setTextFrom(status: status, content: content)
|
||||
contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
||||
contentTextView.navigationDelegate = delegate
|
||||
self.updateAttachmentsUI(status: status)
|
||||
pollView.isHidden = status.poll == nil
|
||||
|
|
|
@ -20,13 +20,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
static let monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 16, weight: .regular))
|
||||
static let contentParagraphStyle = HTMLConverter.defaultParagraphStyle
|
||||
|
||||
static let htmlConverter = HTMLConverter(
|
||||
font: TimelineStatusCollectionViewCell.contentFont,
|
||||
monospaceFont: TimelineStatusCollectionViewCell.monospaceFont,
|
||||
color: .label,
|
||||
paragraphStyle: TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
)
|
||||
|
||||
private static let timelineReasonIconSize: CGFloat = 25
|
||||
|
||||
// MARK: Subviews
|
||||
|
@ -208,7 +201,9 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
$0.backgroundColor = nil
|
||||
$0.isEditable = false
|
||||
$0.isSelectable = false
|
||||
$0.emojiFont = TimelineStatusCollectionViewCell.contentFont
|
||||
$0.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||
$0.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
$0.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
}
|
||||
|
||||
let cardView = StatusCardView().configure {
|
||||
|
@ -615,8 +610,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
mainContainerTopToReblogLabelConstraint.isActive = true
|
||||
}
|
||||
|
||||
let content = precomputedContent ?? TimelineStatusCollectionViewCell.htmlConverter.convert(status.content)
|
||||
doUpdateUI(status: status, content: content)
|
||||
doUpdateUI(status: status, precomputedContent: precomputedContent)
|
||||
|
||||
doUpdateTimestamp(status: status)
|
||||
timestampLabel.isHidden = showPinned
|
||||
|
|
|
@ -14,9 +14,13 @@ class StatusContentTextView: ContentTextView {
|
|||
|
||||
private var statusID: String?
|
||||
|
||||
func setTextFrom(status: some StatusProtocol, content attributedText: NSAttributedString) {
|
||||
func setTextFrom(status: some StatusProtocol, precomputed attributedText: NSAttributedString? = nil) {
|
||||
statusID = status.id
|
||||
if let attributedText {
|
||||
self.attributedText = attributedText
|
||||
} else {
|
||||
setTextFromHtml(status.content)
|
||||
}
|
||||
setEmojis(status.emojis, identifier: status.id)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue