// Copies the desktop renderer into public/legacy/ for the web host, without editing the scripts: // js/.js renderer scripts, byte for byte except the fixes in tools/legacy-patches.mjs // (app.js and monitor.js are left out) // views/.html each
of index.html // chrome.html shared overlays: #menu, #modal, #toast, #task // legacy.css app.css scoped under .lsx (CSS nesting), @keyframes hoisted out, // :root tokens moved onto .lsx so the web theme can override them // usage: node tools/extract-legacy.mjs import fs from "node:fs"; import path from "node:path"; import { applyPatches, PATCHES } from "./legacy-patches.mjs"; const REF = path.resolve("../reference/lstools-desktop/src/renderer"); const OUT = path.resolve("public/legacy"); const SKIP = new Set(["app.js", "monitor.js"]); const DROP_VIEWS = new Set(["iop", "pm", "mon", "pw"]); // IOP Manager UI comes back later as its own page fs.rmSync(OUT, { recursive: true, force: true }); fs.mkdirSync(path.join(OUT, "js"), { recursive: true }); fs.mkdirSync(path.join(OUT, "views"), { recursive: true }); // ---------- scripts ---------- const scripts = fs.readdirSync(REF).filter((f) => f.endsWith(".js") && !SKIP.has(f)); for (const f of scripts) { if (!PATCHES.some((p) => p.file === f)) { fs.copyFileSync(path.join(REF, f), path.join(OUT, "js", f)); continue; } fs.writeFileSync(path.join(OUT, "js", f), applyPatches(f, fs.readFileSync(path.join(REF, f), "utf8"))); console.log(`patched ${f}: ${PATCHES.filter((p) => p.file === f).length}`); } // ---------- markup ---------- const html = fs.readFileSync(path.join(REF, "index.html"), "utf8"); /** The element starting at `start` (an opening tag) up to its matching close tag. */ function element(src, start) { const tag = src.slice(start + 1).match(/^[\w-]+/)[0]; const re = new RegExp(`<(/?)${tag}\\b[^>]*>`, "g"); re.lastIndex = start; let depth = 0; for (let m; (m = re.exec(src)); ) { if (m[0].endsWith("/>")) continue; depth += m[1] ? -1 : 1; if (depth === 0) return src.slice(start, re.lastIndex); } throw new Error(`unclosed <${tag}> at ${start}`); } const views = []; for (const m of html.matchAll(/
/g)) { const name = m[1]; if (DROP_VIEWS.has(name)) continue; fs.writeFileSync(path.join(OUT, "views", `${name}.html`), element(html, m.index)); views.push(name); } const chrome = []; for (const id of ["menu", "modal", "toast", "task"]) { const m = html.match(new RegExp(`<[\\w-]+[^>]*\\bid="${id}"[^>]*>`)); if (m) chrome.push(element(html, m.index)); else console.warn(`no #${id} in index.html`); } fs.writeFileSync(path.join(OUT, "chrome.html"), chrome.join("\n")); // ---------- css ---------- let css = fs.readFileSync(path.join(REF, "app.css"), "utf8"); const keyframes = []; css = css.replace(/@keyframes\s+[\w-]+\s*\{(?:[^{}]*\{[^{}]*\})*[^{}]*\}/g, (k) => (keyframes.push(k), "")); css = css.replace(/:root\s*\{/, "& {"); fs.writeFileSync(path.join(OUT, "legacy.css"), `/* Generated by tools/extract-legacy.mjs from the desktop app.css. Do not edit. */\n${keyframes.join("\n")}\n.lsx {\n${css}\n}\n`); // ---------- host.js: the shared helpers of app.js, copied line for line ---------- // Line ranges of the reference app.js (1-based, inclusive). Only helpers the tool scripts use; // IOP Manager / Patch Manager / Monitor code stays out. const APP_RANGES = [ [6, 103], // $, bridge, call, el, size, hex, fmtDos, MAX_ENTRIES, toast, modal, confirmBox, prompt, runTask [162, 162], // baseName [494, 558], // ICONS, icon() [564, 593], // showMenu, hideMenu, cmd [818, 823], // replay [1051, 1063], // unsavedWork (profileSource / profileSourceOption: web versions in host-web-after.js) ]; const appLines = fs.readFileSync(path.join(REF, "app.js"), "utf8").split(/\r?\n/); const copied = APP_RANGES.map(([a, b]) => `// ---- app.js ${a}-${b} ----\n` + appLines.slice(a - 1, b).join("\n")).join("\n\n"); const webPart = fs.readFileSync(path.resolve("src/legacy/host-web.js"), "utf8"); const webAfter = fs.readFileSync(path.resolve("src/legacy/host-web-after.js"), "utf8"); fs.writeFileSync( path.join(OUT, "host.js"), `"use strict";\n// Generated by tools/extract-legacy.mjs: helpers copied from the desktop app.js + web additions.\n\n${webPart}\n\n${copied}\n\n${webAfter}\n`, ); // Load order = the desktop index.html order (scripts depend on earlier ones' globals) const order = [...html.matchAll(/