UNPKG

markdown-it-info

Version:

The markdown-it plugin that enables easy creation of Qiita and Zenn-style admonition boxes within Markdown documents.

188 lines (153 loc) 5.92 kB
// 定数定義 const MARKER = ':', MIN_MARKER_LEN = 3, VALID_TYPES = new Set([ "info", "warn", "warning", "alert", "question" ]); // Markdown-itプラグインのメイン関数 module.exports = function MarkdownItInfo(md, options = {}) { const defaultOptions = { admonitionStyle: 'default', defaultType: "info", defaultTitle: "Enter the title here" }, config = { ...defaultOptions, ...options }; md.block.ruler.after("fence", "admonition", admonition(config), {}); md.renderer.rules["admonition_open"] = renderAdmonitionOpen(config); md.renderer.rules["admonition_title_open"] = renderAdmonitionTitleOpen(config); md.renderer.rules["admonition_title_close"] = renderToken; md.renderer.rules["admonition_close"] = renderToken; } // レンダリング関数 function renderAdmonitionOpen(config) { return(tokens, index, options, _env, self) => { const token = tokens[index], stylePrefix = `${config.admonitionStyle}-`; token.attrJoin("class", `${stylePrefix}admonition ${token.info}`); return self.renderToken(tokens, index, options); }; } function renderAdmonitionTitleOpen(config) { return (tokens, index, options, _env, self) => { const stylePrefix = `${config.admonitionStyle}-`; tokens[index].attrJoin("class", `${stylePrefix}admonition-title`); return self.renderToken(tokens, index, options); }; } function renderToken(tokens, index, options, _env, self) { return self.renderToken(tokens, index, options); } // ボックスの解析関数 function admonition(config) { return (state, startLine, endLine, silent) => { const startPos = state.bMarks[startLine] + state.tShift[startLine], maxPos = state.eMarks[startLine]; // マーカーの検証 if (state.src.charAt(startPos) !== MARKER) return false; const markerCount = countConsecutiveChars(state.src, startPos, MARKER); if (markerCount < MIN_MARKER_LEN) return false; // パラメータの解析 const params = state.src.slice(startPos + markerCount, maxPos).trim().split(/\s+/); if (params[0] !== "note" && params[0] !== "message") return false; let type, title; if (VALID_TYPES.has(params[1])) { type = params[1]; title = params.slice(2).join(' ') || config.defaultTitle; } else { type = config.defaultType; title = params.slice(1).join(' ') || config.defaultTitle; } // ブロックの終了位置を検索 const endLineInfo = findAdmonitionEnd(state, startLine + 1, endLine); if (!endLineInfo) { if (!silent) { state.pending += state.src.slice(startPos, maxPos); state.line = startLine + 1; } return true; } // バリデーションモードの場合はここで終了 if (silent) return true; // トークンの生成 createAdmonitionTokens(state, startLine, endLineInfo.endLine, type, title, markerCount); state.line = endLineInfo.nextLine; return true; } } // 連続する文字数をカウント function countConsecutiveChars(str, start, char) { let count = 0; while (str.charAt(start + count) === char) count++; return count; } // Admonitionブロックの終了位置を検索 function findAdmonitionEnd(state, startLine, endLine) { let nextLine = startLine, nestLevel = 1; for (; nextLine < endLine; nextLine++) { const lineStart = state.bMarks[nextLine] + state.tShift[nextLine], lineEnd = state.eMarks[nextLine], lineContent = state.src.slice(lineStart, lineEnd).trim(); if (lineContent.startsWith(':::')) { if (lineContent === ':::') { nestLevel--; if (nestLevel === 0) { return { endLine: nextLine, nextLine: nextLine + 1 }; } } else { nestLevel++; } } } // 終了マーカーが見つからない場合 return null; } // Admonitionトークンの生成 function createAdmonitionTokens(state, startLine, endLine, type, title, markerCount) { const markup = MARKER.repeat(markerCount); // ボックスの開始トークン let token = state.push("admonition_open", "div", 1); token.markup = markup; token.block = true; token.info = type; token.map = [startLine, endLine + 1]; // タイトルトークン token = state.push("admonition_title_open", "p", 1); token.markup = `${markup} ${type}`; token.map = [startLine, startLine + 1]; token = state.push("inline", "", 0); token.content = title; token.map = [startLine, startLine + 1]; token.children = []; token = state.push("admonition_title_close", "p", -1); token.markup = `${markup} ${type}`; // 本文の解析 const contentStart = startLine + 1, oldParentType = state.parentType; state.parentType = "admonition"; state.md.block.parse( state.src.slice( state.bMarks[contentStart], state.eMarks[endLine - 1] ), state.md, state.env, state.tokens ); state.parentType = oldParentType; // ボックスの終了トークン token = state.push("admonition_close", "div", -1); token.markup = markup; token.block = true; }