markdown-it-livecodes
Version:
A markdown-it plugin for to convert code blocks to LiveCodes playgrounds.
154 lines (151 loc) • 5.01 kB
JavaScript
import { getPlaygroundUrl } from "livecodes";
//#region ../shared/index.ts
function parseMeta(metaString) {
const meta = {};
metaString.split(" ").forEach((str) => {
const equalIndex = str.indexOf("=");
if (equalIndex > 0) {
const key = str.slice(0, equalIndex);
const value = str.slice(equalIndex + 1);
meta[key] = value;
} else meta[str] = "";
});
return meta;
}
function processOptions({ options, meta, language, content }) {
const render = meta.render ?? options.render ?? "playground";
const height = meta.height ?? options.height;
const className = [...new Set([
options.className,
meta.className,
meta.class
])].filter((x) => x != null).map((x) => x.replace(/['"{}<>\n]/g, "")).join(" ").trim();
const config = options.config;
const configIsObj = config && typeof config === "object";
const configMode = configIsObj ? config.mode : void 0;
const mode = meta.mode ?? options.params?.mode ?? configMode ?? (render === "playground" ? "simple" : void 0);
const lang = language.includes("{") ? language.split("{")[0] : language;
const params = {
...options.params,
...meta,
...mode ? { mode } : {},
[lang]: content
};
const extraKeys = [
"livecodes",
"render",
"height",
"className",
"class",
"lang"
];
extraKeys.forEach((key) => {
delete params[key];
});
const embedOptions = {
appUrl: options.appUrl,
config,
params,
import: meta.import ?? options.import,
loading: meta.loading ?? options.loading,
template: meta.template ?? options.template
};
const optionKeys = Object.keys(embedOptions);
optionKeys.forEach((key) => {
if (embedOptions[key] == null) delete embedOptions[key];
});
return {
embedOptions,
render,
height,
className
};
}
const h = (tag, props = {}, children = []) => {
if (Array.isArray(props) || typeof props === "string") {
children = props;
props = {};
}
const tagName = tag === "link" ? "a" : tag === "image" ? "img" : tag === "text" ? "span" : tag === "element" && props.data?.hName === "iframe" ? "iframe" : "div";
const attrs = {};
if (props.url) {
if (tag === "link") attrs.href = props.url;
else if (tag === "image") attrs.src = props.url;
}
for (const [key, value] of Object.entries(props.data?.hProperties || {})) attrs[key] = value;
if (tag === "text" && Object.keys(attrs).length === 0 && typeof children === "string") return children;
return `<${tagName} ${Object.entries(attrs).map(([k, v]) => `${k}="${v ?? ""}"`).join(" ")}>${typeof children === "string" ? children : children.join("")}</${tagName}>`;
};
function renderLink({ url, render, className = "", u = h }) {
const linkContent = render === "link" ? u("text", "Edit in LiveCodes") : u("image", {
url: `https://livecodes.io/livecodes/assets/images/edit-in-livecodes-button${className.toLowerCase().includes("dark") ? "-dark" : ""}.svg`,
data: { hProperties: {
alt: "Edit in LiveCodes",
style: "height: 28px;"
} }
});
return u("link", {
url,
data: { hProperties: {
target: "_blank",
rel: "noopener noreferrer",
class: className
} }
}, [linkContent]);
}
function renderIframe({ url, height, className, loading = "lazy", data, u = h }) {
return u("element", { data: {
...data,
hName: "iframe",
hProperties: {
...data?.hProperties,
scrolling: "no",
loading: loading === "eager" ? "eager" : "lazy",
style: `height: ${height ?? "300px"}; width: 100%; border:1px solid black; border-radius:6px;`,
class: className,
src: url,
sandbox: "allow-same-origin allow-downloads allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-presentation allow-scripts"
}
} });
}
//#endregion
//#region src/index.ts
function markDownItLiveCodes(md, options = {}) {
const { unescapeAll } = md.utils;
const defaultRenderer = md.renderer.rules.fence ?? ((tokens, idx, options$1, _env, slf) => slf.renderToken(tokens, idx, options$1));
md.renderer.rules.fence = (tokens, idx, mdOptions, env, slf) => {
const token = tokens[idx];
const info = token.info ? unescapeAll(token.info).trim() : "";
const content = (token.content || "").trim();
const renderDefault = () => defaultRenderer(tokens, idx, mdOptions, env, slf);
const [lang, ...restInfo] = info.split(/(\s+)/g);
const meta = parseMeta(restInfo.join("") || "");
if (meta.livecodes == null && !options.auto || meta.livecodes === "false") return renderDefault();
const language = meta.lang ?? lang;
const { embedOptions, render, height, className } = processOptions({
options,
meta,
language,
content
});
const url = getPlaygroundUrl(embedOptions);
if (render === "link" || render === "button") return renderDefault() + renderLink({
url,
render,
className
});
if (render === "meta") {
env.livecodesUrl = url;
token.attrSet("data-livecodes-url", url);
return renderDefault();
}
return renderIframe({
url,
height,
className,
loading: options.loading
});
};
}
//#endregion
export { markDownItLiveCodes as default };