~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
85 lines · 3.8 KB Raw History
import Foundation

/// The pulls and issues a Markdown text names by their `at://` URIs, as the
/// server links them: read from the HTML it made of the text, where each one
/// of the repository's is a link to its page, a bare one showing its title.
/// One the server didn't link, as another repository's, stays as written.
struct MarkdownReferences: Hashable, Sendable {
    /// The title of each the server linked, by its URI.
    var titles: [String: String] = [:]
    /// Each the server linked, a bare one or a link's target.
    var known: Set<String> = []

    /// What a link to a pull or issue the app opens itself points at.
    static let scheme = "knotless-thread"

    init() {}

    init(html: String, source: String) {
        for found in html.matches(of: #/<a href="/[^/"]+/[^/"]+/(issue|pull)/(did:[^/"]+)/([^/"]+)">(.*?)</a>/#.dotMatchesNewlines()) {
            let uri = "at://\(found.output.2)/dev.knotless.\(found.output.1)/\(found.output.3)"
            known.insert(uri)
            let title = MarkdownFences.unescaped(String(found.output.4).replacing(#/<[^>]*>/#, with: ""))
            // Where the text links the URI itself, those are its own words, not the title.
            if titles[uri] == nil, !source.contains("[\(title)](\(uri)") { titles[uri] = title }
        }
    }

    /// `text` with each `at://` URI the server linked made a link the app opens:
    /// a bare one showing its title, as the pages show it. Not in code.
    func linked(_ text: String) -> String {
        guard !known.isEmpty, text.contains("at://") else { return text }
        var out = ""
        var done = text.startIndex
        var from = text.startIndex
        while let found = text[from...].range(of: "at://") {
            from = found.upperBound
            let at = found.lowerBound
            let before = text[..<at]
            if let last = before.last, last.isLetter || last.isNumber { continue }
            if before.filter({ $0 == "`" }).count % 2 == 1 { continue }
            let rest = text[at...]
            var end = rest.firstIndex { $0.isWhitespace || "<>()[]\"'`".contains($0) } ?? rest.endIndex
            while end > at, ".,;:!?".contains(text[text.index(before: end)]) { end = text.index(before: end) }
            let uri = String(text[at..<end])
            guard known.contains(uri) else { continue }
            if before.hasSuffix("](") {
                out += text[done..<at] + Self.target(uri)
            } else if let title = titles[uri] {
                out += text[done..<at] + "[\(Self.escaped(title))](\(Self.target(uri)))"
            } else {
                continue
            }
            done = end
            from = end
        }
        return out + text[done...]
    }

    /// The pull or issue of `repo` a link opens, if it's one: by the link this
    /// makes of its URI, or its page on the site.
    static func thread(_ url: URL, in repo: RepoRef) -> String? {
        if url.scheme == scheme {
            return url.absoluteString.dropFirst(scheme.count + 1).removingPercentEncoding
        }
        guard url.scheme == "https", url.host() == URL(string: MarkdownPlace.site)?.host() else { return nil }
        let p = url.path(percentEncoded: false).split(separator: "/").map(String.init)
        guard p.count == 5, [repo.handle, repo.did].contains(p[0]), p[1] == repo.name,
              ["issue", "pull"].contains(p[2]), p[3].hasPrefix("did:")
        else { return nil }
        return "at://\(p[3])/dev.knotless.\(p[2])/\(p[4])"
    }

    private static func target(_ uri: String) -> String {
        "\(scheme):" + (uri.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? uri)
    }

    private static func escaped(_ title: String) -> String {
        var out = ""
        for c in title {
            if "\\`*_[]<>!~".contains(c) { out.append("\\") }
            out.append(c)
        }
        return out
    }
}