From 38483da05207d137d25a5ce42d5f74f5b0875807 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 20 Jan 2024 13:35:42 +0100 Subject: [PATCH] feat: support GitHub-flavored alerts --- docs/guide/markdown.md | 36 +++++++++++ .../styles/components/custom-block.css | 60 ++++++++++++++++++ src/client/theme-default/styles/vars.css | 40 ++++++++++++ src/node/markdown/markdown.ts | 2 + src/node/markdown/plugins/containers.ts | 3 + src/node/markdown/plugins/githubAlerts.ts | 63 +++++++++++++++++++ 6 files changed, 204 insertions(+) create mode 100644 src/node/markdown/plugins/githubAlerts.ts diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 7dccbe7e..7a972264 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -263,6 +263,42 @@ Wraps in a
}) ``` +## GitHub-flavored Alerts + +VitePress also supports [GitHub-flavored alerts](https://github.com/orgs/community/discussions/16925) to render as callouts, they will be rendered the same as the [custom containers](/guide/markdown#default-title). + +``` +> [!NOTE] +> Highlights information that users should take into account, even when skimming. + +> [!TIP] +> Optional information to help a user be more successful. + +> [!IMPORTANT] +> Crucial information necessary for users to succeed. + +> [!WARNING] +> Critical content demanding immediate user attention due to potential risks. + +> [!CAUTION] +> Negative potential consequences of an action. +``` + +> [!NOTE] +> Highlights information that users should take into account, even when skimming. + +> [!TIP] +> Optional information to help a user be more successful. + +> [!IMPORTANT] +> Crucial information necessary for users to succeed. + +> [!WARNING] +> Critical content demanding immediate user attention due to potential risks. + +> [!CAUTION] +> Negative potential consequences of an action. + ## Syntax Highlighting in Code Blocks VitePress uses [Shikiji](https://github.com/antfu/shikiji) (an improved version of [Shiki](https://shiki.matsu.io/)) to highlight language syntax in Markdown code blocks, using coloured text. Shiki supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block: diff --git a/src/client/theme-default/styles/components/custom-block.css b/src/client/theme-default/styles/components/custom-block.css index f4527868..242080cd 100644 --- a/src/client/theme-default/styles/components/custom-block.css +++ b/src/client/theme-default/styles/components/custom-block.css @@ -27,6 +27,26 @@ background-color: var(--vp-custom-block-info-code-bg); } +.custom-block.note { + border-color: var(--vp-custom-block-note-border); + color: var(--vp-custom-block-note-text); + background-color: var(--vp-custom-block-note-bg); +} + +.custom-block.note a, +.custom-block.note code { + color: var(--vp-c-brand-1); +} + +.custom-block.note a:hover, +.custom-block.note a:hover > code { + color: var(--vp-c-brand-2); +} + +.custom-block.note code { + background-color: var(--vp-custom-block-note-code-bg); +} + .custom-block.tip { border-color: var(--vp-custom-block-tip-border); color: var(--vp-custom-block-tip-text); @@ -47,6 +67,26 @@ background-color: var(--vp-custom-block-tip-code-bg); } +.custom-block.important { + border-color: var(--vp-custom-block-important-border); + color: var(--vp-custom-block-important-text); + background-color: var(--vp-custom-block-important-bg); +} + +.custom-block.important a, +.custom-block.important code { + color: var(--vp-c-important-1); +} + +.custom-block.important a:hover, +.custom-block.important a:hover > code { + color: var(--vp-c-important-2); +} + +.custom-block.important code { + background-color: var(--vp-custom-block-important-code-bg); +} + .custom-block.warning { border-color: var(--vp-custom-block-warning-border); color: var(--vp-custom-block-warning-text); @@ -87,6 +127,26 @@ background-color: var(--vp-custom-block-danger-code-bg); } +.custom-block.caution { + border-color: var(--vp-custom-block-caution-border); + color: var(--vp-custom-block-caution-text); + background-color: var(--vp-custom-block-caution-bg); +} + +.custom-block.caution a, +.custom-block.caution code { + color: var(--vp-c-caution-1); +} + +.custom-block.caution a:hover, +.custom-block.caution a:hover > code { + color: var(--vp-c-caution-2); +} + +.custom-block.caution code { + background-color: var(--vp-custom-block-caution-code-bg); +} + .custom-block.details { border-color: var(--vp-custom-block-details-border); color: var(--vp-custom-block-details-text); diff --git a/src/client/theme-default/styles/vars.css b/src/client/theme-default/styles/vars.css index d85304cd..80525155 100644 --- a/src/client/theme-default/styles/vars.css +++ b/src/client/theme-default/styles/vars.css @@ -54,6 +54,11 @@ --vp-c-indigo-3: #5672cd; --vp-c-indigo-soft: rgba(100, 108, 255, 0.14); + --vp-c-purple-1: #6f42c1; + --vp-c-purple-2: #7e4cc9; + --vp-c-purple-3: #8e5cd9; + --vp-c-purple-soft: rgba(159, 122, 234, 0.14); + --vp-c-green-1: #18794e; --vp-c-green-2: #299764; --vp-c-green-3: #30a46c; @@ -83,6 +88,11 @@ --vp-c-indigo-3: #3e63dd; --vp-c-indigo-soft: rgba(100, 108, 255, 0.16); + --vp-c-purple-1: #c8abfa; + --vp-c-purple-2: #a879e6; + --vp-c-purple-3: #8e5cd9; + --vp-c-purple-soft: rgba(159, 122, 234, 0.16); + --vp-c-green-1: #3dd68c; --vp-c-green-2: #30a46c; --vp-c-green-3: #298459; @@ -215,11 +225,21 @@ --vp-c-tip-3: var(--vp-c-brand-3); --vp-c-tip-soft: var(--vp-c-brand-soft); + --vp-c-note-1: var(--vp-c-brand-1); + --vp-c-note-2: var(--vp-c-brand-2); + --vp-c-note-3: var(--vp-c-brand-3); + --vp-c-note-soft: var(--vp-c-brand-soft); + --vp-c-success-1: var(--vp-c-green-1); --vp-c-success-2: var(--vp-c-green-2); --vp-c-success-3: var(--vp-c-green-3); --vp-c-success-soft: var(--vp-c-green-soft); + --vp-c-important-1: var(--vp-c-purple-1); + --vp-c-important-2: var(--vp-c-purple-2); + --vp-c-important-3: var(--vp-c-purple-3); + --vp-c-important-soft: var(--vp-c-purple-soft); + --vp-c-warning-1: var(--vp-c-yellow-1); --vp-c-warning-2: var(--vp-c-yellow-2); --vp-c-warning-3: var(--vp-c-yellow-3); @@ -229,6 +249,11 @@ --vp-c-danger-2: var(--vp-c-red-2); --vp-c-danger-3: var(--vp-c-red-3); --vp-c-danger-soft: var(--vp-c-red-soft); + + --vp-c-caution-1: var(--vp-c-red-1); + --vp-c-caution-2: var(--vp-c-red-2); + --vp-c-caution-3: var(--vp-c-red-3); + --vp-c-caution-soft: var(--vp-c-red-soft); } /** @@ -394,11 +419,21 @@ --vp-custom-block-info-bg: var(--vp-c-default-soft); --vp-custom-block-info-code-bg: var(--vp-c-default-soft); + --vp-custom-block-note-border: transparent; + --vp-custom-block-note-text: var(--vp-c-text-1); + --vp-custom-block-note-bg: var(--vp-c-default-soft); + --vp-custom-block-note-code-bg: var(--vp-c-default-soft); + --vp-custom-block-tip-border: transparent; --vp-custom-block-tip-text: var(--vp-c-text-1); --vp-custom-block-tip-bg: var(--vp-c-tip-soft); --vp-custom-block-tip-code-bg: var(--vp-c-tip-soft); + --vp-custom-block-important-border: transparent; + --vp-custom-block-important-text: var(--vp-c-text-1); + --vp-custom-block-important-bg: var(--vp-c-important-soft); + --vp-custom-block-important-code-bg: var(--vp-c-important-soft); + --vp-custom-block-warning-border: transparent; --vp-custom-block-warning-text: var(--vp-c-text-1); --vp-custom-block-warning-bg: var(--vp-c-warning-soft); @@ -409,6 +444,11 @@ --vp-custom-block-danger-bg: var(--vp-c-danger-soft); --vp-custom-block-danger-code-bg: var(--vp-c-danger-soft); + --vp-custom-block-caution-border: transparent; + --vp-custom-block-caution-text: var(--vp-c-text-1); + --vp-custom-block-caution-bg: var(--vp-c-caution-soft); + --vp-custom-block-caution-code-bg: var(--vp-c-caution-soft); + --vp-custom-block-details-border: var(--vp-custom-block-info-border); --vp-custom-block-details-text: var(--vp-custom-block-info-text); --vp-custom-block-details-bg: var(--vp-custom-block-info-bg); diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index 5b6978c5..bf81f38d 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -35,6 +35,7 @@ import { lineNumberPlugin } from './plugins/lineNumbers' import { linkPlugin } from './plugins/link' import { preWrapperPlugin } from './plugins/preWrapper' import { snippetPlugin } from './plugins/snippet' +import { gitHubAlertsPlugin } from './plugins/githubAlerts' export type { Header } from '../shared' @@ -208,6 +209,7 @@ export const createMarkdownRenderer = async ( .use(preWrapperPlugin, { hasSingleTheme }) .use(snippetPlugin, srcDir) .use(containerPlugin, { hasSingleTheme }, options.container) + .use(gitHubAlertsPlugin, options.container) .use(imagePlugin, options.image) .use( linkPlugin, diff --git a/src/node/markdown/plugins/containers.ts b/src/node/markdown/plugins/containers.ts index 2a70e217..5008850b 100644 --- a/src/node/markdown/plugins/containers.ts +++ b/src/node/markdown/plugins/containers.ts @@ -129,8 +129,11 @@ function createCodeGroup(options: Options): ContainerArgs { export interface ContainerOptions { infoLabel?: string + noteLabel?: string tipLabel?: string warningLabel?: string dangerLabel?: string detailsLabel?: string + importantLabel?: string + cautionLabel?: string } diff --git a/src/node/markdown/plugins/githubAlerts.ts b/src/node/markdown/plugins/githubAlerts.ts new file mode 100644 index 00000000..27fbdf07 --- /dev/null +++ b/src/node/markdown/plugins/githubAlerts.ts @@ -0,0 +1,63 @@ +import type MarkdownIt from 'markdown-it' +import type { ContainerOptions } from './containers' + +export const gitHubAlertsPlugin = ( + md: MarkdownIt, + options?: ContainerOptions +) => { + const markers = ['TIP', 'NOTE', 'INFO', 'IMPORTANT', 'WARNING', 'CAUTION', 'DANGER'] + const matchCaseSensitive = true + const titleMark = { + tip: options?.tipLabel || 'TIP', + note: options?.noteLabel || 'NOTE', + info: options?.infoLabel || 'INFO', + important: options?.importantLabel || 'IMPORTANT', + warning: options?.warningLabel || 'WARNING', + caution: options?.cautionLabel || 'CAUTION', + danger: options?.dangerLabel || 'DANGER', + } as Record + + const markerNameRE = markers.join('|') + const RE = new RegExp(`^\\[\\!(${markerNameRE})\\]([^\\n\\r]*)`, matchCaseSensitive ? '' : 'i') + + md.core.ruler.after('block', 'github-alerts', (state) => { + const tokens = state.tokens + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].type === 'blockquote_open') { + const open = tokens[i] + const startIndex = i + while (tokens[i]?.type !== 'blockquote_close' && i <= tokens.length) + i += 1 + const close = tokens[i] + const endIndex = i + const firstContent = tokens.slice(startIndex, endIndex + 1).find(token => token.type === 'inline') + if (!firstContent) + continue + const match = firstContent.content.match(RE) + if (!match) + continue + const type = match[1].toLowerCase() + const title = match[2].trim() || titleMark[type] || capitalize(type) + firstContent.content = firstContent.content.slice(match[0].length).trimStart() + open.type = 'github_alert_open' + open.tag = 'div' + open.meta = { + title, + type, + } + close.type = 'github_alert_close' + close.tag = 'div' + } + } + }) + md.renderer.rules.github_alert_open = function (tokens, idx) { + const { title, type } = tokens[idx].meta + const attrs = '' + return `

${title}

\n` + } +} + + +function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1) +}