Publish
Publish copied to clipboard
Replace dependency on Ink with swift-markdown.
The motivation for this PR boils down to:
- Standardize on a single markdown implementation that's well-known and well-tested.
- Convert from Markdown → Node tree.
- Modifiers can hook into the parsing of (nearly) every type of markdown markup and adjust or replace the generated node structure.
- One such use of this could be to create a image resizing plugin that writes
width
andheight
attributes to the generated HTML.
- One such use of this could be to create a image resizing plugin that writes
- Unlock parsing of Block Directives.
For example:
One might imagine some HTML such as:
@TOC
# Top Level
## Second Level
Where the @TOC
is generates HTML table of contents for the document. The implementation of such a plugin might look like this:
public static var tableOfContentsPlugin: Self {
var headings: [MarkdownDocument.ID: [(level: Int, heading: String, slug: String)]] = .init()
return .init(name: "Table of Contents") { context in
let slugSafeCharacters = CharacterSet(charactersIn: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-")
func slug(_ string: String) -> String? {
if let latin = string.applyingTransform(StringTransform("Any-Latin; Latin-ASCII; Lower;"), reverse: false) {
let urlComponents = latin.components(separatedBy: slugSafeCharacters.inverted)
let result = urlComponents.filter { $0 != "" }.joined(separator: "-")
if result.count > 0 {
return result
}
}
return nil
}
// capture any heading, and it's level.
context.markdownParser.addModifier(for: .heading) { html, document, markup in
guard let heading = markup as? Markdown.Heading else { return html }
let slug = slug(heading.plainText) ?? UUID().uuidString
headings[document.id, default: []].append((heading.level, heading.plainText, slug))
return .group( .a(.attribute(named: "name", value: slug)), html )
}
context.markdownParser.addModifier(for: .blockDirective("TOC")) { _, document, _ in
let id = document.id
func tree(headings: ArraySlice<(level: Int, heading: String, slug: String)>, level: Int) -> Node<HTML.ListContext> {
guard !headings.isEmpty else { return .empty }
precondition(headings.allSatisfy { $0.0 >= level })
var headings = headings
var result: [Node<HTML.ListContext>] = []
while let first = headings.first {
let atLevel = first.level == level
if atLevel { headings = headings.dropFirst() }
let children = headings.prefix { $0.level > level }
result.append(
.li(
.if(atLevel, .a(.href("#\(first.slug)"), .text(first.heading))),
.if(!children.isEmpty, .ul(tree(headings: children, level: level + 1)))
)
)
headings = headings.dropFirst(children.count)
}
return .group(result)
}
return .lazy {
.ul(tree(headings: headings[id, default: []][...], level: 1))
}
}
}
}
Note that this depends on the Node.lazy
PR that I opened up a few weeks ago.
I should also note that I don't actually expect you to merge this (especially since its adding a couple of new external dependencies), but I thought you might be interested in some of the ideas!
Great jobs done! I have been trying the same thing (actually my site czj.io has been using swift-markdown since last autumn) but haven't achieved a proper state for merging. in fact there has been discussion about introducing html conversion to swift-markdown (e.g. https://github.com/apple/swift-markdown/pull/106). Maybe we can implement the node conversion part and wait for "official" support for html of swift-markdown?
+1 for this. I'm also trying to use swift-markdown to replace Ink on my blog's Publish-end. Hope the upstream will merge it soon.