refactor: migrate the frontend from AngularJS to Vue 3

This commit is contained in:
tdurieux
2026-09-09 13:19:53 +02:00
parent dc0ef022fb
commit 4a18f94631
65 changed files with 6164 additions and 8425 deletions
+21 -40
View File
@@ -1,33 +1,13 @@
/**
* Renders a repository's .html file as a document instead of showing its
* source (#771). Self-contained HTML reports — Quarto, R Markdown, nbconvert
* — are a common way to ship rendered results, and dumping several megabytes
* of markup into the code editor was neither readable nor fast.
*
* The markup is untrusted: it comes from the repository under review. It is
* therefore written into a `sandbox`ed iframe, and scripts are OFF by default
* — the reader opts in per file with the "Enable JS" action. Even when they
* do, the sandbox never gets `allow-same-origin`: with `allow-scripts` that
* pair lets the framed document remove its own sandbox, after which it could
* reach the app's cookies, session and DOM.
*
* We use `srcdoc` rather than pointing the iframe at the file API so the
* response's `X-Frame-Options: SAMEORIGIN` doesn't block the frame: under
* sandbox the document's origin is opaque and never matches "same origin".
* An injected <base> keeps any relative images/stylesheets resolving against
* the file's own directory in the anonymized repo.
*/
import { h, ref, watch, onMounted } from "vue";
export default {
name: "htmlDoc",
props: ["content","baseUrl","allowScripts"],
setup(props) {
const elementRef = ref(null);
onMounted(() => {
const element = [elementRef.value];
angular.module("htmlDoc", []).directive("htmlDoc", [
function () {
return {
restrict: "E",
scope: {
content: "<",
baseUrl: "@",
allowScripts: "<",
},
link: function (scope, element) {
const host = element[0];
host.classList.add("html-doc");
@@ -35,15 +15,15 @@ angular.module("htmlDoc", []).directive("htmlDoc", [
// The sandbox attribute only takes effect on navigation, so a fresh
// iframe is the reliable way to apply a changed policy.
host.innerHTML = "";
const content = scope.content;
const content = props.content;
if (typeof content !== "string") return;
const iframe = document.createElement("iframe");
iframe.className = "html-doc-frame";
iframe.setAttribute("title", "Rendered HTML document");
// No allow-same-origin — see the note above.
// Never combine allow-scripts with allow-same-origin.
const sandbox = ["allow-popups", "allow-popups-to-escape-sandbox"];
if (scope.allowScripts) {
if (props.allowScripts) {
sandbox.push("allow-scripts", "allow-forms", "allow-modals");
}
iframe.setAttribute("sandbox", sandbox.join(" "));
@@ -51,19 +31,20 @@ angular.module("htmlDoc", []).directive("htmlDoc", [
host.appendChild(iframe);
let base = "";
if (scope.baseUrl) {
if (props.baseUrl) {
base =
'<base href="' +
scope.baseUrl.replace(/&/g, "&amp;").replace(/"/g, "&quot;") +
props.baseUrl.replace(/&/g, "&amp;").replace(/"/g, "&quot;") +
'">';
}
iframe.srcdoc = base + content;
}
scope.$watch("content", render);
scope.$watch("baseUrl", render);
scope.$watch("allowScripts", render);
},
};
watch(() => props.content, render, { immediate: true });
watch(() => props.baseUrl, render, { immediate: true });
watch(() => props.allowScripts, render, { immediate: true });
});
return () => h("html-doc", { ref: elementRef });
},
]);
};