import Ink extension PageGenerationResults: ItemLinkResults { } struct MarkdownLinkProcessor: MarkdownProcessor { static let modifier: Modifier.Target = .links private let content: Content private let results: ItemLinkResults private let language: ContentLanguage init(content: Content, results: ItemLinkResults, language: ContentLanguage) { self.content = content self.results = results self.language = language } init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results self.language = language } func process(html: String, markdown: Substring) -> String { let markdownUrl = markdown.between("(", and: ")") guard let (textToReplace, url) = convert(markdownUrl: markdownUrl) else { // Keep original code, no changes needed return html } guard let convertedUrl = url else { // Remove link since the target can't be found return markdown.between("[", and: "]") } return html.replacingOccurrences(of: textToReplace, with: convertedUrl) } private let pageLinkMarker = "page:" private let tagLinkMarker = "tag:" private let fileLinkMarker = "file:" func convert(markdownUrl: String) -> (textToChange: String, url: String?)? { if let result = handle(prefix: pageLinkMarker, in: markdownUrl, handler: convert(inlinePageLink:)) { return result } if let result = handle(prefix: tagLinkMarker, in: markdownUrl, handler: convert(inlineTagLink:)) { return result } if let result = handle(prefix: fileLinkMarker, in: markdownUrl, handler: convert(inlineFileLink:)) { return result } results.externalLink(to: markdownUrl) // No need to change anything return nil } private func handle(prefix: String, in markdownUrl: String, handler: (String) -> String?) -> (textToChange: String, url: String?)? { guard markdownUrl.hasPrefix(prefix) else { // Continue search return nil } let inlineLink = markdownUrl.replacingOccurrences(of: prefix, with: "") let itemId = inlineLink.dropAfterFirst("#") let url = handler(itemId) return (textToChange: prefix + itemId, url: url) } private func convert(inlinePageLink pageId: String) -> String? { guard let page = content.page(pageId) else { results.missing(page: pageId, source: "Inline page link") // Remove link since the page can't be found return nil } // Note link even for draft pages results.linked(to: page) guard !page.isDraft else { // TODO: Report links to draft pages return nil } return page.absoluteUrl(in: language) } private func convert(inlineTagLink: String) -> String? { let tagId = inlineTagLink.dropAfterFirst("#") guard let tag = content.tag(tagId) else { results.missing(tag: tagId, source: "Inline tag link") // Remove link since the tag can't be found return nil } results.linked(to: tag) return tag.absoluteUrl(in: language) } private func convert(inlineFileLink fileId: String) -> String? { guard let file = content.file(fileId) else { results.missing(file: fileId, source: "Inline file link") // Remove link since the file can't be found return nil } results.require(file: file) return file.absoluteUrl } }