Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace dependency on Ink with swift-markdown. #149

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

peterkovacs
Copy link

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 and height attributes to the generated HTML.
  • 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!

Create a Markdown Visitor that converts directly to Plot.Node.
@Ze0nC
Copy link
Contributor

Ze0nC commented Aug 7, 2023

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. apple/swift-markdown#106). Maybe we can implement the node conversion part and wait for "official" support for html of swift-markdown?

@Kyle-Ye
Copy link

Kyle-Ye commented Aug 14, 2023

+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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants