Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions MarkdownViewer/Markdown/MarkdownRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct MarkdownRenderer: MarkupWalker {
var result = ""
var outline: [OutlineItem] = []
var slugger = HeadingSlugger()
private var stripCancelledPrefix = false

mutating func render(_ document: Document) -> RenderedMarkdown {
result = ""
Expand Down Expand Up @@ -86,7 +87,19 @@ struct MarkdownRenderer: MarkupWalker {
for child in paragraph.children { visit(child) }
result += "</p>\n"
case let text as Markdown.Text:
result += escapeHTML(text.string)
if stripCancelledPrefix {
stripCancelledPrefix = false
var str = text.string
for prefix in ["[-] ", "[~] "] {
if str.hasPrefix(prefix) {
str = String(str.dropFirst(prefix.count))
break
}
}
result += escapeHTML(str)
} else {
result += escapeHTML(text.string)
}
case let emphasis as Emphasis:
result += "<em>"
for child in emphasis.children { visit(child) }
Expand All @@ -108,15 +121,26 @@ struct MarkdownRenderer: MarkupWalker {
let alt = image.plainText
result += "<img src=\"\(image.source ?? "")\" alt=\"\(escapeHTML(alt))\">"
case let list as UnorderedList:
result += "<ul>\n"
let hasCheckboxes = list.children.contains {
($0 as? ListItem)?.checkbox != nil || isCancelledCheckbox($0 as? ListItem)
}
result += hasCheckboxes ? "<ul class=\"task-list\">\n" : "<ul>\n"
for child in list.children { visit(child) }
result += "</ul>\n"
case let list as OrderedList:
result += "<ol>\n"
for child in list.children { visit(child) }
result += "</ol>\n"
case let item as ListItem:
result += "<li>"
if let checkbox = item.checkbox {
let checked = checkbox == .checked ? " checked" : ""
result += "<li class=\"task-list-item\"><input type=\"checkbox\" disabled\(checked)> "
} else if isCancelledCheckbox(item) {
result += "<li class=\"task-list-item cancelled\"><input type=\"checkbox\" disabled checked> "
stripCancelledPrefix = true
} else {
result += "<li>"
}
for child in item.children { visit(child) }
result += "</li>\n"
case let quote as BlockQuote:
Expand Down Expand Up @@ -166,6 +190,19 @@ struct MarkdownRenderer: MarkupWalker {
}
}

private func isCancelledCheckbox(_ item: ListItem?) -> Bool {
guard let item = item, item.checkbox == nil else { return false }
for child in item.children {
guard let paragraph = child as? Paragraph else { continue }
for inline in paragraph.children {
guard let text = inline as? Markdown.Text else { continue }
return text.string.hasPrefix("[-] ") || text.string.hasPrefix("[~] ")
}
break
}
return false
}

private func escapeHTML(_ string: String) -> String {
var result = ""
var index = string.startIndex
Expand Down
15 changes: 15 additions & 0 deletions MarkdownViewer/Resources/markdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,21 @@ em { font-style: italic; }
color: var(--color-fg-default);
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}
ul.task-list {
list-style: none;
padding-left: 1.5em;
}
.task-list-item input[type="checkbox"] {
margin: 0 0.35em 0 -1.3em;
vertical-align: middle;
}
.task-list-item p {
display: inline;
}
.task-list-item.cancelled p {
text-decoration: line-through;
color: var(--color-fg-muted);
}
.mermaid {
margin-top: 0;
margin-bottom: 16px;
Expand Down