1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

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
This commit is contained in:
William Casarin 2023-07-16 14:32:24 -07:00
parent bf1175f22c
commit bd4c29604f
8 changed files with 26 additions and 175 deletions

View File

@ -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 */,

View File

@ -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))

View File

@ -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)
}
}

View File

@ -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))

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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