{
value: VT
desc: string
}
+
+/**
+ * Options for the `markedAlert` extension.
+ */
+export interface AlertOptions {
+ className?: string
+ variants?: AlertVariantItem[]
+ theme?: Theme
+}
+
+/**
+ * Configuration for an alert type.
+ */
+export interface AlertVariantItem {
+ type: string
+ icon: string
+ title?: string
+ titleClassName?: string
+}
+
+/**
+ * Represents an alert token.
+ */
+export interface Alert {
+ type: `alert`
+ meta: {
+ className: string
+ variant: string
+ icon: string
+ title: string
+ titleClassName: string
+ }
+ raw: string
+ text: string
+ tokens: Token[]
+}
diff --git a/src/utils/MDAlert.ts b/src/utils/MDAlert.ts
new file mode 100644
index 0000000..a2942ee
--- /dev/null
+++ b/src/utils/MDAlert.ts
@@ -0,0 +1,159 @@
+import type { AlertOptions, AlertVariantItem } from '@/types'
+import type { MarkedExtension, Tokens } from 'marked'
+import { getStyleString } from '.'
+
+/**
+ * https://github.com/bent10/marked-extensions/tree/main/packages/alert
+ * To support theme, we need to modify the source code.
+ * A [marked](https://marked.js.org/) extension to support [GFM alerts](https://github.com/orgs/community/discussions/16925).
+ */
+export default function markedAlert(options: AlertOptions = {}): MarkedExtension {
+ const { className = `markdown-alert`, variants = [] } = options
+ const resolvedVariants = resolveVariants(variants)
+
+ return {
+ walkTokens(token) {
+ if (token.type !== `blockquote`)
+ return
+
+ const matchedVariant = resolvedVariants.find(({ type }) =>
+ new RegExp(createSyntaxPattern(type), `i`).test(token.text),
+ )
+
+ if (matchedVariant) {
+ const {
+ type: variantType,
+ icon,
+ title = ucfirst(variantType),
+ titleClassName = `${className}-title`,
+ } = matchedVariant
+ const typeRegexp = new RegExp(createSyntaxPattern(variantType))
+
+ Object.assign(token, {
+ type: `alert`,
+ meta: {
+ className,
+ variant: variantType,
+ icon,
+ title,
+ titleClassName,
+ style: {
+ ...options.theme?.block[className],
+ ...options.theme?.block[`${className}-${variantType}`],
+ },
+ titleStyle: {
+ ...options.theme?.block[titleClassName],
+ ...options.theme?.block[`${titleClassName}-${variantType}`],
+ },
+ contentWrapperStyle: {
+ margin: options.theme?.block[`${className}-content-wrapper`]?.margin,
+ },
+ },
+ })
+
+ console.log({
+ ...options.theme?.block[className],
+ ...options.theme?.block[`${className}-${variantType}`],
+ }, `style`)
+
+ const firstLine = token.tokens?.[0] as Tokens.Paragraph
+ const firstLineText = firstLine.raw?.replace(typeRegexp, ``).trim()
+
+ if (firstLineText) {
+ const patternToken = firstLine.tokens[0] as Tokens.Text
+
+ Object.assign(patternToken, {
+ raw: patternToken.raw.replace(typeRegexp, ``),
+ text: patternToken.text.replace(typeRegexp, ``),
+ })
+
+ if (firstLine.tokens[1]?.type === `br`) {
+ firstLine.tokens.splice(1, 1)
+ }
+ }
+ else {
+ token.tokens?.shift()
+ }
+ }
+ },
+ extensions: [
+ {
+ name: `alert`,
+ level: `block`,
+ renderer({ meta, tokens = [] }) {
+ let tmpl = `\n`
+ tmpl += ``
+ tmpl += meta.icon.replace(
+ `
\n`
+
+ return tmpl
+ },
+ },
+ ],
+ }
+}
+
+/**
+ * The default configuration for alert variants.
+ */
+const defaultAlertVariant: AlertVariantItem[] = [
+ {
+ type: `note`,
+ icon: ``,
+ },
+ {
+ type: `tip`,
+ icon: ``,
+ },
+ {
+ type: `important`,
+ icon: ``,
+ },
+ {
+ type: `warning`,
+ icon: ``,
+ },
+ {
+ type: `caution`,
+ icon: ``,
+ },
+]
+
+/**
+ * Resolves the variants configuration, combining the provided variants with
+ * the default variants.
+ */
+export function resolveVariants(variants: AlertVariantItem[]) {
+ if (!variants.length)
+ return defaultAlertVariant
+
+ return Object.values(
+ [...defaultAlertVariant, ...variants].reduce(
+ (map, item) => {
+ map[item.type] = item
+ return map
+ },
+ {} as { [key: string]: AlertVariantItem },
+ ),
+ )
+}
+
+/**
+ * Returns regex pattern to match alert syntax.
+ */
+export function createSyntaxPattern(type: string) {
+ return `^(?:\\[!${type}])\\s*?\n*`
+}
+
+/**
+ * Capitalizes the first letter of a string.
+ */
+export function ucfirst(str: string) {
+ return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase()
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index c787cfa..a18c86c 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,4 +1,4 @@
-import type { Block, Inline, Theme } from '@/types'
+import type { Block, ExtendedProperties, Inline, Theme } from '@/types'
import type { PropertiesHyphen } from 'csstype'
import { prefix } from '@/config'
@@ -116,6 +116,15 @@ export function css2json(css: string): Partial `${key}: ${value}`).join(`; `)
+}
+
/**
* 格式化内容
* @param {string} content - 要格式化的内容
diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts
index b1c18c1..5f6729a 100644
--- a/src/utils/renderer.ts
+++ b/src/utils/renderer.ts
@@ -6,6 +6,8 @@ import hljs from 'highlight.js'
import { marked } from 'marked'
import mermaid from 'mermaid'
+import { getStyleString } from '.'
+import markedAlert from './MDAlert'
import { MDKatex } from './MDKatex'
marked.use(MDKatex({ nonStandard: true }))
@@ -58,9 +60,7 @@ function getStyles(styleMapping: ThemeStyles, tokenName: string, addition: strin
if (!dict) {
return ``
}
- const styles = Object.entries(dict)
- .map(([key, value]) => `${key}:${value}`)
- .join(`;`)
+ const styles = getStyleString(dict)
return `style="${styles}${addition}"`
}
@@ -89,9 +89,9 @@ function transform(legend: string, text: string | null, title: string | null): s
const macCodeSvg = `
`.trim()
@@ -126,6 +126,7 @@ export function initRenderer(opts: IOpts) {
function setOptions(newOpts: Partial): void {
opts = { ...opts, ...newOpts }
styleMapping = buildTheme(opts)
+ marked.use(markedAlert({ theme: opts.theme }))
}
const buildFootnotes = () => {
@@ -211,18 +212,19 @@ export function initRenderer(opts: IOpts) {
return `
${subText}`
},
- link({ href, title, text }: Tokens.Link): string {
+ link({ href, title, text, tokens }: Tokens.Link): string {
+ const parsedText = this.parser.parseInline(tokens)
if (href.startsWith(`https://mp.weixin.qq.com`)) {
- return `${text}`
+ return `${parsedText}`
}
if (href === text) {
- return text
+ return parsedText
}
- if (opts.status) {
+ if (opts.citeStatus) {
const ref = addFootnote(title || text, href)
- return `${text}[${ref}]`
+ return `${parsedText}[${ref}]`
}
- return styledContent(`link`, text, `span`)
+ return styledContent(`link`, parsedText, `span`)
},
strong({ tokens }: Tokens.Strong): string {