Refactor page content generators
This commit is contained in:
@ -0,0 +1,32 @@
|
||||
import Ink
|
||||
|
||||
struct MarkdownCodeProcessor: MarkdownProcessor {
|
||||
|
||||
static let modifier: Modifier.Target = .codeBlocks
|
||||
|
||||
private let results: PageGenerationResults
|
||||
|
||||
private let blocks: [ContentBlock : BlockProcessor]
|
||||
|
||||
private let other: OtherCodeProcessor
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.results = results
|
||||
self.other = .init(results: results)
|
||||
|
||||
self.blocks = ContentBlock.allCases.reduce(into: [:]) { blocks, block in
|
||||
blocks[block] = block.processor.init(content: content, results: results, language: language)
|
||||
}
|
||||
}
|
||||
|
||||
private let codeHighlightFooter = "<script>hljs.highlightAll();</script>"
|
||||
|
||||
func process(html: String, markdown: Substring) -> String {
|
||||
let input = String(markdown)
|
||||
let rawBlockId = input.dropAfterFirst("\n").dropBeforeFirst("```").trimmed
|
||||
guard let blockId = ContentBlock(rawValue: rawBlockId) else {
|
||||
return other.process(html: html)
|
||||
}
|
||||
return blocks[blockId]!.process(markdown)
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import Ink
|
||||
|
||||
struct MarkdownHeadlineProcessor: MarkdownProcessor {
|
||||
|
||||
static let modifier: Modifier.Target = .headings
|
||||
|
||||
let content: Content
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
self.language = language
|
||||
}
|
||||
|
||||
/**
|
||||
Modify headlines by extracting an id from the headline and adding it into the html element
|
||||
|
||||
Format: ##<title>#<id>
|
||||
|
||||
The id is created by lowercasing the string, removing all special characters, and replacing spaces with scores
|
||||
*/
|
||||
func process(html: String, markdown: Substring) -> String {
|
||||
let id = markdown
|
||||
.last(after: "#")
|
||||
.trimmed
|
||||
.filter { $0.isNumber || $0.isLetter || $0 == " " }
|
||||
.lowercased()
|
||||
.components(separatedBy: " ")
|
||||
.filter { $0 != "" }
|
||||
.joined(separator: "-")
|
||||
let parts = html.components(separatedBy: ">")
|
||||
return parts[0] + " id=\"\(id)\">" + parts.dropFirst().joined(separator: ">")
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import Ink
|
||||
|
||||
struct MarkdownImageProcessor: MarkdownProcessor {
|
||||
|
||||
static var modifier: Modifier.Target = .images
|
||||
|
||||
private let content: Content
|
||||
|
||||
private let results: PageGenerationResults
|
||||
|
||||
private let language: ContentLanguage
|
||||
|
||||
private let commands: [CommandType : CommandProcessor]
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
self.language = language
|
||||
|
||||
self.commands = CommandType.allCases.reduce(into: [:]) { commands, command in
|
||||
commands[command] = command.processor.init(content: content, results: results, language: language)
|
||||
}
|
||||
}
|
||||
|
||||
var html: HtmlCommand {
|
||||
commands[.includedHtml] as! HtmlCommand
|
||||
}
|
||||
|
||||
func process(html: String, markdown: Substring) -> String {
|
||||
let argumentList = markdown.between(first: "](", andLast: ")").percentDecoded()
|
||||
let arguments = argumentList.components(separatedBy: ";")
|
||||
|
||||
let rawCommand = markdown.between("![", and: "]").trimmed.percentDecoded()
|
||||
guard rawCommand != "" else {
|
||||
return commands[.image]!.process(arguments, markdown: markdown)
|
||||
}
|
||||
|
||||
guard let command = CommandType(rawValue: rawCommand) else {
|
||||
// Treat unknown commands as normal links
|
||||
results.invalid(command: nil, markdown)
|
||||
return html
|
||||
}
|
||||
return commands[command]!.process(arguments, markdown: markdown)
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import Ink
|
||||
|
||||
struct MarkdownLinkProcessor: MarkdownProcessor {
|
||||
|
||||
static let modifier: Modifier.Target = .links
|
||||
|
||||
private let content: Content
|
||||
|
||||
private let results: PageGenerationResults
|
||||
|
||||
private let language: ContentLanguage
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
self.language = language
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if url.hasPrefix(tagLinkMarker) {
|
||||
return handleInlineTagLink(url: url, html: html, markdown: markdown)
|
||||
}
|
||||
if url.hasPrefix(fileLinkMarker) {
|
||||
return handleInlineFileLink(url: url, html: html, markdown: markdown)
|
||||
}
|
||||
results.externalLink(to: url)
|
||||
return html
|
||||
}
|
||||
|
||||
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: "")
|
||||
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: "]")
|
||||
}
|
||||
guard !page.isDraft else {
|
||||
return markdown.between("[", and: "]")
|
||||
}
|
||||
|
||||
results.linked(to: page)
|
||||
let pagePath = page.absoluteUrl(in: language)
|
||||
return html.replacingOccurrences(of: textToChange, with: pagePath)
|
||||
}
|
||||
|
||||
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: "")
|
||||
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: "]")
|
||||
}
|
||||
results.linked(to: tag)
|
||||
let tagPath = tag.absoluteUrl(in: language)
|
||||
return html.replacingOccurrences(of: textToChange, with: tagPath)
|
||||
}
|
||||
|
||||
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: "")
|
||||
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: "]")
|
||||
}
|
||||
results.require(file: file)
|
||||
let filePath = file.absoluteUrl
|
||||
return html.replacingOccurrences(of: url, with: filePath)
|
||||
}
|
||||
}
|
10
CHDataManagement/Generator/Markdown/MarkdownProcessor.swift
Normal file
10
CHDataManagement/Generator/Markdown/MarkdownProcessor.swift
Normal file
@ -0,0 +1,10 @@
|
||||
import Ink
|
||||
|
||||
protocol MarkdownProcessor {
|
||||
|
||||
static var modifier: Modifier.Target { get }
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage)
|
||||
|
||||
func process(html: String, markdown: Substring) -> String
|
||||
}
|
Reference in New Issue
Block a user