Theseus 0.3.109: Save page as PDF or PNG; connector design doc hosted on theseus.x
Right-click on a tab now shows a Save page as submenu with PDF and Image (PNG) items. PDF uses webContents.printToPDF (A4, prefers CSS page size, prints backgrounds); PNG uses webContents.capturePage of the visible viewport. Both open a save dialog with a filename derived from the tab title or hostname, write to the chosen path, and register the result in the downloads list. The Claude connector's design doc now ships at theseus.x/dev/claude-connector/DESIGN-claude-connector.md so readers on the site do not have to leave for the forge; the Developers page links the local copy first and the forge mirror alongside it.
This commit is contained in:
parent
a639f7a80d
commit
3ab3f77594
2 changed files with 47 additions and 1 deletions
46
main.js
46
main.js
|
|
@ -3995,6 +3995,48 @@ function addonIdForSender(sender) {
|
|||
return first || null;
|
||||
} catch { return null; }
|
||||
}
|
||||
// Right-click "Save page as…" → PDF via webContents.printToPDF, and PNG via
|
||||
// webContents.capturePage (visible viewport). Both ask for a save path with
|
||||
// a filename derived from the tab's title or hostname, write the bytes, and
|
||||
// register the file in the downloads list so the toolbar's downloads popup
|
||||
// shows it alongside normal downloads.
|
||||
async function savePageSavePath(wc, ext, fileLabel) {
|
||||
const title = (wc.getTitle() || "").trim();
|
||||
const host = (() => { try { return new URL(wc.getURL()).hostname; } catch { return ""; } })();
|
||||
const base = (title || host || "page").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, " ").slice(0, 120);
|
||||
const def = path.join(app.getPath("downloads"), `${base}.${ext}`);
|
||||
const parent = BrowserWindow.fromWebContents(wc) || win || BrowserWindow.getFocusedWindow();
|
||||
const r = await dialog.showSaveDialog(parent, {
|
||||
title: `Save page as ${fileLabel}`,
|
||||
defaultPath: def,
|
||||
filters: [{ name: fileLabel, extensions: [ext] }],
|
||||
});
|
||||
if (r.canceled || !r.filePath) return null;
|
||||
return r.filePath;
|
||||
}
|
||||
function registerSavedFile(filePath, mime) {
|
||||
try {
|
||||
const st = fs.statSync(filePath);
|
||||
downloads.unshift({ id: nextDlId++, name: path.basename(filePath), path: filePath, size: st.size, mime: mime || "application/octet-stream", state: "completed", receivedBytes: st.size, totalBytes: st.size, startTime: Date.now() });
|
||||
try { emitDownloads?.(); } catch {}
|
||||
} catch (e) { console.warn("registerSavedFile:", e?.message); }
|
||||
}
|
||||
async function savePageAsPdf(wc) {
|
||||
if (!wc || wc.isDestroyed()) return;
|
||||
const outPath = await savePageSavePath(wc, "pdf", "PDF");
|
||||
if (!outPath) return;
|
||||
const buf = await wc.printToPDF({ printBackground: true, pageSize: "A4", margins: { marginType: "default" }, preferCSSPageSize: true });
|
||||
fs.writeFileSync(outPath, buf);
|
||||
registerSavedFile(outPath, "application/pdf");
|
||||
}
|
||||
async function savePageAsImage(wc) {
|
||||
if (!wc || wc.isDestroyed()) return;
|
||||
const outPath = await savePageSavePath(wc, "png", "PNG image");
|
||||
if (!outPath) return;
|
||||
const img = await wc.capturePage();
|
||||
fs.writeFileSync(outPath, img.toPNG());
|
||||
registerSavedFile(outPath, "image/png");
|
||||
}
|
||||
// In-memory download list. Not persisted: closing the browser clears history
|
||||
// (the files are still on disk; only the list of "recent downloads" is dropped).
|
||||
const downloads = []; let nextDlId = 1; const dlItems = new Map(); // id -> DownloadItem
|
||||
|
|
@ -5492,6 +5534,10 @@ function createTab(initial, opts = {}) {
|
|||
{ label: "Back", enabled: wc.navigationHistory.canGoBack(), click: () => wc.navigationHistory.goBack() },
|
||||
{ label: "Forward", enabled: wc.navigationHistory.canGoForward(), click: () => wc.navigationHistory.goForward() },
|
||||
{ label: "Reload", click: () => wc.reload() },
|
||||
{ label: "Save page as…", submenu: [
|
||||
{ label: "PDF…", click: () => savePageAsPdf(wc).catch((e) => console.warn("save-pdf:", e?.message)) },
|
||||
{ label: "Image (PNG)…", click: () => savePageAsImage(wc).catch((e) => console.warn("save-png:", e?.message)) },
|
||||
]},
|
||||
);
|
||||
if (tab.webapp) {
|
||||
const inst = webapps.find(tab.webapp.key);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "theseus-navigator",
|
||||
"version": "0.3.108",
|
||||
"version": "0.3.109",
|
||||
"license": "MPL-2.0",
|
||||
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
||||
"author": "Silent Mode",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue