UNPKG

@markslides/renderer

Version:
484 lines (466 loc) 17.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { appMarp: () => appMarp_default, slideConfigConst: () => slideConfigConst_default, useDefaultMarpRender: () => useDefaultMarpRender_default, useIndependentMarpRender: () => useIndependentMarpRender_default }); module.exports = __toCommonJS(src_exports); // src/lib/marp/appMarp.ts var import_marp_core = require("@marp-team/marp-core"); var import_marpit = require("@marp-team/marpit"); var import_markdown_it_container = __toESM(require("markdown-it-container")); var import_markdown_it_link = __toESM(require("@markslides/markdown-it-link")); var import_markdown_it_mermaid = __toESM(require("@markslides/markdown-it-mermaid")); var import_markdown_it_typograms = __toESM(require("@markslides/markdown-it-typograms")); var import_themes = __toESM(require("@markslides/themes")); // src/lib/marp/plugins/taskLists.ts var markdownItTaskLists = (md) => { const original = md.renderer.rules.text || function(tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options); }; md.renderer.rules.text = (tokens, idx, options, env, self) => { const token = tokens[idx]; if (!token) { return original(tokens, idx, options, env, self); } const content = token.content.trim(); if (content.startsWith("[ ] ")) { return ` <input type="checkbox" style="width: 20px; height: 20px;"> ${content.split("[ ]")[1]} </input> `; } else if (content.startsWith("[x] ")) { return ` <input type="checkbox" checked style="width: 20px; height: 20px;"> ${content.split("[x]")[1]} </input> `; } return original(tokens, idx, options, env, self); }; }; var taskLists_default = markdownItTaskLists; // src/lib/marp/plugins/copyFenceContent.ts var markdownItCopyFenceContent = (md) => { const original = md.renderer.rules.fence || function(tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options); }; md.renderer.rules.fence = (tokens, idx, options, env, self) => { const token = tokens[idx]; if (!token) { return original(tokens, idx, options, env, self); } const content = token.content.trim(); const escapedContent = md.utils.escapeHtml(content); const originalFenceContent = original(tokens, idx, options, env, self); const styles = ` <style> .copy-fence-container { position: relative; } button.copy-fence-content { width: 36px; height: 36px; position: absolute; top: 12px; right: 12px; display: flex; align-items: center; justify-content: center; cursor: pointer; background-color: var(--color-neutral-muted); color: var(--color-fg-default); border: 1px solid var(--color-border-default); border-radius: 4px; transition: all 0.2s ease-in-out; } button.copy-fence-content:hover { background-color: var(--color-fg-muted); color: var(--color-canvas-default); } /* Inactive */ button.copy-fence-content .lucide-copy-icon { display: block !important; } button.copy-fence-content .lucide-check-icon { display: none !important; } /* Active */ button.copy-fence-content:hover.active { color: var(--color-canvas-default); } button.copy-fence-content.active .lucide-copy-icon { display: none !important; } button.copy-fence-content.active .lucide-check-icon { display: block !important; } </style> `; const buttonHtml = ` <button class="copy-fence-content" data-content="${escapedContent}"> <svg class="lucide-copy-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect width="14" height="14" x="8" y="8" rx="2" ry="2" /> <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /> </svg> <svg class="lucide-check-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20 6 9 17l-5-5" /> </svg> </button> `; return ` <div class="copy-fence-container"> ${styles} ${buttonHtml} </div> ${originalFenceContent} `; }; }; var copyFenceContent_default = markdownItCopyFenceContent; // src/lib/marp/plugins/fenceCodeBlockEnhancer.ts var SHOW_LINE_NUMBERS = "showLineNumbers"; var regExpLineNumbers = /{([\d,-]*)}/; var generateFenceStyles = (digits) => ` <style> pre, pre[class*="language-"] { padding: 0.75rem 1rem !important; white-space: pre-wrap; word-break: break-word; line-height: 1rem; overflow: auto; } .line-number { margin-right: ${digits * 1.4}rem; color: var(--color-fg-default); } .highlighted-line { background-color: var(--color-neutral-muted); padding: 0.1rem 12px; margin-left: -16px; margin-right: -16px; border-left: 4px solid var(--color-accent-fg); } </style> `; var markdownItFenceCodeBlockEnhancer = (md) => { const original = md.renderer.rules.fence || function(tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options); }; md.renderer.rules.fence = (tokens, idx, options, env, self) => { const token = tokens[idx]; if (!token || !token.info) { return original(tokens, idx, options, env, self); } const tokenParts = token.info.split(" ").filter(Boolean); const langName = tokenParts[0]; if (!langName) { return original(tokens, idx, options, env, self); } const isShowLineNumber = tokenParts.includes(SHOW_LINE_NUMBERS); let lineNumbers = []; const match = regExpLineNumbers.exec(token.info); if (match && match[1]) { lineNumbers = match[1].split(",").map((v) => v.split("-").map((v2) => parseInt(v2, 10))).filter((range) => range.every((num) => !isNaN(num))); } const content = token.content; const code = options.highlight ? options.highlight(content, langName, "").trim() : content.trim(); const totalLines = code.split("\n").length; const digits = Math.max(1, Math.floor(Math.log10(totalLines)) + 1); const lines = code.split("\n").map((line, index) => { const lineNumber = index + 1; const isInHighlightRange = lineNumbers.some(([start, end]) => { if (start && end) { return lineNumber >= start && lineNumber <= end && start <= totalLines && end <= totalLines; } return lineNumber === start && start <= totalLines; }); const lineNumberStr = isShowLineNumber ? lineNumber.toString().padStart(digits, " ") : ""; const lineContent = `${isShowLineNumber ? `<span class="line-number">${lineNumberStr}</span>` : ""}${line}`; return { content: isInHighlightRange ? `<div class="highlighted-line">${lineContent}</div>` : lineContent, isHighlighted: isInHighlightRange }; }); const highlightedCode = lines.map( (line) => line.isHighlighted ? line.content : `${line.content} ` ).join(""); token.attrSet("class", langName ? `language-${langName}` : ""); const attrs = self.renderAttrs(token); const styles = generateFenceStyles(digits); return `${styles}<pre${attrs}><code${attrs}>${highlightedCode.trim()}</code></pre>`; }; }; var fenceCodeBlockEnhancer_default = markdownItFenceCodeBlockEnhancer; // src/lib/marp/appMarp.ts var appMarp = /* @__PURE__ */ function() { let instance; function createInstance(containerClassName) { const marp = new import_marp_core.Marp({ container: [ new import_marpit.Element("div", { class: containerClassName ?? "marpit" }) ], // slideContainer: new MarpitElement('div', { // class: 'slide', // }), inlineSVG: true, html: true, markdown: { html: true, breaks: true } }); marp.use(import_markdown_it_container.default, "columns-2", {}); marp.use(import_markdown_it_container.default, "columns-3", {}); marp.use(import_markdown_it_container.default, "columns-4", {}); marp.use(import_markdown_it_container.default, "columns-5", {}); marp.use(import_markdown_it_container.default, "columns-6", {}); marp.use(import_markdown_it_link.default); marp.use(taskLists_default); marp.use(fenceCodeBlockEnhancer_default); marp.use(import_markdown_it_mermaid.default); marp.use(import_markdown_it_typograms.default); marp.use(copyFenceContent_default); if (import_themes.default.length > 0) { marp.themeSet.default = marp.themeSet.add(import_themes.default[0].css); import_themes.default.forEach((theme) => { marp.themeSet.add(theme.css); }); } return marp; } return { createInstance, getDefaultInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; }(); var appMarp_default = appMarp; // src/hooks/useDefaultMarpRender.ts var import_react2 = require("react"); // src/hooks/useRefreshCopyFenceContent.ts var import_react = require("react"); function useRefreshCopyFenceContent() { return (0, import_react.useCallback)(() => { const copyFenceContentButtonElems = Array.from( document.querySelectorAll("button.copy-fence-content") ); const handleClickCopyCodeButton = async (event) => { event.stopPropagation(); event.preventDefault(); const elem = event.currentTarget; const content = elem.dataset.content; if (!content) { return; } try { await navigator.clipboard.writeText(content); elem.classList.add("active"); const timeoutId = setTimeout(() => { elem.classList.remove("active"); }, 2e3); return () => { clearTimeout(timeoutId); }; } catch (err) { console.error("Failed to copy:", err); } }; copyFenceContentButtonElems.forEach((elem) => { elem.addEventListener("click", handleClickCopyCodeButton); }); return () => { copyFenceContentButtonElems.forEach((elem) => { elem.removeEventListener("click", handleClickCopyCodeButton); }); }; }, []); } var useRefreshCopyFenceContent_default = useRefreshCopyFenceContent; // src/lib/utils/slideConfigUtil.ts var slideConfigUtil = { generateMarpConfigFromSlideConfigState: (configState) => { return ` marp: true header: ${configState.header} footer: ${configState.footer} paginate: ${configState.paginate} class: ${configState.class} theme: ${configState.theme} size: ${configState.size} style: | pre { overflow: auto; } `.trim(); }, generateSlideConfigStateFromMarpConfig: (marpConfig) => { let slideConfigState = { header: "", footer: "![height:40px](https://www.markslides.ai/image/credit.png)", paginate: false, theme: "default", class: "normal", size: "16:9" }; marpConfig.split("\n").forEach((part) => { const separatorIndex = part.indexOf(":"); const key = part.substring(0, separatorIndex); const value = part.substring(separatorIndex + 1).trim(); if (slideConfigState[key] !== void 0) { slideConfigState[key] = value.trim(); } }); return slideConfigState; } }; var slideConfigUtil_default = slideConfigUtil; // src/hooks/useDefaultMarpRender.ts function useDefaultMarpRender(slideConfig, content) { const { html, css, comments } = (() => { if (content) { try { const config = typeof slideConfig === "string" ? slideConfig : slideConfigUtil_default.generateMarpConfigFromSlideConfigState( slideConfig ); return appMarp_default.getDefaultInstance().render(`--- ${config} --- ${content}`); } catch (error) { console.error(error); } } return { html: null, css: null, comments: null }; })(); const refreshCopyFenceContent = useRefreshCopyFenceContent_default(); const refresh = (0, import_react2.useCallback)(() => { refreshCopyFenceContent(); }, []); (0, import_react2.useEffect)(() => { refresh(); }); return { html, css, comments, refresh }; } var useDefaultMarpRender_default = useDefaultMarpRender; // src/hooks/useIndependentMarpRender.ts var import_react3 = require("react"); function useIndependentMarpRender(containerClassName, slideConfig, content) { const containerClassNameRef = (0, import_react3.useRef)(null); const marpInstanceRef = (0, import_react3.useRef)(null); const { html, css, comments } = (() => { if (content) { try { const config = typeof slideConfig === "string" ? slideConfig : slideConfigUtil_default.generateMarpConfigFromSlideConfigState( slideConfig ); if (containerClassNameRef.current === null || containerClassNameRef.current !== containerClassName || !marpInstanceRef.current) { containerClassNameRef.current = containerClassName; marpInstanceRef.current = appMarp_default.createInstance(containerClassName); } return marpInstanceRef.current.render( `--- ${config} --- ${content}` ); } catch (error) { console.error(error); } } return { html: null, css: null, comments: null }; })(); const refreshCopyFenceContent = useRefreshCopyFenceContent_default(); const refresh = (0, import_react3.useCallback)(() => { refreshCopyFenceContent(); }, []); (0, import_react3.useEffect)(() => { refresh(); }); return { html, css, comments, refresh }; } var useIndependentMarpRender_default = useIndependentMarpRender; // src/lib/constants/slideConfigConst.ts var import_themes2 = __toESM(require("@markslides/themes")); var slideConfigConst = { // themes: ['default', 'gaia', 'uncover'] as const, // themes: ['default', ...themes.map((theme) => theme.name)] as const, themes: [...import_themes2.default.map((theme) => theme.name)], // classes: ['normal', 'invert', 'lead'] as const, classes: [ { label: "light", value: "normal" }, { label: "dark", value: "invert" } ], sizes: ["4:3", "16:9"] }; var slideConfigConst_default = slideConfigConst; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { appMarp, slideConfigConst, useDefaultMarpRender, useIndependentMarpRender });