mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-13 22:28:56 +02:00
refactor: migrate the frontend from AngularJS to Vue 3
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { h, ref, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
|
||||
import HtmlDoc from "./html-doc.js";
|
||||
import PdfViewer from "./pdf-viewer.js";
|
||||
|
||||
const Markdown = {
|
||||
props: ["content", "terms", "options"],
|
||||
setup(props) {
|
||||
const host = ref(null);
|
||||
const render = () => {
|
||||
host.value.innerHTML = renderMD(props.content || "", window.location.pathname + "/../");
|
||||
};
|
||||
onMounted(() => { render(); watch(() => [props.content, props.terms, props.options], render, { deep: true }); });
|
||||
return () => h("markdown", { ref: host });
|
||||
},
|
||||
};
|
||||
const GistFile = {
|
||||
props: ["file", "terms", "options"],
|
||||
setup(props) {
|
||||
const host = ref(null);
|
||||
const highlight = () => nextTick(() => host.value?.querySelectorAll("pre code").forEach(el => window.Prism?.highlightElement(el)));
|
||||
onMounted(highlight);
|
||||
watch(() => [props.file?.content, props.terms, props.options], highlight, { deep: true });
|
||||
return () => {
|
||||
const file = props.file || {};
|
||||
const extension = (file.filename || "").split(".").pop().toLowerCase();
|
||||
if (["md", "markdown"].includes(extension) || file.language === "Markdown") {
|
||||
return h("gist-file", { ref: host }, h(Markdown, { content: file.content, terms: props.terms, options: props.options }));
|
||||
}
|
||||
const aliases = { js: "javascript", jsx: "javascript", ts: "javascript", typescript: "javascript", py: "python", html: "markup", xml: "markup", svg: "markup", sh: "bash" };
|
||||
const language = (file.language || extension || "none").toLowerCase();
|
||||
return h("gist-file", { ref: host }, h("pre", { class: "line-numbers" }, h("code", { class: "language-" + (aliases[language] || language), key: file.content }, file.content || "")));
|
||||
};
|
||||
},
|
||||
};
|
||||
const Notebook = {
|
||||
props: ["file", "content"],
|
||||
setup(props) {
|
||||
const host = ref(null);
|
||||
let generation = 0;
|
||||
let request;
|
||||
const render = async () => {
|
||||
const current = ++generation;
|
||||
request?.abort();
|
||||
request = new AbortController();
|
||||
try {
|
||||
const json = props.content ? JSON.parse(props.content) : await fetch(props.file?.download_url || props.file, { signal: request.signal }).then(r => { if (!r.ok) throw Error("Notebook request failed"); return r.json(); });
|
||||
if (current !== generation) return;
|
||||
host.value.innerHTML = DOMPurify.sanitize(nb.parse(json).render());
|
||||
host.value.querySelectorAll("pre code").forEach(el => window.Prism?.highlightElement(el));
|
||||
} catch (error) { if (current === generation && error.name !== "AbortError") host.value.textContent = "Unable to render the notebook."; }
|
||||
};
|
||||
onMounted(() => { render(); watch(() => [props.file, props.content], render); });
|
||||
onBeforeUnmount(() => { generation++; request?.abort(); });
|
||||
return () => h("notebook", { ref: host });
|
||||
},
|
||||
};
|
||||
const Loc = {
|
||||
props: ["stats"],
|
||||
setup(props) {
|
||||
return () => {
|
||||
const rows = Object.entries(props.stats || {}).filter(([, stat]) => stat.code);
|
||||
const total = rows.reduce((sum, [, stat]) => sum + stat.code, 0);
|
||||
return h("loc", rows.map(([language, stat]) => h("div", { class: "lang", title: `${language}: ${stat.code.toLocaleString()} lines`, style: { width: stat.code * 100 / total + "%", background: langColors[language] } })));
|
||||
};
|
||||
},
|
||||
};
|
||||
export const components = { Markdown, GistFile, Notebook, Loc, HtmlDoc, Pdfviewer: PdfViewer };
|
||||
|
||||
export const codeEditor = {
|
||||
mounted(el, { value }) {
|
||||
const editor = ace.edit(el);
|
||||
el._editor = editor;
|
||||
editor.setValue(String(value.content ?? ""), -1);
|
||||
applyEditorOptions(el, value.options);
|
||||
value.options?.onLoad?.(editor);
|
||||
},
|
||||
updated(el, { value }) {
|
||||
if (el._editor.getValue() !== String(value.content ?? "")) el._editor.setValue(String(value.content ?? ""), -1);
|
||||
applyEditorOptions(el, value.options);
|
||||
},
|
||||
beforeUnmount(el) { el._editor.destroy(); },
|
||||
};
|
||||
function applyEditorOptions(el, options = {}) {
|
||||
if (options.mode) el._editor.session.setMode("ace/mode/" + options.mode);
|
||||
if (options.theme) el._editor.setTheme("ace/theme/" + options.theme);
|
||||
el._editor.setReadOnly(options.readOnly !== false);
|
||||
}
|
||||
export const paperScrollspy = {
|
||||
mounted(el) {
|
||||
if (!window.IntersectionObserver) return;
|
||||
const links = [...el.querySelectorAll('a[href^="#"]')];
|
||||
const visible = new Set();
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(e => e.isIntersecting ? visible.add(e.target.id) : visible.delete(e.target.id));
|
||||
const active = links.find(a => visible.has(a.hash.slice(1)));
|
||||
links.forEach(a => a.classList.toggle("active", a === active));
|
||||
}, { rootMargin: "-20% 0px -60% 0px", threshold: 0 });
|
||||
nextTick(() => links.forEach(a => { const target = document.getElementById(a.hash.slice(1)); if (target) observer.observe(target); }));
|
||||
el._observer = observer;
|
||||
},
|
||||
beforeUnmount(el) { el._observer?.disconnect(); },
|
||||
};
|
||||
Reference in New Issue
Block a user