import type MarkdownIt from 'markdown-it' export interface MarkdownItGitHubAlertsOptions { /** * Prefix for the class names. * * @default 'markdown-alert' */ classPrefix?: string /** * Custom icons for each marker. The key is the marker name, and the value is the html script represent the icon. * The key is always lowercase. * * @default inline svg icons from GitHub */ icons?: Record /** * If the alert should be rendered as a blockquote, * this makes sense when content is displayed in a RSS reader, where you don“t have * control about the rendering * @default true */ keepBlockquote?: boolean /** * List of markers to match. * @default ['TIP', 'NOTE', 'IMPORTANT', 'WARNING', 'CAUTION'] */ markers?: string[] | '*' /** * If markers case sensitively on matching. * @default false */ matchCaseSensitive?: boolean /** * The styling for the title. * Other than a css class, this styling will be available as an inline style even in the RSS reader. * @default "display: flex; align-items: center;" */ titleStyle?: string /** * Custom titles for each marker. The key is the marker name, and the value is the title. * The key is always lowercase. * * When the title is not specified, the default title is the capitalized marker name. */ titles?: Record } const DEFAULT_GITHUB_ICONS = { note: ``, tip: ``, important: ``, warning: ``, caution: ``, } const MarkdownItGitHubAlerts: MarkdownIt.PluginWithOptions = (md, options = {}) => { const { markers = ['TIP', 'NOTE', 'IMPORTANT', 'WARNING', 'CAUTION'], icons = DEFAULT_GITHUB_ICONS, keepBlockquote = true, matchCaseSensitive = false, titles = {}, titleStyle = 'display: flex; align-items: center;', classPrefix = 'markdown-alert', } = options const markerNameRE = markers === '*' ? '\\w+' : 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() as keyof typeof icons const title = match[2].trim() || (titles[type] ?? capitalize(type)) const icon = icons[type] ?? '' firstContent.content = firstContent.content.slice(match[0].length).trimStart() open.type = 'alert_open' open.tag = keepBlockquote ? 'blockquote' : 'div' open.meta = { title, type, icon, } close.type = 'alert_close' close.tag = keepBlockquote ? 'blockquote' : 'div' } } }) md.renderer.rules.alert_open = function (tokens, idx) { const { title, type, icon } = tokens[idx].meta return `<${keepBlockquote ? 'blockquote' : 'div'} class="${classPrefix} ${classPrefix}-${type}">

${icon ? `${icon} ` : ''}${title}

` } } function capitalize(str: string) { return str.charAt(0).toUpperCase() + str.slice(1) } export default MarkdownItGitHubAlerts