mirror of https://github.com/lukechilds/damus.git
Lionello Lunesu
2 years ago
3 changed files with 90 additions and 3 deletions
@ -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) |
|||
} |
|||
} |
@ -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) |
|||
} |
|||
} |
Loading…
Reference in new issue