import { type Renderer, Marked } from "marked"; import { markedHighlight } from "marked-highlight"; import { gfmHeadingId } from "marked-gfm-heading-id"; import * as Prism from "prismjs"; import "prismjs/components/prism-python"; import "prismjs/components/prism-latex"; import "prismjs/components/prism-bash"; import GithubSlugger from "github-slugger"; // import loadLanguages from "prismjs/components/"; // loadLanguages(["python", "latex"]); const LINK_ICON_CODE = ``; const COPY_ICON_CODE = ` `; const CHECK_ICON_CODE = ` `; const COPY_BUTTON_CODE = ``; const escape_test = /[&<>"']/; const escape_replace = new RegExp(escape_test.source, "g"); const escape_test_no_encode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/; const escape_replace_no_encode = new RegExp(escape_test_no_encode.source, "g"); const escape_replacements: Record = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; const get_escape_replacement = (ch: string): string => escape_replacements[ch] || ""; function escape(html: string, encode?: boolean): string { if (encode) { if (escape_test.test(html)) { return html.replace(escape_replace, get_escape_replacement); } } else { if (escape_test_no_encode.test(html)) { return html.replace(escape_replace_no_encode, get_escape_replacement); } } return html; } interface LatexTokenizer { name: string; level: string; start: (src: string) => number | undefined; tokenizer: (src: string, tokens: any) => any; renderer: (token: any) => string; } function createLatexTokenizer( delimiters: { left: string; right: string; display: boolean }[] ): LatexTokenizer { const delimiterPatterns = delimiters.map((delimiter) => ({ start: new RegExp(delimiter.left.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")), end: new RegExp(delimiter.right.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")) })); return { name: "latex", level: "block", start(src: string) { for (const pattern of delimiterPatterns) { const match = src.match(pattern.start); if (match) { return match.index; } } return -1; }, tokenizer(src: string, tokens: any) { for (const pattern of delimiterPatterns) { const match = new RegExp( `${pattern.start.source}([\\s\\S]+?)${pattern.end.source}` ).exec(src); if (match) { return { type: "latex", raw: match[0], text: match[1].trim() }; } } }, renderer(token: any) { return `
${token.text}
`; } }; } const renderer: Partial> = { code( this: Renderer, code: string, infostring: string | undefined, escaped: boolean ) { const lang = (infostring ?? "").match(/\S*/)?.[0] ?? ""; code = code.replace(/\n$/, "") + "\n"; if (!lang) { return ( '
' + COPY_BUTTON_CODE + "
" +
				(escaped ? code : escape(code, true)) +
				"
\n" ); } return ( '
' + COPY_BUTTON_CODE + '
' +
			(escaped ? code : escape(code, true)) +
			"
\n" ); } }; const slugger = new GithubSlugger(); export function create_marked({ header_links, line_breaks, latex_delimiters }: { header_links: boolean; line_breaks: boolean; latex_delimiters: { left: string; right: string; display: boolean }[]; }): typeof marked { const marked = new Marked(); marked.use( { gfm: true, pedantic: false, breaks: line_breaks }, markedHighlight({ highlight: (code: string, lang: string) => { if (Prism.languages[lang]) { return Prism.highlight(code, Prism.languages[lang], lang); } return code; } }), { renderer } ); if (header_links) { marked.use(gfmHeadingId()); marked.use({ extensions: [ { name: "heading", level: "block", renderer(token) { const raw = token.raw .toLowerCase() .trim() .replace(/<[!\/a-z].*?>/gi, ""); const id = "h" + slugger.slug(raw); const level = token.depth; const text = this.parser.parseInline(token.tokens!); return `${LINK_ICON_CODE}${text}\n`; } } ] }); } const latexTokenizer = createLatexTokenizer(latex_delimiters); marked.use({ extensions: [latexTokenizer] }); return marked; } export function copy(node: HTMLDivElement): any { node.addEventListener("click", handle_copy); async function handle_copy(event: MouseEvent): Promise { const path = event.composedPath() as HTMLButtonElement[]; const [copy_button] = path.filter( (e) => e?.tagName === "BUTTON" && e.classList.contains("copy_code_button") ); if (copy_button) { event.stopImmediatePropagation(); const copy_text = copy_button.parentElement!.innerText.trim(); const copy_sucess_button = Array.from( copy_button.children )[1] as HTMLDivElement; const copied = await copy_to_clipboard(copy_text); if (copied) copy_feedback(copy_sucess_button); function copy_feedback(_copy_sucess_button: HTMLDivElement): void { _copy_sucess_button.style.opacity = "1"; setTimeout(() => { _copy_sucess_button.style.opacity = "0"; }, 2000); } } } return { destroy(): void { node.removeEventListener("click", handle_copy); } }; } async function copy_to_clipboard(value: string): Promise { let copied = false; if ("clipboard" in navigator) { await navigator.clipboard.writeText(value); copied = true; } else { const textArea = document.createElement("textarea"); textArea.value = value; textArea.style.position = "absolute"; textArea.style.left = "-999999px"; document.body.prepend(textArea); textArea.select(); try { document.execCommand("copy"); copied = true; } catch (error) { console.error(error); copied = false; } finally { textArea.remove(); } } return copied; }