@markslides/renderer
Version:
484 lines (466 loc) • 17.3 kB
JavaScript
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: "",
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
});
;