loading improvements
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"core.min.js": "core.3db744fc07.min.js",
|
||||||
|
"vendor.min.js": "vendor.9df222182a.min.js",
|
||||||
|
"mermaid.min.js": "mermaid.f848a72d16.min.js",
|
||||||
|
"all.min.css": "all.8d9fbb7ca6.min.css"
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 563 KiB After Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 416 KiB After Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 175 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<!-- index.html -->
|
<!-- index.html -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" ng-app="anonymous-github" ng-controller="mainController">
|
<html lang="en" ng-controller="mainController">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta
|
<meta
|
||||||
@@ -38,8 +38,16 @@
|
|||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||||
|
media="print"
|
||||||
|
onload="this.media='all'"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="/css/all.min.css" />
|
<noscript>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||||
|
/>
|
||||||
|
</noscript>
|
||||||
|
<link rel="stylesheet" href="/css/__ALL_CSS__" />
|
||||||
</head>
|
</head>
|
||||||
<body keypress-events class="d-flex flex-column">
|
<body keypress-events class="d-flex flex-column">
|
||||||
<ng-include src="'partials/header.htm'"></ng-include>
|
<ng-include src="'partials/header.htm'"></ng-include>
|
||||||
@@ -74,26 +82,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/script/bundle.min.js"></script>
|
<script src="/script/__CORE_JS__"></script>
|
||||||
<script>
|
<script>
|
||||||
ace.config.set("basePath", "/script/external/ace/");
|
// Lazy-load mermaid only when a mermaid diagram is encountered
|
||||||
PDFJS.workerSrc = "/script/external/pdf.worker.js";
|
window.loadMermaid = function () {
|
||||||
|
if (window._mermaidLoaded || window._mermaidLoading) return;
|
||||||
|
window._mermaidLoading = true;
|
||||||
|
var s = document.createElement("script");
|
||||||
|
s.src = "/script/__MERMAID_JS__";
|
||||||
|
s.onload = function () {
|
||||||
|
window._mermaidLoaded = true;
|
||||||
|
window._mermaidLoading = false;
|
||||||
|
if (typeof mermaid !== "undefined") {
|
||||||
|
mermaid.initialize({ startOnLoad: false, theme: "default", securityLevel: "loose" });
|
||||||
|
window.mermaidInitialized = true;
|
||||||
|
var els = document.querySelectorAll(".mermaid:not([data-processed])");
|
||||||
|
if (els.length) mermaid.init(undefined, els);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.body.appendChild(s);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="https://storage.ko-fi.com/cdn/scripts/overlay-widget.js"></script>
|
<script src="/script/__VENDOR_JS__" defer onload="
|
||||||
|
ace.config.set('basePath', '/script/external/ace/');
|
||||||
|
PDFJS.workerSrc = '/script/external/pdf.worker.js';
|
||||||
|
angular.bootstrap(document, ['anonymous-github']);
|
||||||
|
"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.addEventListener("load", function () {
|
||||||
var isDark = localStorage.getItem("darkMode") === "true";
|
var s = document.createElement("script");
|
||||||
kofiWidgetOverlay.draw("tdurieux", {
|
s.src = "https://storage.ko-fi.com/cdn/scripts/overlay-widget.js";
|
||||||
type: "floating-chat",
|
s.onload = function () {
|
||||||
"floating-chat.donateButton.text": "Support me",
|
var isDark = localStorage.getItem("darkMode") === "true";
|
||||||
"floating-chat.donateButton.background-color": isDark
|
kofiWidgetOverlay.draw("tdurieux", {
|
||||||
? "#FAF9F6"
|
type: "floating-chat",
|
||||||
: "#1A1815",
|
"floating-chat.donateButton.text": "Support me",
|
||||||
"floating-chat.donateButton.text-color": isDark
|
"floating-chat.donateButton.background-color": isDark
|
||||||
? "#1A1815"
|
? "#FAF9F6"
|
||||||
: "#FAF9F6",
|
: "#1A1815",
|
||||||
});
|
"floating-chat.donateButton.text-color": isDark
|
||||||
})();
|
? "#1A1815"
|
||||||
|
: "#FAF9F6",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.body.appendChild(s);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
<div class="col-md-5 order-md-1">
|
<div class="col-md-5 order-md-1">
|
||||||
<img
|
<img
|
||||||
width="500"
|
width="500"
|
||||||
|
loading="lazy"
|
||||||
src="/imgs/anonymize.png"
|
src="/imgs/anonymize.png"
|
||||||
class="featurette-image img-fluid mx-auto"
|
class="featurette-image img-fluid mx-auto"
|
||||||
alt="Anonymize form"
|
alt="Anonymize form"
|
||||||
@@ -214,6 +215,7 @@
|
|||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<img
|
<img
|
||||||
width="500"
|
width="500"
|
||||||
|
loading="lazy"
|
||||||
src="/imgs/explorer.png"
|
src="/imgs/explorer.png"
|
||||||
class="featurette-image img-fluid mx-auto"
|
class="featurette-image img-fluid mx-auto"
|
||||||
alt="Repository explorer"
|
alt="Repository explorer"
|
||||||
@@ -234,6 +236,7 @@
|
|||||||
<div class="col-md-5 order-md-1">
|
<div class="col-md-5 order-md-1">
|
||||||
<img
|
<img
|
||||||
width="500"
|
width="500"
|
||||||
|
loading="lazy"
|
||||||
src="/imgs/dashboard.png"
|
src="/imgs/dashboard.png"
|
||||||
class="featurette-image img-fluid mx-auto"
|
class="featurette-image img-fluid mx-auto"
|
||||||
alt="Dashboard"
|
alt="Dashboard"
|
||||||
|
|||||||
@@ -24,33 +24,35 @@ function markedMermaid(options) {
|
|||||||
renderer(token) {
|
renderer(token) {
|
||||||
const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
|
const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
|
||||||
|
|
||||||
// Initialize Mermaid if not already done
|
|
||||||
if (typeof mermaid !== 'undefined' && !window.mermaidInitialized) {
|
|
||||||
mermaid.initialize({
|
|
||||||
startOnLoad: false,
|
|
||||||
theme: 'default',
|
|
||||||
securityLevel: 'loose'
|
|
||||||
});
|
|
||||||
window.mermaidInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a div that will be processed by Mermaid
|
// Create a div that will be processed by Mermaid
|
||||||
const div = `<div class="mermaid" id="${id}">${token.text}</div>`;
|
const div = `<div class="mermaid" id="${id}">${token.text}</div>`;
|
||||||
|
|
||||||
|
// Trigger lazy-load of mermaid if not yet loaded
|
||||||
|
if (typeof mermaid === 'undefined' && typeof window.loadMermaid === 'function') {
|
||||||
|
window.loadMermaid();
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule Mermaid rendering for after DOM insertion
|
// Schedule Mermaid rendering for after DOM insertion
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (typeof mermaid !== 'undefined') {
|
if (typeof mermaid === 'undefined') return;
|
||||||
try {
|
if (!window.mermaidInitialized) {
|
||||||
const element = document.getElementById(id);
|
mermaid.initialize({
|
||||||
if (element && !element.getAttribute('data-processed')) {
|
startOnLoad: false,
|
||||||
mermaid.init(undefined, element);
|
theme: 'default',
|
||||||
element.setAttribute('data-processed', 'true');
|
securityLevel: 'loose'
|
||||||
}
|
});
|
||||||
} catch (error) {
|
window.mermaidInitialized = true;
|
||||||
console.error('Mermaid rendering error:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 0);
|
try {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element && !element.getAttribute('data-processed')) {
|
||||||
|
mermaid.init(undefined, element);
|
||||||
|
element.setAttribute('data-processed', 'true');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Mermaid rendering error:', error);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ dotenv();
|
|||||||
|
|
||||||
import { createClient } from "redis";
|
import { createClient } from "redis";
|
||||||
import { resolve, join } from "path";
|
import { resolve, join } from "path";
|
||||||
import { existsSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import rateLimit from "express-rate-limit";
|
import rateLimit from "express-rate-limit";
|
||||||
import { slowDown } from "express-slow-down";
|
import { slowDown } from "express-slow-down";
|
||||||
import RedisStore from "rate-limit-redis";
|
import RedisStore from "rate-limit-redis";
|
||||||
@@ -31,6 +31,40 @@ import { createLogger, serializeError } from "../core/logger";
|
|||||||
|
|
||||||
const logger = createLogger("server");
|
const logger = createLogger("server");
|
||||||
|
|
||||||
|
// Lazily build the templated index.html on first request so the server
|
||||||
|
// works even when started before `gulp` finishes.
|
||||||
|
const indexHtmlPath = resolve("public", "index.html");
|
||||||
|
const manifestPath = resolve("public", "asset-manifest.json");
|
||||||
|
let indexHtmlCache: string | null = null;
|
||||||
|
|
||||||
|
function getIndexHtml(): string {
|
||||||
|
if (indexHtmlCache) return indexHtmlCache;
|
||||||
|
|
||||||
|
let assetManifest: Record<string, string> = {};
|
||||||
|
if (existsSync(manifestPath)) {
|
||||||
|
try {
|
||||||
|
assetManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
||||||
|
} catch {
|
||||||
|
// manifest missing or malformed — fall back to unhashed names
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function asset(name: string): string {
|
||||||
|
return assetManifest[name] || name;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = existsSync(indexHtmlPath)
|
||||||
|
? readFileSync(indexHtmlPath, "utf-8")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
html = html
|
||||||
|
.replace("__CORE_JS__", asset("core.min.js"))
|
||||||
|
.replace("__VENDOR_JS__", asset("vendor.min.js"))
|
||||||
|
.replace("__MERMAID_JS__", asset("mermaid.min.js"))
|
||||||
|
.replace("__ALL_CSS__", asset("all.min.css"));
|
||||||
|
indexHtmlCache = html;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
function indexResponse(req: express.Request, res: express.Response) {
|
function indexResponse(req: express.Request, res: express.Response) {
|
||||||
if (
|
if (
|
||||||
req.path.startsWith("/script") ||
|
req.path.startsWith("/script") ||
|
||||||
@@ -46,14 +80,13 @@ function indexResponse(req: express.Request, res: express.Response) {
|
|||||||
req.headers["accept"].indexOf("text/html") == -1
|
req.headers["accept"].indexOf("text/html") == -1
|
||||||
) {
|
) {
|
||||||
const repoId = req.path.split("/")[2];
|
const repoId = req.path.split("/")[2];
|
||||||
// if it is not an html request, it assumes that the browser try to load a different type of resource
|
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`/api/repo/${repoId}/file/${req.path.substring(
|
`/api/repo/${repoId}/file/${req.path.substring(
|
||||||
req.path.indexOf(repoId) + repoId.length + 1
|
req.path.indexOf(repoId) + repoId.length + 1
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
res.sendFile(resolve("public", "index.html"));
|
res.type("html").send(getIndexHtml());
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function start() {
|
export default async function start() {
|
||||||
@@ -243,11 +276,27 @@ export default async function start() {
|
|||||||
// web view
|
// web view
|
||||||
app.use("/w/", rate, webViewSpeedLimiter, router.webview);
|
app.use("/w/", rate, webViewSpeedLimiter, router.webview);
|
||||||
|
|
||||||
|
// Hashed assets (e.g. core.a1b2c3d4e5.min.js) — immutable, cache for 1 year.
|
||||||
|
// Strip the hash from the filename and serve the underlying file.
|
||||||
|
app.get(
|
||||||
|
/^\/(script|css)\/(.+)\.([a-f0-9]{10})\.(min\.\w+|\w+)$/,
|
||||||
|
(req, res, next) => {
|
||||||
|
const dir = req.params[0]; // "script" or "css"
|
||||||
|
const base = req.params[1]; // e.g. "core"
|
||||||
|
const ext = req.params[3]; // e.g. "min.js"
|
||||||
|
const filePath = join("public", dir, `${base}.${ext}`);
|
||||||
|
if (!existsSync(filePath)) return next();
|
||||||
|
res.set("Cache-Control", "public, max-age=31536000, immutable");
|
||||||
|
res.sendFile(resolve(filePath));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
express.static(join("public"), {
|
express.static(join("public"), {
|
||||||
etag: true,
|
etag: true,
|
||||||
lastModified: true,
|
lastModified: true,
|
||||||
maxAge: 3600, // 1h
|
maxAge: 86400000, // 1 day (fonts, images, partials)
|
||||||
|
index: false, // don't serve index.html for "/" — indexResponse handles it
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||