merge: reconcile paid hosting with latest main

This commit is contained in:
Thomas Durieux committed 2026-11-02 21:12:29 +00:00
commit ded6f905fd
180 files changed
+21709 -3614

No files matched your search

+17 -5
View File
@@ -1,3 +1,4 @@
import { takeReviewFragment } from "./review-consent.js";
import { loadLibrary } from "./lazy-assets.js";
import { createApp, h, watch, provide, inject, onBeforeUnmount } from "vue";
import { createRouter, createWebHistory, RouterView } from "vue-router";
@@ -7,7 +8,7 @@ import { render as iconsTemplate } from "../partials/icons.htm";
import { initializeTemplate } from "./template-state.js";
import { createPageState, createTimers } from "./state.js";
import { createHttp, promises } from "./http.js";
import { mainController } from "./app.js";
import { mainController, highlightRedactions } from "./app.js";
import { createQuotaService } from "./quota.js";
import * as formatters from "./formatters.js";
import translations from "../i18n/locale-en.json";
@@ -18,7 +19,9 @@ import { fieldDirective, formDirective, submitForm } from "./forms.js";
const sessionKey = Symbol("session");
export const serverPaths = /^\/(w|api|github)(\/|$)/;
function translate(key, params = {}) {
const message = key?.split(".").reduce((value, part) => value?.[part], translations);
let message = key?.split(".").reduce((value, part) => value?.[part], translations);
// Unknown error codes would otherwise surface as the raw "ERRORS.code" key.
if (message == null && key?.startsWith("ERRORS.")) message = translations.ERRORS.unknown_error;
return String(message ?? key ?? "").replace(/{{\s*([^}]+?)\s*}}/g, (_, name) => params[name] ?? "");
}
const fmt = { ...formatters, translate };
@@ -31,6 +34,9 @@ export function safeUrl(value) {
}
export function mountApplication(target = "#app", options = {}) {
const isolatedConsent = !!document.querySelector('meta[name="review-consent-page"][content="1"]');
let reviewHandoff = isolatedConsent && window.location.pathname === "/review-link" ? takeReviewFragment(window) : null;
const takeReviewHandoff = () => { const value = reviewHandoff; reviewHandoff = null; return value; };
// AngularJS links stored the repository route in the fragment. Normalize it
// before web history reads the URL, replacing the entry so Back still works.
if (!options.history && window.location.pathname === "/" && /^#!\/r\/[^/?#]+/.test(window.location.hash)) {
@@ -52,7 +58,7 @@ export function mountApplication(target = "#app", options = {}) {
const value = router.currentRoute.value.params[key];
return Array.isArray(value) ? value.join("/") : value;
} });
return { http, ...timers, location, params, window, promises, html: { trustAsHtml: value => value }, translate: (key, params) => Promise.resolve(translate(key, params)), quotaService: createQuotaService(http) };
return { takeReviewHandoff, http, ...timers, location, params, window, promises, html: { trustAsHtml: value => value }, translate: (key, params) => Promise.resolve(translate(key, params)), quotaService: createQuotaService(http) };
}
function pageComponent(definition) {
return {
@@ -81,6 +87,8 @@ export function mountApplication(target = "#app", options = {}) {
root.window = window;
root.Math = Math;
root.sanitize = value => DOMPurify.sanitize(value ?? "");
// Reading site_options re-renders Markdown once the configured mask loads.
root.highlightRedactions = html => (root.site_options, highlightRedactions(html));
root.submitForm = submitForm;
root.safeUrl = safeUrl;
const context = services();
@@ -94,7 +102,7 @@ export function mountApplication(target = "#app", options = {}) {
const href = anchor.getAttribute("href");
if (href.startsWith("#")) return;
const url = new URL(anchor.href, window.location.href);
if (url.origin !== window.location.origin || serverPaths.test(url.pathname)) return;
if (isolatedConsent || /^\/review-link\/?$/i.test(url.pathname) || url.origin !== window.location.origin || serverPaths.test(url.pathname)) return;
event.preventDefault();
router.push(url.pathname + url.search + url.hash);
$("#navbarSupportedContent.show").collapse("hide");
@@ -103,7 +111,7 @@ export function mountApplication(target = "#app", options = {}) {
onBeforeUnmount(() => document.removeEventListener("click", navigate));
return () => [
iconsTemplate(root, []),
h("header", { class: "app-header" }, templates["partials/header.htm"](root, headerCache)),
isolatedConsent ? null : h("header", { class: "app-header" }, templates["partials/header.htm"](root, headerCache)),
h("main", { class: "app-view align-items-stretch w-100" }, h(RouterView, null, {
default: ({ Component, route }) => Component ? h(Component, { key: route.meta.preserveExplorer ? route.matched[0]?.path : route.path }) : null,
})),
@@ -126,6 +134,10 @@ export function mountApplication(target = "#app", options = {}) {
app.directive("paper-scrollspy", paperScrollspy);
app.use(router);
router.beforeEach(async to => {
if ((to.path === "/review-link" && !isolatedConsent) || (isolatedConsent && to.path !== "/review-link")) {
window.location.assign(to.fullPath);
return false;
}
root?.emit("routeLeave");
if (/^\/(r|repository|anonymize|pull-request-anonymize|gist-anonymize|pr|gist)(\/|$)/.test(to.path)) {
await loadLibrary("markdown");