mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
93 lines
3.6 KiB
93 lines
3.6 KiB
// ------------------------------------
|
|
// Markdown - GitHub-style alerts
|
|
// ------------------------------------
|
|
|
|
/**
|
|
* The five kinds GitHub defines, and what each one becomes here.
|
|
*
|
|
* They are mapped onto the admonition classes the content stylesheet already draws — the ones
|
|
* `{.is-info}` and friends attach — so an alert and a hand-classed blockquote are the same object on
|
|
* the page, and there is one place where an admonition is styled. `important` is the one kind with no
|
|
* existing counterpart, and has a hue of its own in `css/_page-contents.scss`.
|
|
*
|
|
* The labels are English, as the marker itself is: what the renderer emits is stored as the page's
|
|
* HTML, so nothing here can follow the reader's locale afterwards.
|
|
*/
|
|
const KINDS = new Map([
|
|
['note', { className: 'is-info', label: 'Note' }],
|
|
['tip', { className: 'is-success', label: 'Tip' }],
|
|
['important', { className: 'is-important', label: 'Important' }],
|
|
['warning', { className: 'is-warning', label: 'Warning' }],
|
|
['caution', { className: 'is-danger', label: 'Caution' }]
|
|
])
|
|
|
|
/**
|
|
* The marker, which has to be the whole of the blockquote's first line.
|
|
*
|
|
* Anything after it on that line means the author wrote a blockquote that happens to open with
|
|
* brackets, which is what GitHub decides too — and the line is then left exactly as it was typed.
|
|
*/
|
|
const MARKER = /^\[!([a-z]+)\][ \t]*(?:\n|$)/i
|
|
|
|
/**
|
|
* The label, as three tokens: a paragraph carrying a class, its inline content, and the close.
|
|
*
|
|
* The inline token is left with nothing but `content`; the core `inline` rule runs after this one and
|
|
* is what turns that into children, the same as for every other paragraph on the page.
|
|
*/
|
|
function labelTokens(state, label) {
|
|
const open = new state.Token('paragraph_open', 'p', 1)
|
|
open.attrSet('class', 'alert-title')
|
|
open.block = true
|
|
|
|
const inline = new state.Token('inline', '', 0)
|
|
inline.content = label
|
|
inline.children = []
|
|
|
|
const close = new state.Token('paragraph_close', 'p', -1)
|
|
close.block = true
|
|
|
|
return [open, inline, close]
|
|
}
|
|
|
|
export default (md) => {
|
|
/*
|
|
After `block` and so before `inline`, which is what makes this a matter of cutting a line off a
|
|
string: at this point a paragraph is still one `inline` token holding its raw source. Run after
|
|
`inline` instead and the same job means walking children and reasoning about where markdown-it put
|
|
the break — a soft one, or a hard one where the author left two spaces after the marker, as the
|
|
examples in GitHub's own documentation do.
|
|
*/
|
|
md.core.ruler.after('block', 'github_alert', (state) => {
|
|
const tokens = state.tokens
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
if (
|
|
tokens[i].type !== 'blockquote_open' ||
|
|
tokens[i + 1]?.type !== 'paragraph_open' ||
|
|
tokens[i + 2]?.type !== 'inline'
|
|
) {
|
|
continue
|
|
}
|
|
|
|
const marker = MARKER.exec(tokens[i + 2].content)
|
|
const kind = marker ? KINDS.get(marker[1].toLowerCase()) : null
|
|
if (!kind) {
|
|
continue
|
|
}
|
|
|
|
// -> Joined rather than set: an author may have classed the quote themselves, and `is-info` on
|
|
// top of that is what the stylesheet is written to expect
|
|
tokens[i].attrJoin('class', kind.className)
|
|
|
|
const rest = tokens[i + 2].content.slice(marker[0].length)
|
|
if (rest) {
|
|
tokens[i + 2].content = rest
|
|
tokens.splice(i + 1, 0, ...labelTokens(state, kind.label))
|
|
} else {
|
|
// -> The marker was the whole paragraph, so the label takes its place rather than joining it
|
|
tokens.splice(i + 1, 3, ...labelTokens(state, kind.label))
|
|
}
|
|
}
|
|
})
|
|
}
|