2025-08-09 21:16:24 +02:00
|
|
|
(() => {
|
2025-09-04 17:15:09 +02:00
|
|
|
const timeouts = new WeakMap(),
|
|
|
|
|
scrollState = {
|
|
|
|
|
el: null,
|
|
|
|
|
startX: 0,
|
|
|
|
|
scrollLeft: 0,
|
|
|
|
|
pointerId: null,
|
|
|
|
|
moved: false,
|
|
|
|
|
};
|
2025-08-09 21:16:24 +02:00
|
|
|
|
|
|
|
|
marked.use({
|
|
|
|
|
async: false,
|
|
|
|
|
breaks: false,
|
|
|
|
|
gfm: true,
|
|
|
|
|
pedantic: false,
|
|
|
|
|
|
2025-09-04 17:15:09 +02:00
|
|
|
walkTokens: token => {
|
2025-08-23 16:17:01 +02:00
|
|
|
const { type, text } = token;
|
2025-08-09 21:16:24 +02:00
|
|
|
|
2025-08-15 03:38:24 +02:00
|
|
|
if (type === "html") {
|
2025-09-04 17:15:09 +02:00
|
|
|
token.text = token.text.replace(/&/g, "&");
|
|
|
|
|
token.text = token.text.replace(/</g, "<");
|
|
|
|
|
token.text = token.text.replace(/>/g, ">");
|
2025-08-15 03:38:24 +02:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
} else if (type !== "code") {
|
2025-08-09 21:16:24 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 16:17:01 +02:00
|
|
|
const lang = token.lang || "plaintext";
|
|
|
|
|
|
2025-08-09 21:16:24 +02:00
|
|
|
let code;
|
|
|
|
|
|
|
|
|
|
if (lang && hljs.getLanguage(lang)) {
|
2025-08-09 22:58:17 +02:00
|
|
|
code = hljs.highlight(text.trim(), {
|
2025-08-09 21:16:24 +02:00
|
|
|
language: lang,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2025-08-09 22:58:17 +02:00
|
|
|
code = hljs.highlightAuto(text.trim());
|
2025-08-09 21:16:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token.escaped = true;
|
2025-08-09 22:58:17 +02:00
|
|
|
token.lang = code.language || "plaintext";
|
2025-08-09 21:16:24 +02:00
|
|
|
token.text = code.value;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
renderer: {
|
|
|
|
|
code(code) {
|
|
|
|
|
const header = `<div class="pre-header">${escapeHtml(code.lang)}<button class="pre-copy" title="Copy code contents"></button></div>`;
|
|
|
|
|
|
2025-08-31 00:31:43 +02:00
|
|
|
return `<pre class="l-${escapeHtml(code.lang)}">${header}<code>${code.text}</code></pre>`;
|
2025-08-09 21:16:24 +02:00
|
|
|
},
|
2025-08-11 01:38:16 +02:00
|
|
|
|
|
|
|
|
link(link) {
|
|
|
|
|
return `<a href="${link.href}" target="_blank">${escapeHtml(link.text || link.href)}</a>`;
|
|
|
|
|
},
|
2025-08-09 21:16:24 +02:00
|
|
|
},
|
2025-09-04 17:15:09 +02:00
|
|
|
|
|
|
|
|
hooks: {
|
|
|
|
|
postprocess: html => {
|
|
|
|
|
html = html.replace(/<table>/g, `<div class="table-wrapper"><table>`);
|
|
|
|
|
html = html.replace(/<\/ ?table>/g, `</table></div>`);
|
|
|
|
|
|
|
|
|
|
return html;
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-08-09 21:16:24 +02:00
|
|
|
});
|
|
|
|
|
|
2025-09-04 17:15:09 +02:00
|
|
|
addEventListener("click", event => {
|
2025-08-09 21:16:24 +02:00
|
|
|
const button = event.target,
|
|
|
|
|
header = button.closest(".pre-header"),
|
|
|
|
|
pre = header?.closest("pre"),
|
|
|
|
|
code = pre?.querySelector("code");
|
|
|
|
|
|
|
|
|
|
if (!code) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clearTimeout(timeouts.get(pre));
|
|
|
|
|
|
|
|
|
|
navigator.clipboard.writeText(code.textContent.trim());
|
|
|
|
|
|
|
|
|
|
button.classList.add("copied");
|
|
|
|
|
|
|
|
|
|
timeouts.set(
|
|
|
|
|
pre,
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
button.classList.remove("copied");
|
2025-09-04 17:15:09 +02:00
|
|
|
}, 1000)
|
2025-08-09 21:16:24 +02:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-04 17:15:09 +02:00
|
|
|
addEventListener("pointerover", event => {
|
|
|
|
|
if (event.pointerType !== "mouse") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const el = event.target.closest(".table-wrapper");
|
|
|
|
|
|
|
|
|
|
if (!el) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
el.classList.toggle("overflowing", el.scrollWidth - el.clientWidth > 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
addEventListener("pointerdown", event => {
|
|
|
|
|
if (event.button !== 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const el = event.target.closest(".table-wrapper");
|
|
|
|
|
|
|
|
|
|
if (!el) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollState.el = el;
|
|
|
|
|
scrollState.pointerId = event.pointerId;
|
|
|
|
|
scrollState.startX = event.clientX;
|
|
|
|
|
scrollState.scrollLeft = el.scrollLeft;
|
|
|
|
|
scrollState.moved = false;
|
|
|
|
|
|
|
|
|
|
el.classList.add("dragging");
|
|
|
|
|
el.setPointerCapture?.(event.pointerId);
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
addEventListener("pointermove", event => {
|
|
|
|
|
if (!scrollState.el || event.pointerId !== scrollState.pointerId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dx = event.clientX - scrollState.startX;
|
|
|
|
|
|
|
|
|
|
if (Math.abs(dx) > 3) {
|
|
|
|
|
scrollState.moved = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollState.el.scrollLeft = scrollState.scrollLeft - dx;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function endScroll(event) {
|
|
|
|
|
if (!scrollState.el || (event && event.pointerId !== scrollState.pointerId)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollState.el.classList.remove("dragging");
|
|
|
|
|
scrollState.el.releasePointerCapture?.(scrollState.pointerId);
|
|
|
|
|
scrollState.el = null;
|
|
|
|
|
scrollState.pointerId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addEventListener("pointerup", endScroll);
|
|
|
|
|
addEventListener("pointercancel", endScroll);
|
|
|
|
|
|
|
|
|
|
window.render = markdown => {
|
2025-08-09 21:16:24 +02:00
|
|
|
return marked.parse(markdown);
|
|
|
|
|
};
|
|
|
|
|
})();
|