Moving it out of the build left it with no way in. Settings can only install from the community catalogue, so a first-party extension that isn't bundled has a working update channel and no first copy for anyone to update — the mechanism was all there and the front door was missing. So it goes back beside screenshot, aegis and pdf-editor: seeded into every profile by the build, listed under "Built into Theseus", and kept current between releases by the operator-signed channel at theseus.x/extensions/docx-editor/. That is the arrangement docs/ADDON-UPDATES.md describes, and the one the signing script was written for. About 400 KB compressed in the installer, most of it the vendored editor libraries — next to the ~4 MB of pdf.js that pdf-editor already ships, the weight argument for keeping it out didn't survive contact with the numbers. The end-to-end driver goes back to checking that a fresh profile seeds it, which is the property that actually matters now.
101 lines
4.3 KiB
JavaScript
101 lines
4.3 KiB
JavaScript
// Rasterise extensions/docx-editor/icon.svg to PNGs.
|
|
//
|
|
// npm run icons (from addon-build/docx-editor/)
|
|
//
|
|
// SVG is what the dock and the catalogue use; the PNGs are for everywhere
|
|
// that can't take one — a favicon fallback, a listing thumbnail, an OS file
|
|
// association later on. There is no sharp/resvg on this machine, so the
|
|
// rasteriser is Electron itself: a hidden window, the SVG scaled to fill it,
|
|
// and capturePage(). Re-run it whenever icon.svg changes.
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
|
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = path.resolve(here, "../..");
|
|
const EXT = path.resolve(ROOT, "bundled-addons/docx-editor");
|
|
const SIZES = [16, 32, 48, 128, 256];
|
|
|
|
const electron = path.join(ROOT, "node_modules", "electron", "dist",
|
|
process.platform === "win32" ? "electron.exe" : "electron");
|
|
if (!fs.existsSync(electron)) {
|
|
console.error(`electron not found at ${electron} — run npm install in TheseusNavigator first`);
|
|
process.exit(2);
|
|
}
|
|
|
|
// The main script runs inside Electron; it is written to a temp dir rather
|
|
// than committed, because it is scaffolding for this script and nothing else.
|
|
const work = fs.mkdtempSync(path.join(os.tmpdir(), "docx-icons-"));
|
|
const svg = fs.readFileSync(path.join(EXT, "icon.svg"), "utf8");
|
|
|
|
for (const size of SIZES) {
|
|
fs.writeFileSync(path.join(work, `page-${size}.html`), `<!doctype html><meta charset="utf-8">
|
|
<style>html,body{margin:0;padding:0;background:transparent}
|
|
svg{width:${size}px;height:${size}px;display:block}</style>${svg}`);
|
|
}
|
|
|
|
fs.writeFileSync(path.join(work, "main.js"), `
|
|
const { app, BrowserWindow } = require("electron");
|
|
const fs = require("fs"), path = require("path");
|
|
const work = ${JSON.stringify(work)};
|
|
const out = ${JSON.stringify(EXT)};
|
|
const sizes = ${JSON.stringify(SIZES)};
|
|
app.disableHardwareAcceleration();
|
|
|
|
// One window, resized per icon. Creating and destroying an offscreen window
|
|
// per size raced with itself: the next loadFile came back ERR_FAILED while
|
|
// the previous one was still tearing down its compositor.
|
|
app.whenReady().then(async () => {
|
|
const win = new BrowserWindow({
|
|
width: 256, height: 256, show: false, frame: false, transparent: true,
|
|
useContentSize: true, webPreferences: { offscreen: true },
|
|
});
|
|
for (const size of sizes) {
|
|
win.setContentSize(size, size);
|
|
await win.loadFile(path.join(work, "page-" + size + ".html"));
|
|
// Offscreen windows paint asynchronously and the first frame is often
|
|
// the blank one, so wait for a couple before capturing.
|
|
await new Promise((r) => {
|
|
let n = 0;
|
|
const onPaint = () => { if (++n >= 2) { win.webContents.off("paint", onPaint); r(); } };
|
|
win.webContents.on("paint", onPaint);
|
|
setTimeout(r, 1500);
|
|
});
|
|
const img = await win.webContents.capturePage();
|
|
fs.writeFileSync(path.join(out, "icon-" + size + ".png"), img.toPNG());
|
|
}
|
|
win.destroy();
|
|
app.quit();
|
|
}).catch((e) => { console.error("icon render failed:", e); app.exit(1); });
|
|
`);
|
|
fs.writeFileSync(path.join(work, "package.json"), JSON.stringify({ name: "docx-icons", main: "main.js" }));
|
|
|
|
execFileSync(electron, [work], { stdio: "inherit" });
|
|
for (const size of SIZES) {
|
|
const f = path.join(EXT, `icon-${size}.png`);
|
|
console.log(`${path.relative(ROOT, f)} ${fs.existsSync(f) ? (fs.statSync(f).size / 1024).toFixed(1) + " KB" : "MISSING"}`);
|
|
}
|
|
fs.rmSync(work, { recursive: true, force: true });
|
|
|
|
// The dock and the catalogue card read `icon` out of addon.json, and both
|
|
// take a data URL (this is how Aegis ships its mark). Deriving it from
|
|
// icon.svg here means there is one drawing, not two that drift apart.
|
|
const minified = svg
|
|
.replace(/<!--[\s\S]*?-->/g, "")
|
|
.replace(/<title>[\s\S]*?<\/title>/g, "")
|
|
.replace(/\s+/g, " ")
|
|
.replace(/>\s+</g, "><")
|
|
.trim();
|
|
const dataUrl = "data:image/svg+xml;utf8," + encodeURIComponent(minified);
|
|
|
|
const manifestPath = path.join(EXT, "addon.json");
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
if (manifest.icon !== dataUrl) {
|
|
manifest.icon = dataUrl;
|
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
console.log(`addon.json icon updated (${dataUrl.length} chars)`);
|
|
} else {
|
|
console.log("addon.json icon already current");
|
|
}
|