theseus/bundled-addons/notepad/note.html

101 lines
3.6 KiB
HTML
Raw Normal View History

Theseus: add-on framework MVP + Notepad reference add-on New subsystem for extending Theseus with folders on disk. Each add-on lives at <userData>/addons/<id>/ with an addon.json manifest and a CommonJS entry that exports activate(api). Nothing about a private add-on ships in the public installer - drop the folder, restart, it's live. Bundled reference add-ons ride in the packaged app under resources/bundled-addons/ and are seeded into <userData>/addons/ on first boot; the framework treats seeded and drop-in add-ons the same. Files: - addons-host.js Loader + api.registerSidebarPanel() + per- addon storage on <userData>/addons-data/. Kept at the CommonJS-scoped top level (lib/ is ESM-scoped via its own package.json). - sidebar-preload.js Runs in every sidebar panel. Exposes window.silentmode.storage.{get,set,all} + onVisibility. Main-side handlers derive the add-on id from the sender file:// URL, so a panel can only touch its own store. - bundled-addons/notepad/ Reference add-on: addon.json, index.js, note.html. Autosaving textarea with char / word count. main.js: - Extension point: sidebar-panel. One right-anchored WebContentsView (SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab views by the sidebar width when visible. First registered panel wins for MVP; picker for multiple panels lands later. - initAddons() at app.whenReady(): seedBundledAddons, then AddonHost.discoverAndActivate. - IPC surface: sidebar-toggle / sidebar-open / sidebar-close / sidebar-state, addons-list / addons-set-enabled / addons-reveal / addons-open-dir / addons-reload, and origin-gated addon-storage-get/set/all. - Settings gains `disabledAddons: []` — off-toggled ids persist and the loader honours them without a restart (discoverAndActivate runs again on toggle). chrome.html: toolbar sidebar-toggle button, hidden until at least one add-on has registered a sidebar panel. settings.html: new "Add-ons" section under privacy. Lists installed add-ons with icon / name / version / description / capabilities; per-add-on enable/disable toggle + Show folder button; page-level Reload and Open add-ons folder buttons; warning note about the trust model. package.json: build.files gains sidebar-preload.js + addons-host.js. extraResources gains bundled-addons/ so the packaged app carries the reference notepad for the first-boot seed. Verified: `npm start` boots, addons-host discovers the notepad, activates it, registers one sidebar panel. Log confirms "1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering + notepad UI need clicked-through validation on a real install. Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban. Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Notepad</title>
<style>
:root { color-scheme: light dark;
--bg:#0e131c; --panel:#141a24; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; }
@media (prefers-color-scheme: light) {
:root { --bg:#f8faff; --panel:#ffffff; --line:rgba(0,0,0,.10);
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5; }
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { background: var(--bg); color: var(--ink);
font: 14px/1.55 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
display: flex; flex-direction: column; }
header { display: flex; align-items: center; justify-content: space-between;
padding: 10px 14px; border-bottom: 1px solid var(--line);
background: var(--panel); }
header .t { font-weight: 600; letter-spacing: .2px; display: flex; gap: 8px; align-items: center; }
header .t .em { font-size: 15px; }
header .m { color: var(--dim); font-size: 11.5px; font-variant-numeric: tabular-nums; }
textarea {
flex: 1; width: 100%; padding: 14px 16px; border: none; outline: none; resize: none;
background: transparent; color: var(--ink);
font: 13.5px/1.65 ui-monospace, "Cascadia Code", Consolas, monospace;
}
textarea::placeholder { color: var(--dim); }
footer { padding: 6px 14px 10px; border-top: 1px solid var(--line);
color: var(--dim); font-size: 11px; display: flex; justify-content: space-between; }
footer .l { display: flex; gap: 10px; }
footer .l b { color: var(--mut); font-weight: 500; }
footer button { border: none; background: transparent; color: var(--mut);
cursor: pointer; font: inherit; padding: 0; }
footer button:hover { color: var(--acid); }
</style>
</head>
<body>
<header>
<div class="t"><span class="em">📝</span> <span>Notepad</span></div>
<div class="m" id="status">saved</div>
</header>
<textarea id="pad" placeholder="Notes. Autosaves as you type."
spellcheck="false" autofocus></textarea>
<footer>
<div class="l"><b><span id="cchars">0</span></b> chars · <b><span id="cwords">0</span></b> words</div>
<button id="clear" title="Clear all notes">clear</button>
</footer>
<script>
const pad = document.getElementById("pad");
const status = document.getElementById("status");
const cchars = document.getElementById("cchars");
const cwords = document.getElementById("cwords");
function updateCounts() {
const v = pad.value;
cchars.textContent = v.length;
cwords.textContent = v.trim() ? v.trim().split(/\s+/).length : 0;
}
// Load previous text on open.
(async () => {
try {
const text = await window.silentmode.storage.get("text", "");
pad.value = text || "";
updateCounts();
} catch (e) { console.warn("load failed:", e); }
})();
// Debounced autosave.
let saveTimer = null;
pad.addEventListener("input", () => {
updateCounts();
status.textContent = "saving…";
clearTimeout(saveTimer);
saveTimer = setTimeout(async () => {
try {
await window.silentmode.storage.set("text", pad.value);
status.textContent = "saved";
} catch (e) {
status.textContent = "save failed";
console.warn("save failed:", e);
}
}, 400);
});
document.getElementById("clear").addEventListener("click", async () => {
if (!pad.value) return;
if (!confirm("Clear all notes?")) return;
pad.value = "";
updateCounts();
try { await window.silentmode.storage.set("text", ""); status.textContent = "saved"; } catch {}
pad.focus();
});
</script>
</body>
</html>