Process post content as markdown

This commit is contained in:
Christoph Hagen
2025-02-03 12:14:07 +01:00
parent a29e6229c1
commit d41c54d174
8 changed files with 194 additions and 46 deletions

View File

@ -0,0 +1,18 @@
protocol ItemLinkResults {
func externalLink(to url: String)
func missing(page: String, source: String)
func linked(to page: Page)
func missing(tag: String, source: String)
func linked(to tag: Tag)
func missing(file: String, source: String)
func require(file: FileResource)
}

View File

@ -1,84 +1,110 @@
import Ink
extension PageGenerationResults: ItemLinkResults {
}
struct MarkdownLinkProcessor: MarkdownProcessor {
static let modifier: Modifier.Target = .links
private let content: Content
private let results: PageGenerationResults
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 process(html: String, markdown: Substring) -> String {
let url = markdown.between("(", and: ")")
if url.hasPrefix(pageLinkMarker) {
return handleInlinePageLink(url: url, html: html, markdown: markdown)
func convert(markdownUrl: String) -> (textToChange: String, url: String?)? {
if let result = handle(prefix: pageLinkMarker, in: markdownUrl, handler: convert(inlinePageLink:)) {
return result
}
if url.hasPrefix(tagLinkMarker) {
return handleInlineTagLink(url: url, html: html, markdown: markdown)
if let result = handle(prefix: tagLinkMarker, in: markdownUrl, handler: convert(inlineTagLink:)) {
return result
}
if url.hasPrefix(fileLinkMarker) {
return handleInlineFileLink(url: url, html: html, markdown: markdown)
if let result = handle(prefix: fileLinkMarker, in: markdownUrl, handler: convert(inlineFileLink:)) {
return result
}
results.externalLink(to: url)
return html
results.externalLink(to: markdownUrl)
// No need to change anything
return nil
}
private func handleInlinePageLink(url: String, html: String, markdown: Substring) -> String {
// Retain links pointing to elements within a page
let textToChange = url.dropAfterFirst("#")
let pageId = textToChange.replacingOccurrences(of: pageLinkMarker, with: "")
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 markdown.between("[", and: "]")
return nil
}
guard !page.isDraft else {
return markdown.between("[", and: "]")
}
// Note link even for draft pages
results.linked(to: page)
let pagePath = page.absoluteUrl(in: language)
return html.replacingOccurrences(of: textToChange, with: pagePath)
guard !page.isDraft else {
// TODO: Report links to draft pages
return nil
}
return page.absoluteUrl(in: language)
}
private func handleInlineTagLink(url: String, html: String, markdown: Substring) -> String {
// Retain links pointing to elements within a page
let textToChange = url.dropAfterFirst("#")
let tagId = textToChange.replacingOccurrences(of: tagLinkMarker, with: "")
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 markdown.between("[", and: "]")
return nil
}
results.linked(to: tag)
let tagPath = tag.absoluteUrl(in: language)
return html.replacingOccurrences(of: textToChange, with: tagPath)
return tag.absoluteUrl(in: language)
}
private func handleInlineFileLink(url: String, html: String, markdown: Substring) -> String {
// Retain links pointing to elements within a page
let fileId = url.replacingOccurrences(of: fileLinkMarker, with: "")
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 markdown.between("[", and: "]")
return nil
}
results.require(file: file)
let filePath = file.absoluteUrl
return html.replacingOccurrences(of: url, with: filePath)
return file.absoluteUrl
}
}