import { parse } from 'yaml'; import TokenClass from 'markdown-it/lib/token.mjs'; const NUMBER_CHAR_RE = /\d/; const STR_SPLITTERS = ["-", "_", "/", "."]; function isUppercase(char = "") { if (NUMBER_CHAR_RE.test(char)) { return void 0; } return char !== char.toLowerCase(); } function splitByCase(str, separators) { const splitters = STR_SPLITTERS; const parts = []; if (!str || typeof str !== "string") { return parts; } let buff = ""; let previousUpper; let previousSplitter; for (const char of str) { const isSplitter = splitters.includes(char); if (isSplitter === true) { parts.push(buff); buff = ""; previousUpper = void 0; continue; } const isUpper = isUppercase(char); if (previousSplitter === false) { if (previousUpper === false && isUpper === true) { parts.push(buff); buff = char; previousUpper = isUpper; continue; } if (previousUpper === true && isUpper === false && buff.length > 1) { const lastChar = buff.at(-1); parts.push(buff.slice(0, Math.max(0, buff.length - 1))); buff = lastChar + char; previousUpper = isUpper; continue; } } buff += char; previousUpper = isUpper; previousSplitter = isSplitter; } parts.push(buff); return parts; } function kebabCase(str, joiner) { return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join("-") : ""; } function parseBracketContent(str, startIndex) { if (str[startIndex] !== "[") return null; let index = startIndex + 1; while (index < str.length) { if (str[index] === "\\" && index + 1 < str.length) { index += 2; continue; } if (str[index] === "]") { const content = str.slice(startIndex + 1, index); return { content, endIndex: index + 1 }; } index += 1; } return null; } const bracketPairs = { "[": "]", "{": "}", "(": ")" }; const quotePairs = { "'": "'", '"': '"', "`": "`" }; function searchProps(content, index = 0) { if (content[index] !== "{") throw new Error(`Invalid props, expected \`{\` but got '${content[index]}'`); const props = []; if (content[index + 1] === "{") return void 0; index += 1; while (index < content.length) { if (content[index] === "\\") { index += 2; } else if (content[index] === "}") { index += 1; break; } else if (content[index] === " ") { index += 1; } else if (content[index] === ".") { index += 1; props.push([ "class", searchUntil(" #.}") ]); } else if (content[index] === "#") { index += 1; props.push([ "id", searchUntil(" #.}") ]); } else { const start = index; while (index < content.length) { index += 1; if (" }=".includes(content[index])) break; } const char = content[index]; if (start !== index) { const key = content.slice(start, index).trim(); if (char === "=") { index += 1; props.push([ key, searchValue() ]); } else { props.push([ key, "true" ]); } } } } function searchUntil(str) { const start = index; while (index < content.length) { index += 1; if (content[index] === "\\") index += 2; if (str.includes(content[index])) break; } return content.slice(start, index); } function searchValue() { const start = index; if (content[index] in bracketPairs) { searchBracket(bracketPairs[content[index]]); index += 1; return content.slice(start, index); } else if (content[index] in quotePairs) { searchString(quotePairs[content[index]]); index += 1; return content.slice(start, index); } else { return searchUntil(" }"); } } function searchBracket(end) { while (index < content.length) { index++; if (content[index] in quotePairs) searchString(quotePairs[content[index]]); else if (content[index] in bracketPairs) searchBracket(bracketPairs[content[index]]); else if (content[index] === end) return; } } function searchString(end) { return searchUntil(end); } props.forEach((v) => { if (v[1].match(/^(['"`]).*\1$/)) v[1] = v[1].slice(1, -1); }); return { props, index }; } const RE_BLOCK_NAME = /^[a-z$][$\w.-]*/i; function parseBlockParams(str) { str = str.trim(); if (!str) return { name: "" }; const name = str.match(RE_BLOCK_NAME)?.[0]; if (!name) throw new Error(`Invalid block params: ${str}`); let remaining = str.slice(name.length).trim(); let content; let props; let unparsedRemaining; if (remaining.startsWith("[")) { const result2 = parseBracketContent(remaining, 0); if (result2) { content = result2.content; remaining = remaining.slice(result2.endIndex).trim(); } } if (remaining.startsWith("{")) { const propsResult = searchProps(remaining, 0); if (propsResult) { props = propsResult.props; const afterProps = remaining.slice(propsResult.index).trim(); if (afterProps) { unparsedRemaining = afterProps; } } } else if (remaining) { unparsedRemaining = remaining; } const result = { name: kebabCase(name) }; if (content !== void 0) { result.content = content; } if (props !== void 0) { result.props = props; } if (unparsedRemaining) { result.remaining = unparsedRemaining; } return result; } const MarkdownItMdcBlock = (md) => { const min_markers = 2; const marker_str = ":"; const marker_char = marker_str.charCodeAt(0); md.block.ruler.before( "fence", "mdc_block_shorthand", // eslint-disable-next-line prefer-arrow-callback function mdc_block_shorthand(state, startLine, endLine, silent) { const line = state.src.slice(state.bMarks[startLine] + state.tShift[startLine], state.eMarks[startLine]); if (!line.match(/^:\w/)) return false; const parsed = parseBlockParams(line.slice(1)); const { name, content, props, remaining } = parsed; if (remaining) { return false; } state.lineMax = startLine + 1; if (!silent) { if (content !== void 0) { const tokenOpen = state.push("mdc_block_shorthand", name, 1); props?.forEach(([key, value]) => { if (key === "class") tokenOpen.attrJoin(key, value); else tokenOpen.attrSet(key, value); }); const inline = state.push("inline", "", 0); inline.content = ""; const text = new state.Token("text", "", 0); text.content = content; inline.children = [text]; state.push("mdc_block_shorthand", name, -1); } else { const token = state.push("mdc_block_shorthand", name, 0); props?.forEach(([key, value]) => { if (key === "class") token.attrJoin(key, value); else token.attrSet(key, value); }); } } state.line = startLine + 1; return true; } ); md.block.ruler.before( "fence", "mdc_block", // eslint-disable-next-line prefer-arrow-callback function mdc_block(state, startLine, endLine, silent) { let pos; let nextLine; let auto_closed = false; let start = state.bMarks[startLine] + state.tShift[startLine]; let max = state.eMarks[startLine]; const indent = state.sCount[startLine]; let inCodeFence = false; let codeFenceCharCode = 0; let codeFenceCount = 0; let nestingDepth = 0; if (state.src[start] !== ":") return false; for (pos = start + 1; pos <= max; pos++) { if (marker_str !== state.src[pos]) break; } const marker_count = Math.floor(pos - start); if (marker_count < min_markers) return false; const markup = state.src.slice(start, pos); const params = parseBlockParams(state.src.slice(pos, max)); if (!params.name) return false; if (silent) return true; nextLine = startLine; for (; ; ) { nextLine++; if (nextLine >= endLine) { break; } start = state.bMarks[nextLine] + state.tShift[nextLine]; max = state.eMarks[nextLine]; if (start < max && state.sCount[nextLine] < state.blkIndent) { break; } const lineCharCode = state.src.charCodeAt(start); if (inCodeFence) { if (lineCharCode === codeFenceCharCode) { let fencePos = start + 1; while (fencePos < max && state.src.charCodeAt(fencePos) === codeFenceCharCode) fencePos++; if (fencePos - start >= codeFenceCount) { const afterFence = state.skipSpaces(fencePos); if (afterFence >= max) inCodeFence = false; } } continue; } if (lineCharCode === 96 || lineCharCode === 126) { let fencePos = start + 1; while (fencePos < max && state.src.charCodeAt(fencePos) === lineCharCode) fencePos++; if (fencePos - start >= 3) { inCodeFence = true; codeFenceCharCode = lineCharCode; codeFenceCount = fencePos - start; continue; } } if (marker_char !== lineCharCode) continue; for (pos = start + 1; pos <= max; pos++) { if (marker_str !== state.src[pos]) break; } if (pos - start !== marker_count) continue; pos = state.skipSpaces(pos); if (pos < max) { nestingDepth++; continue; } if (nestingDepth > 0) { nestingDepth--; continue; } auto_closed = true; break; } const old_parent = state.parentType; const old_line_max = state.lineMax; state.parentType = "mdc_block"; state.lineMax = nextLine; const tokenOpen = state.push("mdc_block_open", params.name, 1); tokenOpen.markup = markup; tokenOpen.block = true; tokenOpen.info = params.name; tokenOpen.map = [startLine, nextLine]; if (params.props) { params.props?.forEach(([key, value]) => { if (key === "class") tokenOpen.attrJoin(key, value); else tokenOpen.attrSet(key, value); }); } const blkIndent = state.blkIndent; state.blkIndent = indent; state.env.mdcBlockTokens ||= []; state.env.mdcBlockTokens.unshift(tokenOpen); state.md.block.tokenize(state, startLine + 1, nextLine); state.blkIndent = blkIndent; state.env.mdcBlockTokens.shift(tokenOpen); const tokenClose = state.push("mdc_block_close", params.name, -1); tokenClose.map = [startLine, nextLine]; tokenClose.markup = state.src.slice(start, pos); tokenClose.block = true; state.tokens.slice( state.tokens.indexOf(tokenOpen) + 1, state.tokens.indexOf(tokenClose) ).filter((i) => i.level === tokenOpen.level + 1).forEach((i, _, arr) => { if (arr.length <= 2 && i.tag === "p") i.hidden = true; }); state.parentType = old_parent; state.lineMax = old_line_max; state.line = nextLine + (auto_closed ? 1 : 0); return true; }, { alt: ["paragraph", "reference", "blockquote", "list"] } ); md.block.ruler.after( "code", "mdc_block_yaml", // eslint-disable-next-line prefer-arrow-callback function mdc_block_yaml(state, startLine, endLine, silent) { if (!state.env.mdcBlockTokens?.length) return false; const start = state.bMarks[startLine] + state.tShift[startLine]; const end = state.eMarks[startLine]; if (state.src.slice(start, end) !== "---") return false; let lineEnd = startLine + 1; let found = false; while (lineEnd < endLine) { const line = state.src.slice(state.bMarks[lineEnd] + state.tShift[startLine], state.eMarks[lineEnd]); if (line === "---") { found = true; break; } lineEnd += 1; } if (!found) return false; if (!silent) { const yaml = state.src.slice(state.bMarks[startLine + 1], state.eMarks[lineEnd - 1]); const data = parse(yaml); const token = state.env.mdcBlockTokens[0]; Object.entries(data || {}).forEach(([key, value]) => { if (key === "class") token.attrJoin(key, value); else token.attrSet(key, typeof value === "string" ? value : JSON.stringify(value)); }); } state.line = lineEnd + 1; state.lineMax = lineEnd + 1; return true; } ); md.block.ruler.after( "code", "mdc_block_slots", // eslint-disable-next-line prefer-arrow-callback function mdc_block(state, startLine, endLine, silent) { if (!state.env.mdcBlockTokens?.length) return false; const start = state.bMarks[startLine] + state.tShift[startLine]; if (!(state.src[start] === "#" && state.src[start + 1] !== " ")) return false; const line = state.src.slice(start, state.eMarks[startLine]); const { name, props } = parseBlockParams(line.slice(1)); let lineEnd = startLine + 1; while (lineEnd < endLine) { const line2 = state.src.slice(state.bMarks[lineEnd] + state.tShift[startLine], state.eMarks[lineEnd]); if (line2.match(/^#(\w)+/) || line2.startsWith("::")) break; lineEnd += 1; } if (silent) { state.line = lineEnd; state.lineMax = lineEnd; return true; } state.lineMax = startLine + 1; const slot = state.push("mdc_block_slot", "template", 1); slot.attrSet(`#${name}`, ""); props?.forEach(([key, value]) => { if (key === "class") slot.attrJoin(key, value); else slot.attrSet(key, value); }); state.line = startLine + 1; state.lineMax = lineEnd; state.md.block.tokenize(state, startLine + 1, lineEnd); state.push("mdc_block_slot", "template", -1); state.line = lineEnd; state.lineMax = lineEnd; return true; } ); }; const ALLOWED_PREV_CHARS = /* @__PURE__ */ new Set([" ", " ", "\n", "*", "_", "["]); const MarkdownItInlineComponent = (md) => { md.inline.ruler.after("entity", "mdc_inline_component", (state, silent) => { const start = state.pos; const char = state.src[start]; if (char !== ":") return false; const prevChar = state.src[start - 1]; if (start > 0 && !ALLOWED_PREV_CHARS.has(prevChar)) return false; let index = start + 1; let nameEnd = -1; let content; while (index < state.src.length) { const char2 = state.src[index]; if (char2 === "[") { nameEnd = index; const result = parseBracketContent(state.src, index); if (result) { content = result.content; index = result.endIndex; } break; } if (!/[\w$\-]/.test(char2)) break; index += 1; } if (nameEnd === -1) nameEnd = index; if (nameEnd <= start + 1) return false; state.pos = index; if (silent) return true; const name = state.src.slice(start + 1, nameEnd); if (content !== void 0) { state.push("mdc_inline_component", name, 1); const text = state.push("text", "", 0); text.content = content; state.push("mdc_inline_component", name, -1); } else { state.push("mdc_inline_component", name, 0); } return true; }); }; const MarkdownItInlineProps = (md) => { md.inline.ruler.after("entity", "mdc_inline_props", (state, silent) => { const start = state.pos; const char = state.src[start]; if (char !== "{") return false; if (state.src[start + 1] === "{" || state.src[start - 1] === "{" || state.src[start - 1] === "$") return false; const search = searchProps(state.src, start); if (!search) return false; const { props, index: end } = search; if (end === start) return false; state.pos = end; if (silent) return true; const token = state.push("mdc_inline_props", "span", 0); token.attrs = props; token.hidden = true; return true; }); md.renderer.rules.mdc_inline_props = () => { return ""; }; const _parse = md.parse; md.parse = function(src, env) { const tokens = _parse.call(this, src, env); tokens.forEach((token, index) => { const prev = tokens[index - 1]; const next = tokens[index + 1]; if (!["heading_open", "paragraph_open", "list_item_open"].includes(prev?.type) || prev.hidden) return; if (token.hidden && next.type === "inline") token = next; if (token.type === "inline" && token.children?.length === 2 && token.children[0].type === "text" && token.children[1].type === "mdc_inline_props") { const props = token.children[1].attrs; token.children.splice(1, 1); props?.forEach(([key, value]) => { if (key === "class") prev.attrJoin("class", value); else prev.attrSet(key, value); }); } }); tokens.forEach((tokenOpen, index) => { if (tokenOpen.type !== "bullet_list_open") return; const prev = tokens[index - 1]; if (!prev || prev.type !== "mdc_block_open" || prev.tag !== "ul") return; let closeIndex = index + 1; while (closeIndex < tokens.length) { const close = tokens[closeIndex]; if (close.type === "bullet_list_close" && close.level === tokenOpen.level) break; closeIndex += 1; } const tokenClose = tokens[closeIndex]; if (tokenClose.type !== "bullet_list_close") return; const next = tokens[closeIndex + 1]; if (next.type === "mdc_block_close" && next.tag === "ul") { tokenOpen.hidden = true; tokenClose.hidden = true; } }); return tokens; }; const _renderInline = md.renderer.renderInline; md.renderer.renderInline = function(tokens, options, env) { tokens = [...tokens]; tokens.forEach((token, index) => { if (token.type === "mdc_inline_props") { let prevIndex = index - 1; let prev = tokens[prevIndex]; while (prevIndex >= 0) { if (prev.type === "text" && !prev.content.trim()) { prevIndex--; prev = tokens[prevIndex]; } else { break; } } if (!prev.tag && prev.type === "text") { prev = new TokenClass("mdc_inline_span", "span", 1); tokens.splice(index - 1, 0, prev); const close = new TokenClass("mdc_inline_span", "span", -1); tokens.splice(index + 2, 0, close); } else if (prev.nesting === -1) { let searchIndex = index - 1; while (searchIndex >= 0) { const searchToken = tokens[searchIndex]; if (searchToken.nesting === 1 && searchToken.tag === prev.tag && searchToken.level === prev.level) { prev = searchToken; break; } searchIndex--; } } if (prev.nesting === -1) throw new Error(`No matching opening tag found for ${JSON.stringify(prev)}`); token.attrs?.forEach(([key, value]) => { if (key === "class") prev.attrJoin("class", value); else prev.attrSet(key, value); }); } }); return _renderInline.call(this, tokens, options, env); }; }; const MarkdownItInlineSpan = (md) => { md.inline.ruler.before("link", "mdc_inline_span", (state, silent) => { const start = state.pos; const char = state.src[start]; if (char !== "[") return false; let index = start + 1; let depth = 0; while (index < state.src.length) { if (state.src[index] === "\\") { index += 2; continue; } if (state.src[index] === "[") { depth++; } else if (state.src[index] === "]") { if (depth === 0) break; depth--; } index += 1; } if (index === start) return false; const nextChar = state.src[index + 1]; if (nextChar === "(" || nextChar === "[") return false; if (silent) return true; state.push("mdc_inline_span", "span", 1); const oldPos = state.pos; const oldPosMax = state.posMax; state.pos = start + 1; state.posMax = index; state.md.inline.tokenize(state); state.pos = oldPos; state.posMax = oldPosMax; state.push("mdc_inline_span", "span", -1); state.pos = index + 1; return true; }); }; const MarkdownItMdc = (md, options = {}) => { const { blockComponent = true, inlineProps = true, inlineSpan = true, inlineComponent = true } = options.syntax || {}; if (blockComponent) md.use(MarkdownItMdcBlock); if (inlineProps) md.use(MarkdownItInlineProps); if (inlineSpan) md.use(MarkdownItInlineSpan); if (inlineComponent) md.use(MarkdownItInlineComponent); }; export { MarkdownItMdc as default };