1
0
mirror of git://jb55.com/damus synced 2024-09-18 19:23:49 +00:00

Make links clickable in profile view

This commit is contained in:
Lionello Lunesu 2022-12-28 13:29:18 -08:00
parent a67cd2a841
commit 75c67bc1e9
3 changed files with 90 additions and 3 deletions

43
damus/Util/Markdown.swift Normal file
View File

@ -0,0 +1,43 @@
//
// Markdown.swift
// damus
//
// Created by Lionello Lunesu on 2022-12-28.
//
import Foundation
public struct Markdown {
private let 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
}
static func parseMarkdown(content: String) -> AttributedString {
// Similar to the parsing in NoteContentView
let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
if let txt = try? AttributedString(markdown: content, 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 {
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) else { continue }
let url = input[range]
output.replaceSubrange(range, with: "[\(url)](\(Markdown.withScheme(url)))")
}
// TODO: escape unintentional markdown
return Markdown.parseMarkdown(content: output)
}
}

View File

@ -143,7 +143,9 @@ struct ProfileView: View {
}
}
}
static let markdownHelper = Markdown()
var DMButton: some View {
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
@ -179,7 +181,6 @@ struct ProfileView: View {
DMButton
if profile.pubkey != damus_state.pubkey {
FollowButtonView(
target: profile.get_follow_target(),
@ -196,7 +197,7 @@ struct ProfileView: View {
ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts)
.padding(.bottom)
Text(data?.about ?? "")
Text(ProfileView.markdownHelper.process(data?.about ?? ""))
.font(.subheadline)
Divider()

View File

@ -0,0 +1,43 @@
//
// MarkdownTests.swift
// damusTests
//
// Created by Lionello Lunesu on 2022-12-28.
//
import XCTest
@testable import damus
class MarkdownTests: XCTestCase {
let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func test_convert_link() throws {
let helper = Markdown()
let md = helper.process("prologue https://nostr.build epilogue")
let expected = try AttributedString(markdown: "prologue [https://nostr.build](https://nostr.build) epilogue", options: md_opts)
XCTAssertEqual(md, expected)
}
func test_convert_link_no_scheme() throws {
let helper = Markdown()
let md = helper.process("prologue damus.io epilogue")
let expected = try AttributedString(markdown: "prologue [damus.io](https://damus.io) epilogue", options: md_opts)
XCTAssertEqual(md, expected)
}
func test_convert_links() throws {
let helper = Markdown()
let md = helper.process("prologue damus.io https://nostr.build epilogue")
let expected = try AttributedString(markdown: "prologue [damus.io](https://damus.io) [https://nostr.build](https://nostr.build) epilogue", options: md_opts)
XCTAssertEqual(md, expected)
}
}