import { createApp, h, shallowRef } from "./vendor/vue/vue.runtime.esm-browser.prod.js";
import MarkdownRender, {
disableD2,
disableInfographic,
disableKatex,
disableMermaid,
setCustomComponents,
} from "./vendor/markstream-vue/index.js";
import {
ALLOW_TRUSTED,
ATTR,
CUSTOM_ID,
EVENTS,
MOUNTED,
STATE,
decodeData,
highlight,
initialState,
readOptions,
reduce,
rendererProps,
sameOriginUrl,
} from "./core.js";
disableMermaid();
disableD2();
disableInfographic();
if (!globalThis.katex) {
disableKatex();
}
const CodeBlock = {
name: "AutumnCodeBlock",
props: { node: { type: Object, required: true }, isDark: Boolean },
setup(props) {
return () => {
const node = props.node ?? {};
const lang = String(node.language ?? "").trim().split(/\s/)[0].toLowerCase();
const code = String(node.code ?? node.raw ?? "");
const children = highlight(code, lang).map((token) =>
token.type === "plain" ? token.text : h("span", { class: "ms-tok-" + token.type }, token.text),
);
return h(
"pre",
{ class: "markstream-code" + (props.isDark ? " is-dark" : ""), "data-language": lang || null },
[h("code", { class: lang ? "language-" + lang : null }, children)],
);
};
},
};
setCustomComponents(CUSTOM_ID, { code_block: CodeBlock });
const SELECTOR = "[" + ATTR.root + "]";
const RENDER = "markstream-render";
const handles = new WeakMap();
function prefersDark() {
return (
typeof window.matchMedia === "function" &&
window.matchMedia("(prefers-color-scheme: dark)").matches
);
}
function trustedAllowed() {
return document.head !== null && document.head.querySelector('meta[name="' + ALLOW_TRUSTED + '"]') !== null;
}
function emit(el, name, detail) {
el.dispatchEvent(new CustomEvent("markstream:" + name, { bubbles: true, detail }));
}
function prepare(el) {
let source = el.querySelector(":scope > pre.markstream-source");
if (source === null) {
source = document.createElement("pre");
source.className = "markstream-source";
el.prepend(source);
}
for (const child of Array.from(el.children)) {
if (child !== source) {
child.remove();
}
}
const target = document.createElement("div");
target.className = RENDER;
target.setAttribute("hx-disable", "");
el.append(target);
return { source, target };
}
function mount(el) {
const existing = handles.get(el);
if (existing) {
return existing;
}
const options = readOptions((name) => el.getAttribute(name), prefersDark(), {
allowTrusted: trustedAllowed(),
});
const { source, target } = prepare(el);
const state = shallowRef(initialState(source.textContent, options.src !== null));
const app = createApp({ render: () => h(MarkdownRender, rendererProps(options, state.value)) });
let events = null;
let active = true;
function close() {
if (events !== null) {
events.close();
events = null;
}
}
function show(next) {
el.setAttribute(STATE, next.status);
el.setAttribute("aria-busy", String(next.status === "streaming"));
}
function apply(type, text) {
if (!active) {
return;
}
const before = state.value;
const next = reduce(before, type, text);
if (next === before) {
return;
}
state.value = next;
source.textContent = next.content;
show(next);
if (next.status !== "streaming") {
close();
emit(el, next.status, { content: next.content });
}
}
const handle = Object.freeze({
append: (text) => apply("chunk", String(text)),
replace: (text) => apply("replace", String(text)),
finish: () => apply("done", null),
fail: () => apply("error", null),
reset(text = "") {
close();
apply("reset", String(text));
},
content: () => state.value.content,
status: () => state.value.status,
unmount() {
if (!active) {
return;
}
active = false;
close();
app.unmount();
handles.delete(el);
el.removeAttribute(MOUNTED);
el.removeAttribute("aria-busy");
},
});
handles.set(el, handle);
el.setAttribute(MOUNTED, "");
show(state.value);
app.mount(target);
emit(el, "mount", { status: state.value.status });
if (options.src !== null) {
const url = sameOriginUrl(options.src, document.baseURI);
try {
if (url === null) {
throw new TypeError("markstream: src must be a same-origin URL");
}
events = new EventSource(url);
for (const type of EVENTS) {
events.addEventListener(type, (e) => apply(type, decodeData(e.data)));
}
} catch {
apply("error", null);
}
}
return handle;
}
function nested(el) {
const parent = el.parentElement;
return parent !== null && parent.closest("[" + MOUNTED + "], ." + RENDER) !== null;
}
function scan(root) {
if (!root || typeof root.querySelectorAll !== "function") {
return;
}
const found = Array.from(root.querySelectorAll(SELECTOR));
if (typeof root.matches === "function" && root.matches(SELECTOR)) {
found.unshift(root);
}
for (const el of found) {
if (!handles.has(el) && el.isConnected && !nested(el)) {
try {
mount(el);
} catch (err) {
console.error("markstream: mount failed", err);
}
}
}
}
function unmountTarget(e) {
const target = e.detail && e.detail.target;
const handle = target ? handles.get(target) : undefined;
if (handle && e.detail.shouldSwap !== false) {
handle.unmount();
}
}
document.addEventListener("htmx:load", (e) => scan(e.detail && e.detail.elt));
document.addEventListener("htmx:beforeSwap", unmountTarget);
document.addEventListener("htmx:oobBeforeSwap", unmountTarget);
document.addEventListener("htmx:beforeCleanupElement", (e) => {
const handle = e.detail && e.detail.elt ? handles.get(e.detail.elt) : undefined;
if (handle) {
handle.unmount();
}
});
window.AutumnMarkstream = Object.freeze({
mount,
scan,
get: (el) => handles.get(el) ?? null,
});
scan(document);
document.dispatchEvent(new CustomEvent("markstream:ready"));