commit bd4c29604fed4eab611fd045baa791d464561262
parent bf1175f22c5ca3ae8a7ffd72a6727887bd769bff
Author: William Casarin <jb55@jb55.com>
Date: Sun, 16 Jul 2023 14:32:24 -0700
Fix broken markdown renderer
This switches away from the old markdown renderer to the new one at
https://github.com/damus-io/swift-markdown-ui
Changelog-Fixed: Fix broken markdown renderer
Diffstat:
8 files changed, 26 insertions(+), 175 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -96,7 +96,6 @@
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; };
4C363AA828297703006E126D /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.swift */; };
- 4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D322960DB0500558C0F /* Markdown.swift */; };
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D3629637E0500558C0F /* PreviewCache.swift */; };
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79A28306D7B00E1F516 /* Contacts.swift */; };
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79C2833036D00E1F516 /* FollowingView.swift */; };
@@ -540,7 +539,6 @@
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
4C363AA328296DEE006E126D /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = "<group>"; };
4C363AA728297703006E126D /* InsertSort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertSort.swift; sourceTree = "<group>"; };
- 4C3A1D322960DB0500558C0F /* Markdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Markdown.swift; sourceTree = "<group>"; };
4C3A1D3629637E0500558C0F /* PreviewCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCache.swift; sourceTree = "<group>"; };
4C3AC79A28306D7B00E1F516 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = "<group>"; };
4C3AC79C2833036D00E1F516 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; };
@@ -1245,7 +1243,6 @@
4CE879492995B58700F758CC /* Relays */,
4CF0ABEA29844B2F00D66079 /* AnyCodable */,
4CC7AAE6297EFA7B00430951 /* Zap.swift */,
- 4C3A1D322960DB0500558C0F /* Markdown.swift */,
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */,
4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */,
4CE4F8CC281352B30009DFBB /* Notifications.swift */,
@@ -2092,7 +2089,6 @@
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */,
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
- 4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
4C9147002A2A891E00DDEA40 /* error.c in Sources */,
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */,
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -1243,7 +1243,7 @@ func render_notification_content_preview(cache: EventCache, ev: NostrEvent, prof
}
switch artifacts {
- case .parts:
+ case .longform:
// we should never hit this until we have more note types built out of parts
// since we handle this case above in known_kind == .longform
return String(ev.content.prefix(prefix_len))
diff --git a/damus/Util/Markdown.swift b/damus/Util/Markdown.swift
@@ -1,88 +0,0 @@
-//
-// Markdown.swift
-// damus
-//
-// Created by Lionello Lunesu on 2022-12-28.
-//
-
-import Foundation
-import SwiftUI
-
-func count_leading_hashes(_ str: String) -> Int {
- var count = 0
- for c in str {
- if c == "#" {
- count += 1
- } else {
- break
- }
- }
-
- return count
-}
-
-func get_heading_title_size(count: Int) -> SwiftUI.Font {
- if count >= 3 {
- return Font.title3
- } else if count >= 2 {
- return Font.title2
- } else if count >= 1 {
- return Font.title
- }
-
- return Font.body
-}
-
-public struct Markdown {
- private var detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
-
- /// Ensure the specified URL has a scheme by prepending "https://" if it's absent.
- static func withScheme(_ url: any StringProtocol) -> any StringProtocol {
- return url.contains("://") ? url : "https://" + url
- }
-
- /// Parse a string with markdown into an `AttributedString`, if possible, or else return it as regular text.
- public static func parse(content: String) -> AttributedString {
- let md_opts: AttributedString.MarkdownParsingOptions =
- .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
-
- guard content.utf8.count > 0 else {
- return AttributedString(stringLiteral: "")
- }
-
- let leading_hashes = count_leading_hashes(content)
- if leading_hashes > 0 {
- if var str = try? AttributedString(markdown: content) {
- str.font = get_heading_title_size(count: leading_hashes)
- return str
- }
- }
-
- // TODO: escape unintentional markdown
- let escaped = content.replacingOccurrences(of: "\\_", with: "\\\\\\_")
- if let txt = try? AttributedString(markdown: escaped, options: md_opts) {
- return txt
- } else {
- return AttributedString(stringLiteral: content)
- }
- }
-
- /// Process the input text and add markdown for any embedded URLs.
- public func process(_ input: String) -> AttributedString {
- guard let detector else {
- return AttributedString(stringLiteral: input)
- }
- let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
- var output = input
- // Start with the last match, because replacing the first would invalidate all subsequent indices
- for match in matches.reversed() {
- guard let range = Range(match.range, in: input)
- , let url = match.url else { continue }
- let text = input[range]
- // Use the absoluteString from the matched URL, except when it defaults to http (since we default to https)
- let uri = url.scheme == "http" ? Markdown.withScheme(text) : url.absoluteString
- output.replaceSubrange(range, with: "[\(text)](\(uri))")
- }
- return Markdown.parse(content: output)
- }
-}
diff --git a/damus/Views/EULAView.swift b/damus/Views/EULAView.swift
@@ -7,6 +7,8 @@
import SwiftUI
+import MarkdownUI
+
let eula = """
**End User License Agreement**
@@ -63,7 +65,7 @@ struct EULAView: View {
var body: some View {
ZStack {
ScrollView {
- Text(Markdown.parse(content: eula))
+ Markdown(eula)
.padding()
}
.padding(EdgeInsets(top: 20, leading: 10, bottom: 50, trailing: 10))
diff --git a/damus/Views/Events/Longform/LongformPreview.swift b/damus/Views/Events/Longform/LongformPreview.swift
@@ -58,9 +58,9 @@ struct LongformPreviewBody: View {
.foregroundColor(.gray)
if case .loaded(let arts) = artifacts.state,
- case .parts(let parts) = arts
+ case .longform(let longform) = arts
{
- Words(parts.words).font(.footnote)
+ Words(longform.words).font(.footnote)
}
}
}
diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift
@@ -11,8 +11,6 @@ struct FollowUserView: View {
let target: FollowTarget
let damus_state: DamusState
- static let markdown = Markdown()
-
var body: some View {
HStack {
UserViewRow(damus_state: damus_state, pubkey: target.pubkey)
diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift
@@ -8,6 +8,7 @@
import SwiftUI
import LinkPresentation
import NaturalLanguage
+import MarkdownUI
struct Blur: UIViewRepresentable {
var style: UIBlurEffect.Style = .systemUltraThinMaterial
@@ -212,8 +213,9 @@ struct NoteContentView: View {
var ArtifactContent: some View {
Group {
switch self.note_artifacts {
- case .parts(let parts):
- artifactPartsView(parts.parts)
+ case .longform(let md):
+ Markdown(md.markdown)
+ .padding(.horizontal)
case .separated(let separated):
MainContent(artifacts: separated)
}
@@ -285,22 +287,27 @@ func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText {
}
}
+struct LongformContent {
+ let markdown: MarkdownContent
+ let words: Int
+
+ init(_ markdown: String) {
+ let blocks = [BlockNode].init(markdown: markdown)
+ self.markdown = MarkdownContent(blocks: blocks)
+ self.words = count_markdown_words(blocks: blocks)
+ }
+}
+
enum NoteArtifacts {
case separated(NoteArtifactsSeparated)
- case parts(NoteArtifactsParts)
-
+ case longform(LongformContent)
+
var images: [URL] {
switch self {
case .separated(let arts):
return arts.images
- case .parts(let parts):
- return parts.parts.reduce(into: [URL]()) { acc, part in
- guard case .media(let m) = part,
- case .image(let url) = m
- else { return }
-
- acc.append(url)
- }
+ case .longform:
+ return []
}
}
}
@@ -390,7 +397,7 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
let blocks = ev.blocks(privkey)
if ev.known_kind == .longform {
- return .parts(render_blocks_parted(blocks: blocks, profiles: profiles))
+ return .longform(LongformContent(ev.content))
}
return .separated(render_blocks(blocks: blocks, profiles: profiles))
@@ -409,68 +416,6 @@ fileprivate func artifact_part_last_text_ind(parts: [ArtifactPart]) -> (Int, Tex
return (ind, txt)
}
-func render_blocks_parted(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsParts {
- let blocks = bs.blocks
-
- let new_parts = NoteArtifactsParts(parts: [], words: bs.words)
-
- return blocks.reduce(into: new_parts) { parts, block in
-
- switch block {
- case .mention(let m):
- guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
- parts.parts.append(.text(mention_str(m, profiles: profiles).text))
- return
- }
- parts.parts[last_ind] = .text(txt + mention_str(m, profiles: profiles).text)
-
- case .text(let str):
- guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
- // TODO: (jb55) md is longform specific
- let md = Markdown.parse(content: str)
- parts.parts.append(.text(Text(md)))
- return
- }
-
- parts.parts[last_ind] = .text(txt + Text(str))
-
- case .relay(let relay):
- guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
- parts.parts.append(.text(Text(relay)))
- return
- }
-
- parts.parts[last_ind] = .text(txt + Text(relay))
-
- case .hashtag(let htag):
- guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
- parts.parts.append(.text(hashtag_str(htag).text))
- return
- }
-
- parts.parts[last_ind] = .text(txt + hashtag_str(htag).text)
-
- case .invoice(let invoice):
- parts.parts.append(.invoice(invoice))
- return
-
- case .url(let url):
- let url_type = classify_url(url)
- switch url_type {
- case .media(let media_url):
- parts.parts.append(.media(media_url))
- case .link(let url):
- guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
- parts.parts.append(.text(url_str(url).text))
- return
- }
-
- parts.parts[last_ind] = .text(txt + url_str(url).text)
- }
- }
- }
-}
-
func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Bool) -> CompatibleText {
var trimmed = txt
diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift
@@ -103,8 +103,6 @@ struct ProfileView: View {
let damus_state: DamusState
let pfp_size: CGFloat = 90.0
let bannerHeight: CGFloat = 150.0
-
- static let markdown = Markdown()
@State var is_zoomed: Bool = false
@State var show_share_sheet: Bool = false