← Notes results

Notes

Qwen 3.8 27B GGUF · typescript

Initial view of Notes

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 129,173 bytes · SHA-256 cbdea950a4f9
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notely — Notes</title>
<style>
/* ── css: base ── */
/* ============ RESET & TOKENS ============ */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
:root {
  /* fallbacks (JS injects the full palette from constants) */
  --bg-app:#f7f5f2; --bg-sidebar:#f0ede8; --bg-list:#f4f1ec; --bg-editor:#fdfcfa;
  --tx-1:#2b2723; --tx-2:#6e675e; --tx-3:#a49a8d;
  --border:#e4e0d7; --border-soft:#ece8e1;
  --hover:rgba(43,39,35,.05); --active:rgba(168,86,46,.10);
  --accent:#a8562e; --accent-ink:#8f4a27; --accent-soft:rgba(168,86,46,.12);
  --mark:#fbe58f; --mark-ink:#4a3d10;
  --shadow:0 6px 24px rgba(43,39,35,.16);
  --font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --font-read:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Charter,Georgia,"Times New Roman",serif;
  --font-mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
  color-scheme: light;
}
html[data-theme="dark"] { color-scheme: dark; }
body {
  font-family: var(--font-ui);
  font-size: 13.5px;
  color: var(--tx-1);
  background: var(--bg-app);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
button { font-family: inherit; font-size: inherit; color: inherit; background: none; border: none; padding: 0; cursor: pointer; }
input, select { font-family: inherit; color: inherit; }
::selection { background: var(--accent-soft); }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: var(--tx-3); border: 2px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-track { background: transparent; }
mark { background: var(--mark); color: var(--mark-ink); border-radius: 3px; padding: 0 1px; }
/* ── css: layout ── */
/* ============ APP SHELL / THREE PANES ============ */
#app { display: flex; height: 100vh; width: 100vw; position: relative; }
.pane { min-width: 0; display: flex; flex-direction: column; overflow: hidden; }

.sidebar {
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border);
  flex: 0 0 auto;
  width: 256px;
  transition: width .18s ease;
}
#app.sb-collapsed .sidebar { width: 0 !important; border-right: none; }
.list-pane {
  background: var(--bg-list);
  border-right: 1px solid var(--border);
  width: 324px;
  flex: 0 0 auto;
}
.editor-pane { flex: 1 1 auto; background: var(--bg-editor); position: relative; }

.resize-handle {
  position: absolute; top: 0; bottom: 0; width: 6px; margin-left: -3px;
  cursor: col-resize; z-index: 30;
}
.resize-handle:hover, .resizing .resize-handle { background: var(--accent-soft); }
body.resizing { cursor: col-resize; user-select: none; }
body.resizing .pane { transition: none; }

/* ============ SIDEBAR ============ */
.sidebar-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 10px 10px 16px;
}
.brand { display: flex; align-items: center; gap: 9px; user-select: none; }
.brand-mark {
  width: 24px; height: 24px; border-radius: 7px;
  background: var(--accent); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 13px; letter-spacing: .2px;
  box-shadow: inset 0 -6px 12px rgba(0,0,0,.14);
}
.brand-name { font-size: 15px; font-weight: 650; letter-spacing: .01em; }

.icon-btn {
  width: 26px; height: 26px; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--tx-2); flex: 0 0 auto;
  transition: background .12s ease, color .12s ease;
}
.icon-btn:hover { background: var(--hover); color: var(--tx-1); }
.icon-btn:active { transform: scale(.94); }
.icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.sb-section-label {
  font-size: 10.5px; font-weight: 650; letter-spacing: .09em; text-transform: uppercase;
  color: var(--tx-3); padding: 14px 16px 5px;
  display: flex; align-items: center; gap: 5px;
}
.sb-section-label svg { opacity: .8; }

.side-row {
  display: flex; align-items: center; gap: 9px;
  margin: 1px 8px; padding: 6px 8px; border-radius: 8px;
  color: var(--tx-2); cursor: pointer; user-select: none;
  transition: background .12s ease, color .12s ease;
}
.side-row:hover { background: var(--hover); color: var(--tx-1); }
.side-row.active { background: var(--active); color: var(--accent-ink); font-weight: 550; }
.row-icon { display: inline-flex; width: 18px; justify-content: center; color: inherit; }
.side-row.active .row-icon { color: var(--accent); }
.row-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.row-count { font-size: 11px; color: var(--tx-3); font-variant-numeric: tabular-nums; }
.side-row.active .row-count { color: var(--accent-ink); opacity: .75; }

.tags-block { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; padding: 2px 14px 0; }
.sidebar-foot { margin-top: auto; border-top: 1px solid var(--border-soft); padding-top: 8px; }

/* tags chips */
.tag-chip {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 11.5px; color: var(--tx-2);
  background: transparent; border: 1px solid var(--border);
  padding: 2px 8px; border-radius: 999px;
  transition: background .12s ease, color .12s, border-color .12s;
}
.tag-chip:hover { background: var(--hover); color: var(--tx-1); border-color: var(--tx-3); }
.tag-chip.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); font-weight: 550; }
.tag-chip.add { border-style: dashed; color: var(--tx-3); }
.tag-x {
  width: 14px; height: 14px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  color: var(--tx-3); opacity: 0; transition: opacity .12s, background .12s;
}
.tag-chip:hover .tag-x { opacity: 1; }
.tag-x:hover { background: var(--hover); color: var(--tx-1); }
.tag-chip-label { cursor: pointer; }

/* ============ NOTEBOOKS (sidebar) ============ */
.notebooks-block { padding-bottom: 6px; }
.nb-row { margin: 1px 8px; border-radius: 8px; transition: background .12s; }
.nb-row.drop-target { background: var(--accent-soft); outline: 1px dashed var(--accent); outline-offset: -2px; }
.nb-head {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px; border-radius: 8px; cursor: pointer; user-select: none;
  color: var(--tx-2);
}
.nb-head:hover { background: var(--hover); color: var(--tx-1); }
.nb-row.active > .nb-head { background: var(--active); color: var(--accent-ink); }
.nb-row.active > .nb-head .nb-icon, .nb-row.active > .nb-head .nb-chev { color: var(--accent); }
.nb-chev {
  width: 18px; height: 18px; border-radius: 5px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: transform .16s ease, background .12s;
  flex: 0 0 auto;
}
.nb-chev:hover { background: var(--hover); }
.nb-row.collapsed .nb-chev { transform: rotate(-90deg); }
.nb-icon { display: inline-flex; flex: 0 0 auto; }
.nb-name {
  flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 550;
}
.nb-menu { opacity: 0; transition: opacity .12s; }
.nb-head:hover .nb-menu { opacity: 1; }

.nb-notes { padding: 1px 0 3px 24px; display: flex; flex-direction: column; gap: 1px; }
.nb-empty { font-size: 12px; color: var(--tx-3); padding: 3px 8px; font-style: italic; }
.mini-note {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 8px; border-radius: 6px; cursor: pointer;
  color: var(--tx-2); font-size: 12.5px;
  transition: background .12s ease, color .12s;
}
.mini-note:hover { background: var(--hover); color: var(--tx-1); }
.mini-note.selected { background: var(--active); color: var(--accent-ink); }
.mini-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-pin { color: var(--accent); display: inline-flex; flex: 0 0 auto; }

/* ============ SIDEBAR TOGGLE (collapsed rail) ============ */
.sb-toggle {
  position: absolute; top: 12px; left: 12px; z-index: 5;
  width: 30px; height: 30px; border-radius: 8px;
  display: none; align-items: center; justify-content: center;
  color: var(--tx-2); background: var(--bg-list); border: 1px solid var(--border);
  transition: background .12s, color .12s;
}
.sb-toggle:hover { color: var(--tx-1); background: var(--hover); }
#app.sb-collapsed .sb-toggle { display: inline-flex; }
/* ── css: list ── */
.search-wrap {
  display: flex; align-items: center; gap: 6px;
  background: var(--bg-app); border: 1px solid var(--border);
  border-radius: 8px; padding: 5px 9px;
  color: var(--tx-3);
  transition: border-color .12s, box-shadow .12s;
  flex: 1 1 auto; min-width: 84px; max-width: 170px;
  margin-left: 4px;
}
.search-wrap:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.search-wrap svg { flex: 0 0 auto; }
.search-input { border: none; outline: none; background: transparent; width: 100%; font-size: 12.5px; color: var(--tx-1); min-width: 0; }
.search-input::placeholder { color: var(--tx-3); }
/* ── css: editor ── */
/* ============ EDITOR PANE ============ */
.theme-btn {
  position: absolute; top: 10px; right: 10px; z-index: 20;
  width: 30px; height: 30px; border-radius: 8px;
  background: var(--bg-list); border: 1px solid var(--border);
  transition: background .12s, color .12s, transform .3s ease;
}
.theme-btn:hover { color: var(--tx-1); background: var(--hover); }
.theme-btn:active { transform: rotate(40deg); }

.toolbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 1px; flex-wrap: wrap;
  padding: 8px 48px 8px 14px;
  background: var(--bg-editor);
  border-bottom: 1px solid var(--border-soft);
}
.tb-btn {
  width: 28px; height: 28px; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--tx-2);
  transition: background .1s, color .1s;
}
.tb-btn:hover { background: var(--hover); color: var(--tx-1); }
.tb-btn.on { background: var(--accent-soft); color: var(--accent-ink); }
.tb-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }

.saved-ind {
  position: absolute; top: 14px; right: 48px; z-index: 20;
  font-size: 11px; font-weight: 550; color: var(--accent-ink);
  background: var(--accent-soft); padding: 3px 9px; border-radius: 999px;
  opacity: 0; transform: translateY(-3px);
  transition: opacity .25s ease, transform .25s ease;
  pointer-events: none;
}
.saved-ind.show { opacity: 1; transform: none; }

.note-view { height: 100%; display: flex; flex-direction: column; overflow: hidden; }
.note-meta {
  padding: 10px 26px 0; font-size: 11.5px; color: var(--tx-3);
  letter-spacing: .01em; user-select: none;
}

.editor-root {
  flex: 1; overflow-y: auto; position: relative;
  padding: 12px 26px 40vh;
  font-family: var(--font-read);
  font-size: 15.5px; line-height: 1.72;
  color: var(--tx-1);
  caret-color: var(--accent);
  outline: none;
}
.editor-root:has(> p:only-child:has(> br:only-child))::before {
  content: "Start writing…  (try “# ”, “- ” or “[] ” at the start of a line)";
  position: absolute; top: 12px; left: 26px;
  color: var(--tx-3); font-size: 15.5px;
  pointer-events: none;
}
.editor-root > * { margin: 0 0 .55em; }
.editor-root p { min-height: 1.72em; }
.editor-root h1 { font-size: 1.72em; font-weight: 700; line-height: 1.25; margin: .4em 0 .5em; letter-spacing: -.01em; }
.editor-root h2 { font-size: 1.36em; font-weight: 680; line-height: 1.3; margin: .7em 0 .45em; letter-spacing: -.005em; }
.editor-root h3 { font-size: 1.13em; font-weight: 660; line-height: 1.35; margin: .7em 0 .4em; }
.editor-root blockquote {
  margin: .8em 0; padding: .15em 0 .15em 18px;
  border-left: 3px solid var(--accent);
  color: var(--tx-2);
}
.editor-root blockquote > * { margin-bottom: .3em; }
.editor-root ul, .editor-root ol { padding-left: 1.5em; margin: .3em 0 .6em; }
.editor-root li { margin: .18em 0; }
.editor-root ol { list-style: decimal; }
.editor-root ul { list-style: none; }
.editor-root li:not([data-check])::before { content: "•"; color: var(--accent); margin-right: .55em; display: inline-block; }
.editor-root li li:not([data-check])::before { content: "◦"; }
.editor-root hr { border: none; border-top: 1px solid var(--border); margin: 1.3em 0; }
.editor-root code {
  font-family: var(--font-mono); font-size: .82em;
  background: var(--hover); border: 1px solid var(--border-soft);
  padding: .1em .35em; border-radius: 5px; color: var(--accent-ink);
}
.editor-root b, .editor-root strong { font-weight: 700; }
.editor-root input { display: none; }

/* checklist rows (li[data-check]) */
.editor-root li[data-check] { display: block; margin: .22em 0; list-style: none; }
.editor-root li[data-check]::before { content: none; }
.ck-label { display: flex; align-items: flex-start; gap: 9px; cursor: pointer; }
.ck-box {
  flex: 0 0 auto; margin-top: .28em;
  width: 17px; height: 17px; border-radius: 5px;
  border: 1.6px solid var(--tx-3);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .13s ease, border-color .13s ease;
}
.ck-box::after {
  content: "✓"; font-size: 11px; font-weight: 800; color: #fff;
  transform: scale(0); transition: transform .14s cubic-bezier(.34,1.56,.64,1);
  font-family: var(--font-ui);
}
.editor-root li[data-check="true"] .ck-box { background: var(--accent); border-color: var(--accent); }
.editor-root li[data-check="true"] .ck-box::after { transform: scale(1); }
.ck-text { flex: 1; }
.editor-root li[data-check="true"] .ck-text { color: var(--tx-3); text-decoration: line-through; text-decoration-color: var(--tx-3); }

/* ============ TAG ROW (under editor) ============ */
.tag-row { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 26px 0; }
.tag-row:empty { padding: 0; }

/* ============ EMPTY STATE ============ */
.empty-state {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; color: var(--tx-3); text-align: center;
  animation: fade-in .35s ease;
}
@keyframes fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.empty-mark {
  width: 58px; height: 58px; border-radius: 16px;
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 8px;
}
.empty-mark svg { width: 26px; height: 26px; }
.empty-title { font-family: var(--font-read); font-size: 21px; font-weight: 640; color: var(--tx-2); letter-spacing: .01em; }
.empty-sub { font-size: 13px; max-width: 320px; line-height: 1.5; }
.primary-btn {
  margin-top: 16px;
  background: var(--accent); color: #fff;
  font-size: 13px; font-weight: 600;
  padding: 8px 20px; border-radius: 9px;
  box-shadow: 0 2px 8px rgba(168,86,46,.35);
  transition: transform .12s ease, box-shadow .12s ease, background .12s;
}
.primary-btn:hover { background: var(--accent-ink); transform: translateY(-1px); box-shadow: 0 4px 14px rgba(168,86,46,.4); }
.primary-btn:active { transform: translateY(0); }
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./editor":"src/editor.ts","./view":"src/view.ts","./sanitize":"src/sanitize.ts","./types":"src/types.ts"},"src/state.ts":{"./constants":"src/constants.ts","./types":"src/types.ts","./utils":"src/utils.ts","./sanitize":"src/sanitize.ts"},"src/editor.ts":{"./constants":"src/constants.ts","./state":"src/state.ts","./sanitize":"src/sanitize.ts"},"src/view.ts":{"./constants":"src/constants.ts","./types":"src/types.ts","./utils":"src/utils.ts","./sanitize":"src/sanitize.ts","./state":"src/state.ts","./editor":"src/editor.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// MAIN — entry point: seed data, build DOM, wire the editor
// and the store, kick off the render loop.
// ============================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const S = __importStar(require("./state"));
const editor_1 = require("./editor");
const view_1 = require("./view");
const sanitize_1 = require("./sanitize");
function seed() {
    const t0 = Date.now() - 86400000 * 12;
    const nbWork = { id: 'nb-work', name: 'Work', collapsed: false };
    const nbLife = { id: 'nb-life', name: 'Life', collapsed: false };
    const n1 = {
        id: 'seed-1', notebookId: 'nb-work', pinned: true,
        createdAt: t0, updatedAt: Date.now() - 3600000 * 2,
        trashedAt: null, trashedFrom: null, tags: ['ideas'],
        body: `<h1>Welcome to Notely</h1>
<p>A quiet place to think. Everything you write is saved <b>automatically</b> — look for the “Saved” pulse up top.</p>
<h2>Things that work well</h2>
<ul><li>Full rich-text editing, with a <i>serif reading face</i></li><li>Search that finds text <u>inside</u> bold spans</li><li>Tags, pins, notebooks and a Trash</li></ul>
<h2>Keyboard-first</h2>
<ul>
<li data-check="false"><label class="ck-label"><span class="ck-box"><input class="ck-cb" type="checkbox"></span><span class="ck-text">Try <code>Ctrl</code>+<code>B</code> for bold</span></label><br></li>
<li data-check="true"><label class="ck-label"><span class="ck-box"><input class="ck-cb" type="checkbox" checked></span><span class="ck-text">This one is already done</span></label><br></li>
<li data-check="false"><label class="ck-label"><span class="ck-box"><input class="ck-cb" type="checkbox"></span><span class="ck-text">Click a box to check it — the state persists</span></label><br></li>
</ul>
<blockquote><p>“The scariest moment is always just before you start.”</p></blockquote>
<hr>
<p>Created on day one of this notebook.</p>`,
    };
    const n2 = {
        id: 'seed-2', notebookId: 'nb-work', pinned: false,
        createdAt: t0 + 86400000, updatedAt: Date.now() - 3600000 * 26,
        trashedAt: null, trashedFrom: null, tags: ['meeting'],
        body: `<p>Sync notes for <b>friday review</b>. Bring the roadmaps and the open risks.</p>
<p>- </p>`,
    };
    n2.body = `<p>Sync notes for <b>friday review</b>. Bring the roadmaps and the open risks.</p>
<ul><li>Roadmap — final pass</li><li>Risks — top three</li><li>Demo — two minutes max</li></ul>`;
    const n3 = {
        id: 'seed-3', notebookId: 'nb-life', pinned: false,
        createdAt: t0 + 86400000 * 2, updatedAt: Date.now() - 86400000 * 1,
        trashedAt: null, trashedFrom: null, tags: ['garden'],
        body: `<p>What to plant in spring:</p>
<ul><li>Tomatoes — the big one</li><li>Basil, everywhere</li><li><s>Pumpkin</s> — no room this year</li></ul>
<p><i>Read more about raised beds before the soil arrives.</i></p>`,
    };
    const n4 = {
        id: 'seed-4', notebookId: null, pinned: false,
        createdAt: t0 + 86400000 * 3, updatedAt: Date.now() - 86400000 * 2,
        trashedAt: null, trashedFrom: null, tags: ['draft'],
        body: `<p>A longish snippet to prove the list preview truncates nicely: it keeps the first hundred and fifty characters of the <b>visible text</b>, strips every tag, and never lets markup leak into the snippet or the search index. This sentence is deliberately a bit longer so the ellipsis shows up. Done.</p>`,
    };
    const app = S.getState();
    app.notebooks = [nbWork, nbLife];
    app.notes = [n1, n2, n3, n4];
    app.ui.view = { type: 'all' };
    app.ui.selectedNoteId = n1.id;
}
function start() {
    // 1. state: try storage, else seed
    const loaded = S.loadFromStorage();
    if (!loaded)
        seed();
    S.persistNow();
    // 2. static shell
    (0, view_1.buildView)();
    // 3. editor: toolbar wiring
    const ed = (0, editor_1.getEditor)();
    const e = (0, view_1.getViewEls)();
    const btnDefs = [
        ['bold', view_1.ICONS.bold], ['italic', view_1.ICONS.italic], ['underline', view_1.ICONS.underline],
        ['strike', view_1.ICONS.strike], ['code', view_1.ICONS.code],
        ['h1', view_1.ICONS.h1], ['h2', view_1.ICONS.h2], ['h3', view_1.ICONS.h3],
        ['ul', view_1.ICONS.ul], ['ol', view_1.ICONS.ol], ['checklist', view_1.ICONS.check],
        ['quote', view_1.ICONS.quote], ['hr', view_1.ICONS.hr],
    ];
    const btnMap = {};
    for (const [key, icon] of btnDefs) {
        const b = document.createElement('button');
        b.className = 'tb-btn';
        b.innerHTML = icon;
        b.title = titleFor(key);
        b.type = 'button';
        b.addEventListener('mousedown', (ev) => ev.preventDefault()); // keep editor focus
        b.addEventListener('click', () => act(key));
        e.toolbar.appendChild(b);
        btnMap[key] = b;
    }
    ed.setToolbarCallback((st) => {
        for (const key of Object.keys(btnMap)) {
            btnMap[key].classList.toggle('on', !!st[key]);
        }
    });
    function titleFor(key) {
        switch (key) {
            case 'bold': return 'Bold (Ctrl+B)';
            case 'italic': return 'Italic (Ctrl+I)';
            case 'underline': return 'Underline (Ctrl+U)';
            case 'strike': return 'Strikethrough (Ctrl+Shift+S)';
            case 'code': return 'Inline code (Ctrl+E)';
            case 'h1': return 'Heading 1';
            case 'h2': return 'Heading 2';
            case 'h3': return 'Heading 3';
            case 'ul': return 'Bulleted list';
            case 'ol': return 'Numbered list';
            case 'checklist': return 'Checklist';
            case 'quote': return 'Quote';
            case 'hr': return 'Divider';
            default: return key;
        }
    }
    function act(key) {
        switch (key) {
            case 'bold':
                ed.applyInline('b');
                break;
            case 'italic':
                ed.applyInline('i');
                break;
            case 'underline':
                ed.applyInline('u');
                break;
            case 'strike':
                ed.applyInline('s');
                break;
            case 'code':
                ed.applyInline('code');
                break;
            case 'h1':
                ed.setBlock('H1');
                break;
            case 'h2':
                ed.setBlock('H2');
                break;
            case 'h3':
                ed.setBlock('H3');
                break;
            case 'ul':
                ed.toggleList('ul');
                break;
            case 'ol':
                ed.toggleList('ol');
                break;
            case 'checklist':
                ed.toggleChecklist();
                break;
            case 'quote':
                ed.setBlock('BLOCKQUOTE');
                break;
            case 'hr':
                ed.insertHR();
                break;
        }
    }
    // 4. editor extras: checkbox clicks + Ctrl+E code shortcut
    e.editorRoot.addEventListener('click', (ev) => {
        const t = ev.target;
        if (t.classList && t.classList.contains('ck-cb')) {
            ev.preventDefault();
            const li = t.closest('li[data-check]');
            if (li) {
                ed.toggleCheck(li);
                ev.stopPropagation();
            }
        }
    });
    e.editorRoot.addEventListener('keydown', (ev) => {
        const mod = ev.metaKey || ev.ctrlKey;
        if (mod && !ev.altKey && ev.key.toLowerCase() === 'e') {
            ev.preventDefault();
            ed.applyInline('code');
        }
    });
    // 5. render loop
    S.subscribe(() => {
        (0, view_1.renderRoot)();
        const ch = S.lastChange();
        if (ch.kind === 'note' || ch.kind === 'boot')
            (0, view_1.flashSaved)();
    });
    (0, view_1.renderRoot)();
    // 6. window-level niceties
    window.addEventListener('beforeunload', () => S.persistNow());
    document.addEventListener('keydown', (ev) => {
        const mod = ev.metaKey || ev.ctrlKey;
        if (mod && ev.key.toLowerCase() === 'k') {
            ev.preventDefault();
            e.searchInput.focus();
            e.searchInput.select();
        }
    });
    // expose for automated probes
    window.Notely = {
        store: S,
        editor: ed,
        sanitize: sanitize_1.sanitizeHTML,
        getState: S.getState,
    };
}
start();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.C = void 0;
// ============================================================
// TUNABLE CONSTANTS — adjust these to change the app's feel.
// ============================================================
exports.C = {
    STORAGE_KEY: 'notely.state.v1',
    /** Debounce (ms) between the last keystroke and the write to localStorage. */
    AUTOSAVE_MS: 700,
    /** Characters shown in a note-list snippet. */
    SNIPPET_LEN: 150,
    /** Max length of a derived note title. */
    TITLE_LEN: 64,
    /** Undo/redo history depth (snapshots). */
    UNDO_LIMIT: 200,
    /** Keystrokes inside this window (ms) merge into a single undo step. */
    UNDO_TYPING_GAP_MS: 800,
    SIDEBAR: { min: 210, max: 400, def: 256 },
    LIST: { min: 250, max: 560, def: 324 },
    FONT_UI: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`,
    FONT_READ: `"Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Charter, Georgia, "Times New Roman", serif`,
    FONT_MONO: `ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace`,
    /** Full theme palettes; injected as CSS variables at startup.
     *  Keys are CSS variable names without the dash prefix. */
    PALETTE: {
        light: {
            'bg-app': '#f7f5f2', 'bg-sidebar': '#f0ede8', 'bg-list': '#f4f1ec', 'bg-editor': '#fdfcfa',
            'tx-1': '#2b2723', 'tx-2': '#6e675e', 'tx-3': '#a49a8d',
            border: '#e4e0d7', 'border-soft': '#ece8e1',
            hover: 'rgba(43,39,35,0.05)', active: 'rgba(168,86,46,0.10)',
            accent: '#a8562e', 'accent-ink': '#8f4a27', 'accent-soft': 'rgba(168,86,46,0.12)',
            mark: '#fbe58f', 'mark-ink': '#4a3d10',
            shadow: '0 6px 24px rgba(43,39,35,0.16)',
        },
        dark: {
            'bg-app': '#1e1b18', 'bg-sidebar': '#262220', 'bg-list': '#221f1c', 'bg-editor': '#2a2622',
            'tx-1': '#ece7df', 'tx-2': '#a89f93', 'tx-3': '#6f675d',
            border: '#3b352f', 'border-soft': '#322d28',
            hover: 'rgba(236,231,223,0.06)', active: 'rgba(208,138,90,0.16)',
            accent: '#d08a5a', 'accent-ink': '#e0a878', 'accent-soft': 'rgba(208,138,90,0.16)',
            mark: 'rgba(240,215,120,0.28)', 'mark-ink': '#f0d98a',
            shadow: '0 6px 24px rgba(0,0,0,0.5)',
        },
    },
};
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// DATA MODEL — the single source of truth.
// Everything here is JSON-serializable; the DOM is a projection
// of this state, never the other way around.
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.sameView = sameView;
function sameView(a, b) {
    if (a.type !== b.type)
        return false;
    if (a.type === 'notebook' && b.type === 'notebook')
        return a.id === b.id;
    if (a.type === 'tag' && b.type === 'tag')
        return a.tag === b.tag;
    return true;
}
};

// ── module: src/utils.ts ──
__mods["src/utils.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// SMALL UTILITIES — ids, dom helpers, time formatting.
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.uid = uid;
exports.clamp = clamp;
exports.esc = esc;
exports.el = el;
exports.relTime = relTime;
exports.svgIcon = svgIcon;
function uid() {
    return Date.now().toString(36) + Math.random().toString(36).slice(2, 9);
}
function clamp(n, a, b) {
    return Math.min(b, Math.max(a, n));
}
function esc(s) {
    return s
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;');
}
function el(tag, opts = {}) {
    const n = document.createElement(tag);
    if (opts.cls)
        n.className = opts.cls;
    if (opts.text != null)
        n.textContent = opts.text;
    if (opts.html != null)
        n.innerHTML = opts.html;
    if (opts.title)
        n.title = opts.title;
    if (opts.attrs) {
        for (const [k, v] of Object.entries(opts.attrs)) {
            if (v == null)
                n.removeAttribute(k);
            else
                n.setAttribute(k, String(v));
        }
    }
    return n;
}
function relTime(ts) {
    const diff = Date.now() - ts;
    const m = Math.floor(diff / 60000);
    if (m < 1)
        return 'just now';
    if (m < 60)
        return m + 'm ago';
    const h = Math.floor(m / 60);
    if (h < 24)
        return h + 'h ago';
    const days = Math.floor(h / 24);
    if (days === 1)
        return 'yesterday';
    if (days < 7)
        return days + 'd ago';
    const d = new Date(ts);
    const sameYear = d.getFullYear() === new Date().getFullYear();
    return d.toLocaleDateString(undefined, {
        month: 'short',
        day: 'numeric',
        ...(sameYear ? {} : { year: 'numeric' }),
    });
}
function svgIcon(path, size = 16, sw = 1.8) {
    return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
}
};

// ── module: src/sanitize.ts ──
__mods["src/sanitize.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// SANITIZER — the only gate through which HTML enters or
// leaves the model. Pasting and every editor commit pass
// through here, so stored content is always a safe subset:
//
//   blocks:   p, h1, h2, h3, ul, ol, li (checklist rows are
//             li[data-check]), blockquote, hr
//   inline:   b, strong, i, em, u, s, strike, code, label, span
//   special:  input[type=checkbox] (only inside checklist rows)
//   allowed attrs: data-check on li/div
//
// Everything else (scripts, styles, foreign markup, attributes,
// event handlers) is stripped; visible text inside it is kept.
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.sanitizeHTML = sanitizeHTML;
exports.normalizeHTML = normalizeHTML;
exports.extractText = extractText;
exports.deriveTitle = deriveTitle;
const BLOCK_TAGS = new Set(['P', 'H1', 'H2', 'H3', 'UL', 'OL', 'LI', 'BLOCKQUOTE', 'HR', 'DIV']);
const INLINE_TAGS = new Set(['B', 'STRONG', 'I', 'EM', 'U', 'S', 'STRIKE', 'CODE', 'LABEL', 'SPAN']);
const ALLOWED = new Set([
    'P', 'H1', 'H2', 'H3', 'UL', 'OL', 'LI', 'BLOCKQUOTE', 'HR', 'DIV',
    'B', 'STRONG', 'I', 'EM', 'U', 'S', 'STRIKE', 'CODE', 'LABEL', 'SPAN',
]);
const DANGEROUS = new Set([
    'SCRIPT', 'STYLE', 'IFRAME', 'OBJECT', 'EMBED', 'LINK', 'META', 'SVG',
    'MATH', 'FORM', 'TEXTAREA', 'SELECT', 'BUTTON', 'VIDEO', 'AUDIO',
    'NOSCRIPT', 'TEMPLATE', 'IMG', 'BASE', 'SOURCE', 'TRACK',
]);
/** Sanitize an HTML string. Returns a safe HTML string. */
function sanitizeHTML(dirty) {
    if (!dirty)
        return '';
    const tpl = document.createElement('template');
    tpl.innerHTML = dirty;
    sanitizeNode(tpl.content);
    return tpl.innerHTML;
}
function sanitizeNode(node) {
    // Depth-first: process children first so removals are stable.
    for (const child of Array.from(node.childNodes))
        sanitizeNode(child);
    if (node.nodeType !== Node.ELEMENT_NODE)
        return;
    const elem = node;
    const tag = elem.tagName;
    if (DANGEROUS.has(tag)) {
        elem.remove();
        return;
    }
    if (tag === 'BR') {
        while (elem.hasAttributes())
            elem.removeAttribute(elem.attributes[0].name);
        return;
    }
    if (tag === 'INPUT') {
        const isCheckbox = (elem.getAttribute('type') ?? '').toLowerCase() === 'checkbox';
        while (elem.hasAttributes())
            elem.removeAttribute(elem.attributes[0].name);
        if (!isCheckbox) {
            elem.remove();
            return;
        }
        elem.setAttribute('type', 'checkbox');
        return;
    }
    if (!ALLOWED.has(tag)) {
        // Unknown element: replace with its (already sanitized) children.
        const parent = elem.parentNode;
        if (!parent)
            return;
        while (elem.firstChild)
            parent.insertBefore(elem.firstChild, elem);
        elem.remove();
        return;
    }
    // Strip every attribute; re-apply the single whitelisted one.
    const check = (tag === 'LI' || tag === 'DIV') ? elem.dataset.check : null;
    while (elem.hasAttributes())
        elem.removeAttribute(elem.attributes[0].name);
    if (check === 'true' || check === 'false')
        elem.dataset.check = check;
}
/* ---------------------------------------------------------- */
/* structural normalization                                    */
/* ---------------------------------------------------------- */
function isBlock(n) {
    return n.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(n.tagName);
}
function endsWithBlockOrBr(n) {
    const last = n.lastChild;
    if (!last)
        return false;
    if (isBlock(last))
        return true;
    if (last.nodeType === Node.ELEMENT_NODE && last.tagName === 'BR')
        return true;
    return false;
}
/**
 * Structural normalization: browsers collapse empty blocks and merge
 * lone text runs, which silently breaks markdown shortcuts, Enter
 * behavior and persistence. This enforces a canonical shape:
 *  - content is always >= 1 block element
 *  - bare text inside list/quote/root becomes a <p> (LI keeps raw text)
 *  - blocks can park a caret (trailing <br> when empty)
 */
function normalizeHTML(html) {
    if (!html || !html.trim())
        return '<p><br></p>';
    const tpl = document.createElement('template');
    tpl.innerHTML = html;
    const root = tpl.content;
    normalizeNode(root);
    if (!Array.from(root.children).some(c => isBlock(c))) {
        const p = document.createElement('p');
        while (root.firstChild)
            p.appendChild(root.firstChild);
        if (!p.textContent && !p.querySelector('br'))
            p.appendChild(document.createElement('br'));
        root.appendChild(p);
    }
    return tpl.innerHTML;
}
function normalizeNode(node) {
    const parentTag = node.tagName ?? '';
    const children = Array.from(node.childNodes);
    // containers that may only hold blocks: bare text there must become <p>
    const wrapParents = new Set(['UL', 'OL', 'BLOCKQUOTE', 'BODY', '#DOCUMENT_FRAGMENT', '']);
    for (const child of children) {
        if (child.nodeType === Node.TEXT_NODE) {
            const txt = child.data;
            if (parentTag === 'LI') {
                // bare text inside an <li> is valid content
                if (!/\S/.test(txt))
                    child.remove();
                continue;
            }
            if (!/\S/.test(txt)) {
                // whitespace: strip between blocks / in containers, keep inside inlines
                if (wrapParents.has(parentTag))
                    child.remove();
                continue;
            }
            if (wrapParents.has(parentTag)) {
                const p = document.createElement('p');
                node.insertBefore(p, child);
                p.appendChild(child);
                continue;
            }
            // text inside P / H* / DIV / inlines is legal — leave it alone
        }
        else if (child.nodeType === Node.ELEMENT_NODE) {
            const e = child;
            if (isBlock(e)) {
                normalizeNode(e);
            }
            else if (e.tagName === 'INPUT' || e.tagName === 'BR') {
                // keep as-is
            }
            else if (INLINE_TAGS.has(e.tagName)) {
                // hoist blocks smuggled inside an inline, out to after it
                const smuggled = Array.from(e.childNodes).filter(k => k.nodeType === Node.ELEMENT_NODE && isBlock(k));
                for (const k of smuggled) {
                    const next = e.nextSibling;
                    e.parentNode?.insertBefore(k, next);
                }
            }
        }
    }
    // trailing-br guarantees so a caret can always park
    const tag = node.tagName ?? '';
    if (tag === 'LI' && !endsWithBlockOrBr(node)) {
        node.appendChild(document.createElement('br'));
    }
    if ((tag === 'DIV' || tag === 'LI') && node.dataset.check !== undefined && !endsWithBlockOrBr(node)) {
        node.appendChild(document.createElement('br'));
    }
    if ((tag === 'UL' || tag === 'OL') && node.childNodes.length === 0) {
        node.remove(); // empty list is meaningless
        return;
    }
    if (BLOCK_TAGS.has(tag) && tag !== 'LI' && tag !== 'DIV' && tag !== 'BLOCKQUOTE' && node.childNodes.length === 0) {
        node.appendChild(document.createElement('br'));
    }
    if (tag === 'BLOCKQUOTE' && !Array.from(node.children).some(c => isBlock(c))) {
        const p = document.createElement('p');
        p.appendChild(document.createElement('br'));
        node.appendChild(p);
    }
}
/* ---------------------------------------------------------- */
/* text extraction (search + snippets)                         */
/* ---------------------------------------------------------- */
/**
 * Pure text extraction from stored HTML — feeds search and snippets.
 * Tags vanish; only visible characters remain (plus a space between
 * blocks), so a search for "b" or "div" can never match a tag name.
 */
function extractText(html) {
    if (!html)
        return '';
    const tpl = document.createElement('template');
    tpl.innerHTML = html;
    const out = [];
    const walk = (node) => {
        for (const child of Array.from(node.childNodes)) {
            if (child.nodeType === Node.TEXT_NODE) {
                out.push(child.data);
            }
            else if (child.nodeType === Node.ELEMENT_NODE) {
                const tag = child.tagName;
                if (tag === 'BR') {
                    out.push(' ');
                    continue;
                }
                if (tag === 'INPUT')
                    continue; // checkboxes have no text
                walk(child);
                if (isBlock(child))
                    out.push(' ');
            }
        }
    };
    walk(tpl.content);
    return out.join('').replace(/[ \t]+/g, ' ').replace(/ ?\s ?/g, ' ').trim();
}
/** First text line of a note — used for derived titles. */
function deriveTitle(html) {
    const t = extractText(html);
    const line = t.split(/\s{2,}|\n/)[0] || '';
    return line.slice(0, 64);
}
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// STATE — the single source of truth: an App object plus
// pure mutators. The store emits 'change' events; the view
// layer listens, decides what changed (via last-change kind)
// and re-renders only what's needed. Persistence: debounced
// write-through to localStorage.
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.getState = void 0;
exports.subscribe = subscribe;
exports.lastChange = lastChange;
exports.emitChange = emitChange;
exports.persistNow = persistNow;
exports.loadFromStorage = loadFromStorage;
exports.createNotebook = createNotebook;
exports.renameNotebook = renameNotebook;
exports.deleteNotebook = deleteNotebook;
exports.toggleNotebookCollapsed = toggleNotebookCollapsed;
exports.noteById = noteById;
exports.createNote = createNote;
exports.updateNoteContent = updateNoteContent;
exports.duplicateNote = duplicateNote;
exports.trashNote = trashNote;
exports.restoreNote = restoreNote;
exports.destroyNote = destroyNote;
exports.moveNote = moveNote;
exports.setPinned = setPinned;
exports.setNoteTags = setNoteTags;
exports.visibleNotes = visibleNotes;
exports.setView = setView;
exports.selectNote = selectNote;
exports.setSort = setSort;
exports.setTheme = setTheme;
exports.setSidebarOpen = setSidebarOpen;
exports.setPaneWidth = setPaneWidth;
exports.commitLayout = commitLayout;
const constants_1 = require("./constants");
const types_1 = require("./types");
const utils_1 = require("./utils");
const sanitize_1 = require("./sanitize");
function titleOf(n) {
    return (0, sanitize_1.deriveTitle)(n.body) || 'Untitled';
}
const store = {
    app: blank(),
    last: { kind: 'boot' },
    persistScheduled: false,
    version: 0,
};
function blank() {
    return {
        notebooks: [],
        notes: [],
        ui: {
            theme: 'light',
            sort: 'modified',
            view: { type: 'all' },
            selectedNoteId: null,
            sidebarOpen: true,
            sidebarW: constants_1.C.SIDEBAR.def,
            listW: constants_1.C.LIST.def,
        },
    };
}
const getState = () => store.app;
exports.getState = getState;
const listeners = new Set();
function subscribe(fn) {
    listeners.add(fn);
    return () => { listeners.delete(fn); };
}
function emit(kind, noteId) {
    store.last = { kind, noteId };
    store.version++;
    for (const fn of listeners)
        fn();
}
function lastChange() {
    return store.last;
}
/** For external modules (view/main) to trigger a full re-render. */
function emitChange(kind, noteId) {
    emit(kind, noteId);
}
/* ---------------------------------------------------------- */
/* persistence                                                */
/* ---------------------------------------------------------- */
function persistNow() {
    try {
        localStorage.setItem(constants_1.C.STORAGE_KEY, JSON.stringify(store.app));
    }
    catch {
        /* storage full or unavailable — keep running in memory */
    }
}
function schedulePersist() {
    if (store.persistScheduled)
        return;
    store.persistScheduled = true;
    setTimeout(() => {
        store.persistScheduled = false;
        persistNow();
    }, constants_1.C.AUTOSAVE_MS);
}
function loadFromStorage() {
    let raw = null;
    try {
        raw = localStorage.getItem(constants_1.C.STORAGE_KEY);
    }
    catch {
        raw = null;
    }
    if (!raw)
        return false;
    try {
        const data = JSON.parse(raw);
        if (!data || !Array.isArray(data.notes) || !Array.isArray(data.notebooks))
            return false;
        store.app = {
            notebooks: data.notebooks.map(n => ({
                id: String(n.id ?? (0, utils_1.uid)()),
                name: String(n.name ?? 'Untitled'),
                collapsed: !!n.collapsed,
            })),
            notes: data.notes.map(n => ({
                id: String(n.id ?? (0, utils_1.uid)()),
                notebookId: typeof n.notebookId === 'string' ? n.notebookId : null,
                body: (0, sanitize_1.sanitizeHTML)(String(n.body ?? '')),
                tags: Array.isArray(n.tags) ? n.tags.map(t => String(t)).filter(Boolean).slice(0, 24) : [],
                pinned: !!n.pinned,
                createdAt: Number(n.createdAt) || Date.now(),
                updatedAt: Number(n.updatedAt) || Date.now(),
                trashedAt: typeof n.trashedAt === 'number' ? n.trashedAt : null,
                trashedFrom: typeof n.trashedFrom === 'string' ? n.trashedFrom : null,
            })),
            ui: {
                theme: data.ui?.theme === 'dark' ? 'dark' : 'light',
                sort: ['modified', 'created', 'alpha'].includes(data.ui?.sort)
                    ? data.ui.sort : 'modified',
                view: normalizeView(data.ui?.view),
                selectedNoteId: typeof data.ui?.selectedNoteId === 'string' ? data.ui.selectedNoteId : null,
                sidebarOpen: data.ui?.sidebarOpen !== false,
                sidebarW: clampNum(data.ui?.sidebarW, constants_1.C.SIDEBAR.min, constants_1.C.SIDEBAR.max, constants_1.C.SIDEBAR.def),
                listW: clampNum(data.ui?.listW, constants_1.C.LIST.min, constants_1.C.LIST.max, constants_1.C.LIST.def),
            },
        };
        return true;
    }
    catch {
        return false;
    }
}
function normalizeView(v) {
    if (v && typeof v === 'object') {
        const o = v;
        if (o.type === 'notebook' && typeof o.id === 'string')
            return { type: 'notebook', id: o.id };
        if (o.type === 'trash')
            return { type: 'trash' };
        if (o.type === 'tag' && typeof o.tag === 'string')
            return { type: 'tag', tag: o.tag };
    }
    return { type: 'all' };
}
function clampNum(v, min, max, def) {
    return typeof v === 'number' && isFinite(v) ? Math.min(max, Math.max(min, v)) : def;
}
/* ---------------------------------------------------------- */
/* notebook ops                                               */
/* ---------------------------------------------------------- */
function createNotebook(name) {
    const nb = { id: (0, utils_1.uid)(), name: name.trim() || 'Untitled', collapsed: false };
    store.app.notebooks.push(nb);
    store.app.ui.view = { type: 'notebook', id: nb.id };
    emit('notebook');
    return nb;
}
function renameNotebook(id, name) {
    const nb = store.app.notebooks.find(n => n.id === id);
    if (nb) {
        nb.name = name.trim() || nb.name;
        emit('notebook');
    }
}
function deleteNotebook(id) {
    const i = store.app.notebooks.findIndex(n => n.id === id);
    if (i < 0)
        return;
    store.app.notebooks.splice(i, 1);
    for (const note of store.app.notes) {
        if (note.notebookId === id && !note.trashedAt) {
            note.notebookId = null; // falls back to "All Notes"
        }
    }
    if ((0, types_1.sameView)(store.app.ui.view, { type: 'notebook', id })) {
        store.app.ui.view = { type: 'all' };
    }
    emit('notebook');
}
function toggleNotebookCollapsed(id) {
    const nb = store.app.notebooks.find(n => n.id === id);
    if (nb) {
        nb.collapsed = !nb.collapsed;
        emit('notebook');
    }
}
/* ---------------------------------------------------------- */
/* note ops                                                   */
/* ---------------------------------------------------------- */
function noteById(id) {
    if (!id)
        return null;
    return store.app.notes.find(n => n.id === id) ?? null;
}
function createNote(notebookId, opts = {}) {
    const now = Date.now();
    const note = {
        id: (0, utils_1.uid)(),
        notebookId,
        body: (0, sanitize_1.sanitizeHTML)(opts.html ?? '<p><br></p>'),
        tags: [...(opts.tags ?? [])],
        pinned: false,
        createdAt: now,
        updatedAt: now,
        trashedAt: null,
        trashedFrom: null,
    };
    store.app.notes.push(note);
    store.app.ui.selectedNoteId = note.id;
    emit('note');
    return note;
}
function updateNoteContent(id, html, mark) {
    const note = noteById(id);
    if (!note)
        return;
    note.body = html;
    if (mark)
        note.updatedAt = Date.now();
    emit('note', id);
    if (store.last.kind === 'note')
        schedulePersist();
}
function duplicateNote(id) {
    const src = noteById(id);
    if (!src)
        return null;
    const now = Date.now();
    const copy = {
        ...src,
        id: (0, utils_1.uid)(),
        body: src.body,
        tags: [...src.tags],
        pinned: false,
        createdAt: now,
        updatedAt: now,
        trashedAt: null,
        trashedFrom: null,
    };
    store.app.notes.push(copy);
    store.app.ui.selectedNoteId = copy.id;
    emit('note');
    return copy;
}
function trashNote(id) {
    const note = noteById(id);
    if (!note || note.trashedAt)
        return;
    note.trashedFrom = note.notebookId;
    note.trashedAt = Date.now();
    note.pinned = false;
    emit('note');
}
function restoreNote(id) {
    const note = noteById(id);
    if (!note || !note.trashedAt)
        return;
    note.trashedAt = null;
    // Original notebook if it still exists, else null (All Notes).
    const nb = store.app.notebooks.find(n => n.id === note.trashedFrom);
    note.notebookId = nb ? nb.id : null;
    note.trashedFrom = null;
    emit('note');
}
function destroyNote(id) {
    const i = store.app.notes.findIndex(n => n.id === id);
    if (i < 0)
        return;
    store.app.notes.splice(i, 1);
    if (store.app.ui.selectedNoteId === id)
        store.app.ui.selectedNoteId = null;
    emit('note');
}
function moveNote(id, notebookId) {
    const note = noteById(id);
    if (!note || note.trashedAt || note.notebookId === notebookId)
        return;
    note.notebookId = notebookId;
    note.updatedAt = Date.now();
    emit('note');
}
function setPinned(id, pinned) {
    const note = noteById(id);
    if (!note)
        return;
    note.pinned = pinned;
    emit('note');
}
function setNoteTags(id, tags) {
    const note = noteById(id);
    if (!note)
        return;
    note.tags = tags;
    emit('note');
}
/** Notes visible in the current view, sorted (pinned first). */
function visibleNotes() {
    const app = store.app;
    const v = app.ui.view;
    let pool = app.notes;
    if (v.type === 'trash')
        pool = pool.filter(n => n.trashedAt);
    else if (v.type === 'notebook')
        pool = pool.filter(n => !n.trashedAt && n.notebookId === v.id);
    else if (v.type === 'tag')
        pool = pool.filter(n => !n.trashedAt && n.tags.includes(v.tag));
    else
        pool = pool.filter(n => !n.trashedAt);
    const sortKey = app.ui.sort;
    return [...pool].sort((a, b) => {
        if (!!a.pinned !== !!b.pinned)
            return a.pinned ? -1 : 1;
        if (sortKey === 'created')
            return b.createdAt - a.createdAt;
        if (sortKey === 'alpha') {
            const ta = titleOf(a).toLowerCase();
            const tb = titleOf(b).toLowerCase();
            return ta < tb ? -1 : ta > tb ? 1 : b.updatedAt - a.updatedAt;
        }
        return b.updatedAt - a.updatedAt;
    });
}
function setView(view) {
    if (!(0, types_1.sameView)(store.app.ui.view, view)) {
        store.app.ui.view = view;
        emit('view');
    }
}
function selectNote(id) {
    if (store.app.ui.selectedNoteId !== id) {
        store.app.ui.selectedNoteId = id;
        emit('view');
    }
}
function setSort(sort) {
    store.app.ui.sort = sort;
    emit('view');
}
/* ---------------------------------------------------------- */
/* ui / layout ops                                            */
/* ---------------------------------------------------------- */
function setTheme(theme) {
    store.app.ui.theme = theme;
    emit('layout');
}
function setSidebarOpen(open) {
    store.app.ui.sidebarOpen = open;
    emit('layout');
}
function setPaneWidth(which, w) {
    if (which === 'sidebar')
        store.app.ui.sidebarW = w;
    else
        store.app.ui.listW = w;
    emit('layout');
}
function commitLayout() {
    persistNow();
}
};

// ── module: src/editor.ts ──
__mods["src/editor.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// EDITOR — contenteditable engine.
//  * custom undo/redo (snapshot history with caret restore)
//  * markdown shortcuts: "# " "- " "[] " "> " at line start
//  * Enter: continues lists, exits empty items, splits blocks
//  * Backspace: merges blocks, exits lists/headings/quotes
//  * paste sanitized to the supported subset
//  * toolbar state mirrors the selection
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.blockOf = blockOf;
exports.getEditor = getEditor;
const constants_1 = require("./constants");
const state_1 = require("./state");
const sanitize_1 = require("./sanitize");
const BLOCK_TAGS = new Set(['P', 'H1', 'H2', 'H3', 'UL', 'OL', 'LI', 'BLOCKQUOTE', 'DIV']);
const INLINE_SET = new Set(['B', 'STRONG', 'I', 'EM', 'U', 'S', 'STRIKE', 'CODE']);
/** Non-null-or-range selection helper. */
function sel() {
    const s = getSelection();
    if (!s || !s.rangeCount)
        return null;
    return s;
}
class EditorEngine {
    constructor(ed) {
        this.noteId = null;
        this.hist = [];
        this.histIdx = -1;
        this.baseHtml = '';
        this.baseSel = null;
        this.lastPushAt = 0;
        this.loading = false;
        this.composing = false;
        this.onToolbar = null;
        this.ed = ed;
        try {
            document.execCommand('defaultParagraphSeparator', false, 'p');
            document.execCommand('styleWithCSS', false, 'false');
        }
        catch { /* execCommand availability varies; fallbacks cover it */ }
        ed.addEventListener('input', () => this.onInput());
        ed.addEventListener('keydown', (e) => this.onKeyDown(e));
        ed.addEventListener('keyup', () => this.refreshToolbar());
        ed.addEventListener('click', () => this.refreshToolbar());
        ed.addEventListener('paste', (e) => this.onPaste(e));
        ed.addEventListener('drop', (e) => e.preventDefault());
        ed.addEventListener('compositionstart', () => { this.composing = true; });
        ed.addEventListener('compositionend', () => { this.composing = false; this.afterManual(); });
        document.addEventListener('selectionchange', () => {
            if (this.focusedInEditor())
                this.refreshToolbar();
        });
    }
    /* ------------------------------------------------------ */
    /* public API                                              */
    /* ------------------------------------------------------ */
    setToolbarCallback(fn) {
        this.onToolbar = fn;
    }
    getHTML() {
        return this.ed.innerHTML;
    }
    focus() {
        this.ed.focus();
    }
    isActive() {
        return document.activeElement === this.ed;
    }
    /** Load a note into the editor. keepSelection restores caret. */
    load(noteId, html, keepSelection) {
        // No-op guard: the editor already holds exactly this content (e.g. a
        // commit just round-tripped). Skipping here keeps undo history intact.
        if (this.noteId === noteId && this.ed.innerHTML === html) {
            return;
        }
        const sel = keepSelection && this.noteId === noteId ? captureSelection(this.ed) : null;
        this.loading = true;
        this.noteId = noteId;
        this.ed.innerHTML = (0, sanitize_1.normalizeHTML)(html) || '<p><br></p>';
        this.loading = false;
        this.hist = [{ html: this.ed.innerHTML, sel: captureSelection(this.ed) }];
        this.histIdx = 0;
        this.baseHtml = this.ed.innerHTML;
        this.baseSel = captureSelection(this.ed);
        this.lastPushAt = 0; // first input always starts a fresh undo burst
        if (sel)
            restoreSelection(this.ed, sel);
        else if (keepSelection)
            placeCaretStartIn(this.ed);
        this.refreshToolbar();
    }
    clear() {
        this.noteId = null;
        this.ed.innerHTML = '<p><br></p>';
        this.hist = [];
        this.histIdx = -1;
        this.refreshToolbar();
    }
    /* ------------------------------------------------------ */
    /* commit / history                                        */
    /* ------------------------------------------------------ */
    /** Sanitize + normalize live DOM -> model. Call after any mutation. */
    commit(mark = true) {
        if (!this.noteId || this.loading)
            return;
        const norm = (0, sanitize_1.normalizeHTML)(this.ed.innerHTML);
        if (norm !== this.ed.innerHTML) {
            const s = captureSelection(this.ed);
            this.ed.innerHTML = norm;
            restoreSelection(this.ed, s);
        }
        this.baseHtml = this.ed.innerHTML;
        this.baseSel = captureSelection(this.ed);
        (0, state_1.updateNoteContent)(this.noteId, norm, mark);
    }
    /** A programmatic mutation just happened: record it + sync model. */
    afterManual() {
        this.pushState();
        this.commit(true);
    }
    /** Record the CURRENT (post-mutation) DOM as a new history state. */
    pushState() {
        if (this.loading)
            return;
        const snap = { html: this.ed.innerHTML, sel: captureSelection(this.ed) };
        const top = this.hist[this.histIdx];
        if (top && top.html === snap.html) {
            this.baseHtml = snap.html;
            this.baseSel = snap.sel;
            return; // no change
        }
        this.hist.splice(this.histIdx + 1);
        this.hist.push(snap);
        if (this.hist.length > constants_1.C.UNDO_LIMIT)
            this.hist.shift();
        this.histIdx = this.hist.length - 1;
        this.lastPushAt = Date.now();
        this.baseHtml = snap.html;
        this.baseSel = snap.sel;
    }
    undo() {
        if (this.histIdx <= 0)
            return false;
        this.histIdx--;
        this.applySnapshot(this.hist[this.histIdx]);
        return true;
    }
    redo() {
        if (this.histIdx >= this.hist.length - 1)
            return false;
        this.histIdx++;
        this.applySnapshot(this.hist[this.histIdx]);
        return true;
    }
    applySnapshot(s) {
        this.loading = true;
        this.ed.innerHTML = s.html || '<p><br></p>';
        this.loading = false;
        this.baseHtml = s.html;
        this.baseSel = s.sel;
        this.lastPushAt = Date.now();
        restoreSelection(this.ed, s.sel);
        this.commit(false);
    }
    /* ------------------------------------------------------ */
    /* formatting commands                                     */
    /* ------------------------------------------------------ */
    applyInline(tag) {
        if (!this.focusedInEditor())
            this.focus();
        if (tag === 'code') {
            this.wrapSelectionInCode();
        }
        else {
            const ok = document.execCommand(tag, false);
            if (!ok)
                this.wrapSelection(tag === 'b' ? 'b' : tag);
        }
        this.afterManual();
    }
    wrapSelection(tag) {
        const s = sel();
        if (!s)
            return;
        const r = s.getRangeAt(0);
        const wrap = document.createElement(tag);
        const frag = r.extractContents();
        wrap.appendChild(frag);
        r.insertNode(wrap);
        const nr = document.createRange();
        nr.selectNodeContents(wrap);
        nr.collapse(false);
        s.removeAllRanges();
        s.addRange(nr);
    }
    wrapSelectionInCode() {
        this.wrapSelection('code');
    }
    setBlock(tag) {
        if (!this.focusedInEditor())
            this.focus();
        const block = blockOf(this.ed, caretNode());
        if (block && (block.tagName === 'LI' || blockIsCheck(block)))
            return; // lists own their block type
        document.execCommand('formatBlock', false, tag);
        this.afterManual();
    }
    toggleList(kind) {
        if (!this.focusedInEditor())
            this.focus();
        const block = blockOf(this.ed, caretNode());
        if (block && blockIsCheck(block))
            return;
        if (block && block.tagName === 'LI') {
            const list = block.parentElement;
            if (list && (list.tagName === 'UL' || list.tagName === 'OL')) {
                listToParagraphs(list);
                this.afterManual();
            }
            return;
        }
        document.execCommand(kind === 'ul' ? 'insertUnorderedList' : 'insertOrderedList', false);
        this.afterManual();
    }
    toggleChecklist() {
        if (!this.focusedInEditor())
            this.focus();
        const block = blockOf(this.ed, caretNode());
        if (block && blockIsCheck(block)) {
            // convert check row back to a paragraph
            const ul = block.parentElement;
            const p = document.createElement('p');
            const label = block.querySelector('.ck-text');
            if (label)
                p.appendChild(label.cloneNode(true));
            p.normalize();
            if (ul && ul.tagName === 'UL' && ul.children.length === 1)
                ul.replaceWith(p);
            else
                block.replaceWith(p);
            placeCaretEnd(p);
            this.afterManual();
            return;
        }
        const target = block ?? ensureParagraph(this.ed);
        const li = makeCheckRow(false);
        const textSpan = li.querySelector('.ck-text');
        const frag = document.createDocumentFragment();
        while (target.firstChild)
            frag.appendChild(target.firstChild);
        textSpan.appendChild(frag);
        if (!textSpan.textContent && textSpan.childNodes.length === 0) {
            textSpan.appendChild(document.createElement('br'));
        }
        if (target.tagName === 'LI' && target.parentElement?.tagName === 'UL') {
            target.replaceWith(li);
        }
        else {
            const ul = document.createElement('ul');
            ul.appendChild(li);
            target.replaceWith(ul);
        }
        placeCaretEnd(textSpan);
        this.afterManual();
    }
    insertHR() {
        if (!this.focusedInEditor())
            this.focus();
        document.execCommand('insertHorizontalRule', false);
        this.afterManual();
    }
    /** Toggle the checkbox of the row containing the caret. */
    toggleCheckAtCaret() {
        const block = blockOf(this.ed, caretNode());
        if (!block || !blockIsCheck(block))
            return;
        this.toggleCheck(block);
    }
    toggleCheck(li) {
        const cb = li.querySelector('.ck-cb');
        if (!cb)
            return;
        cb.checked = !cb.checked;
        li.dataset.check = cb.checked ? 'true' : 'false';
        this.afterManual();
    }
    /* ------------------------------------------------------ */
    /* key handling                                            */
    /* ------------------------------------------------------ */
    onInput() {
        if (this.composing)
            return;
        // A new burst (gap since last state > merge window) records the
        // post-input DOM as a fresh undo state.
        if (this.histIdx >= 0 && Date.now() - this.lastPushAt > constants_1.C.UNDO_TYPING_GAP_MS) {
            this.pushState();
            return;
        }
        // Continuous typing: keep baseline fresh, no new state.
        this.baseHtml = this.ed.innerHTML;
        this.baseSel = captureSelection(this.ed);
        this.commit(true);
    }
    onKeyDown(e) {
        const mod = e.metaKey || e.ctrlKey;
        if (mod && !e.altKey) {
            const k = e.key.toLowerCase();
            if (k === 'b') {
                e.preventDefault();
                this.applyInline('b');
                return;
            }
            if (k === 'i') {
                e.preventDefault();
                this.applyInline('i');
                return;
            }
            if (k === 'u') {
                e.preventDefault();
                this.applyInline('u');
                return;
            }
            if (k === 's') {
                e.preventDefault();
                this.applyInline('s');
                return;
            }
            if (k === 'z') {
                e.preventDefault();
                if (e.shiftKey)
                    this.redo();
                else
                    this.undo();
                return;
            }
            if (k === 'y') {
                e.preventDefault();
                this.redo();
                return;
            }
        }
        if (e.isComposing)
            return;
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            this.handleEnter();
            return;
        }
        if (e.key === 'Backspace') {
            if (this.handleBackspace(e))
                return;
        }
        if (e.key === 'Tab' && !e.shiftKey) {
            e.preventDefault();
            this.indentList(1);
            return;
        }
        if (e.key === 'Tab' && e.shiftKey) {
            e.preventDefault();
            this.indentList(-1);
            return;
        }
        if (e.key === ' ' && !mod && !e.shiftKey && !e.altKey) {
            this.tryMarkdownShortcut(e);
        }
    }
    /* -------- markdown shortcuts -------- */
    tryMarkdownShortcut(e) {
        const s = sel();
        if (!s)
            return;
        const r = s.getRangeAt(0);
        if (!r.collapsed)
            return;
        const anchor = r.startContainer;
        if (anchor.nodeType !== Node.TEXT_NODE)
            return;
        const node = anchor;
        const idx = r.startOffset;
        const before = node.data.slice(0, idx);
        let marker = '';
        if (idx >= 2 && before.slice(idx - 2, idx) === '[]')
            marker = '[]';
        else if (idx >= 1 && (before[idx - 1] === '#' || before[idx - 1] === '-' || before[idx - 1] === '>')) {
            marker = before[idx - 1];
        }
        if (!marker)
            return;
        const start = idx - marker.length;
        // Must be at the very start of the line: nothing but whitespace before.
        if (/\S/.test(before.slice(0, start)))
            return;
        const parent = node.parentElement;
        if (!parent || parent.tagName !== 'P')
            return;
        let prev = parent.previousSibling;
        while (prev) {
            if (prev.nodeType === Node.TEXT_NODE) {
                if (prev.textContent && prev.textContent.trim() !== '')
                    return;
            }
            else {
                return;
            }
            prev = prev.previousSibling;
        }
        // Convert now and consume the space that would be typed.
        e.preventDefault();
        const del = document.createRange();
        del.setStart(node, start);
        del.setEnd(node, idx);
        del.deleteContents();
        const rest = node.data.slice(idx); // text that already followed the marker
        if (marker === '#') {
            const h = document.createElement('H1');
            if (rest)
                h.textContent = rest;
            else
                h.appendChild(document.createElement('br'));
            parent.replaceWith(h);
            placeCaretEnd(h);
        }
        else if (marker === '-') {
            const li = document.createElement('LI');
            if (rest)
                li.textContent = rest;
            else
                li.appendChild(document.createElement('br'));
            const ul = document.createElement('UL');
            ul.appendChild(li);
            parent.replaceWith(ul);
            placeCaretEnd(li);
        }
        else if (marker === '[]') {
            const li = makeCheckRow(false);
            const span = li.querySelector('.ck-text');
            if (rest)
                span.textContent = rest;
            else
                span.appendChild(document.createElement('br'));
            const ul = document.createElement('UL');
            ul.appendChild(li);
            parent.replaceWith(ul);
            placeCaretEnd(span);
        }
        else if (marker === '>') {
            const q = document.createElement('BLOCKQUOTE');
            const p2 = document.createElement('P');
            if (rest)
                p2.textContent = rest;
            else
                p2.appendChild(document.createElement('br'));
            q.appendChild(p2);
            parent.replaceWith(q);
            placeCaretEnd(p2);
        }
        this.afterManual();
    }
    /* -------- Enter -------- */
    handleEnter() {
        const s = sel();
        if (!s)
            return;
        const r = s.getRangeAt(0);
        const block = blockOf(this.ed, r.startContainer);
        if (!block) {
            const p = ensureParagraph(this.ed);
            const np = document.createElement('P');
            np.appendChild(document.createElement('br'));
            p.after(np);
            placeCaretEnd(np);
            this.afterManual();
            return;
        }
        if (block.tagName === 'LI') {
            this.enterList(block, r);
            return;
        }
        if (blockIsCheck(block)) {
            this.enterCheckRow(block, r);
            return;
        }
        if (block.tagName === 'BLOCKQUOTE') {
            this.enterQuote(block, r);
            return;
        }
        if (block.tagName === 'HR') {
            const p = document.createElement('P');
            p.appendChild(document.createElement('br'));
            block.after(p);
            placeCaretEnd(p);
            this.afterManual();
            return;
        }
        // P or H*
        const [head, tail] = splitBlock(block, r);
        const tag = (block.tagName === 'P' ? 'P' : block.tagName);
        const newBlock = document.createElement(tag);
        fillOrBr(newBlock, tail);
        block.after(newBlock);
        ensureNotEmpty(head);
        placeCaretEnd(newBlock);
        this.afterManual();
    }
    exitListToParagraph(list, li) {
        if (list.children.length === 1) {
            const p = document.createElement('P');
            p.appendChild(document.createElement('br'));
            list.replaceWith(p);
            placeCaretEnd(p);
        }
        else {
            li.remove();
            const prevLi = li.previousElementSibling;
            if (prevLi)
                placeCaretEnd(prevLi);
        }
    }
    enterList(li, r) {
        const list = li.parentElement;
        if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL')) {
            this.enterPlainBlock(li, r);
            return;
        }
        if (liIsEmpty(li)) {
            // Enter on an empty item exits the list.
            this.exitListToParagraph(list, li);
            this.afterManual();
            return;
        }
        // Enter at the end (or middle) of a non-empty item continues the list.
        const [head, tail] = splitBlock(li, r);
        const newLi = document.createElement('LI');
        fillOrBr(newLi, tail);
        li.after(newLi);
        ensureNotEmpty(head);
        placeCaretEnd(newLi);
        this.afterManual();
    }
    enterPlainBlock(block, r) {
        const [head, tail] = splitBlock(block, r);
        const tag = (block.tagName === 'P' ? 'P' : block.tagName);
        const newBlock = document.createElement(tag);
        fillOrBr(newBlock, tail);
        block.after(newBlock);
        ensureNotEmpty(head);
        placeCaretEnd(newBlock);
        this.afterManual();
    }
    enterCheckRow(div, r) {
        const ul = div.parentElement;
        const label = div.querySelector('.ck-text');
        if (!ul || !label)
            return;
        const text = label.textContent ?? '';
        const atTextEnd = caretAtEndOf(r, label);
        if (text.trim() === '') {
            // empty row -> exit
            if (ul.children.length === 1) {
                const p = document.createElement('P');
                p.appendChild(document.createElement('br'));
                ul.replaceWith(p);
                placeCaretEnd(p);
            }
            else {
                div.remove();
                const prev = div.previousElementSibling;
                const prevText = (prev ? prev.querySelector('.ck-text') : null);
                if (prevText)
                    placeCaretEnd(prevText);
            }
            this.afterManual();
            return;
        }
        if (atTextEnd) {
            const newDiv = makeCheckRow(false);
            ul.insertBefore(newDiv, div.nextSibling);
            placeCaretEnd(newDiv.querySelector('.ck-text'));
        }
        else {
            const newDiv = makeCheckRow(false);
            const newSpan = newDiv.querySelector('.ck-text');
            const rng = document.createRange();
            rng.setStart(r.startContainer, r.startOffset);
            rng.setEnd(label, label.childNodes.length);
            const frag = rng.extractContents();
            newSpan.appendChild(frag);
            ul.insertBefore(newDiv, div.nextSibling);
            ensureNotEmptySpan(newSpan);
            placeCaretEnd(newSpan);
        }
        this.afterManual();
    }
    enterQuote(q, r) {
        const inner = q.querySelector('P, H1, H2, H3');
        if (inner) {
            const [head, tail] = splitBlock(inner, r);
            if (tailEmpty(tail)) {
                ensureNotEmpty(head);
                const p = document.createElement('P');
                p.appendChild(document.createElement('br'));
                q.after(p);
                placeCaretEnd(p);
            }
            else {
                const newP = document.createElement('P');
                fillOrBr(newP, tail);
                q.appendChild(newP);
                ensureNotEmpty(head);
                placeCaretEnd(newP);
            }
        }
        else {
            const p = document.createElement('P');
            p.appendChild(document.createElement('br'));
            q.after(p);
            placeCaretEnd(p);
        }
        this.afterManual();
    }
    /* -------- Backspace -------- */
    handleBackspace(e) {
        const s = sel();
        if (!s)
            return false;
        const r = s.getRangeAt(0);
        if (!r.collapsed)
            return false; // let default delete the selection
        const block = blockOf(this.ed, r.startContainer);
        if (!block)
            return false;
        const atBlockStart = (() => {
            const t = document.createRange();
            t.selectNodeContents(block);
            t.setEnd(r.startContainer, r.startOffset);
            return t.toString() === '';
        })();
        if (!atBlockStart)
            return false;
        // Caret at the very start of a block.
        if (block.parentElement && block.parentElement.tagName === 'BLOCKQUOTE') {
            e.preventDefault();
            const q = block.parentElement;
            const p = document.createElement('P');
            p.appendChild(document.createElement('br'));
            q.parentElement?.insertBefore(p, q);
            placeCaretEnd(p);
            this.afterManual();
            return true;
        }
        if (block.tagName === 'LI' || blockIsCheck(block)) {
            e.preventDefault();
            const list = block.parentElement;
            const prev = previousBlockSibling(block);
            if (prev && (prev.tagName === 'LI' || blockIsCheck(prev))) {
                mergeInto(prev, block);
            }
            else {
                const p = document.createElement('P');
                const frag = document.createDocumentFragment();
                if (block.tagName === 'LI') {
                    while (block.firstChild)
                        frag.appendChild(block.firstChild);
                }
                else {
                    const label = block.querySelector('.ck-text');
                    if (label)
                        frag.appendChild(label.cloneNode(true));
                }
                p.appendChild(frag);
                if (list && (list.tagName === 'UL' || list.tagName === 'OL') && list.children.length === 1) {
                    list.replaceWith(p);
                }
                else if (list && (list.tagName === 'UL' || list.tagName === 'OL')) {
                    list.parentElement?.insertBefore(p, list);
                    list.removeChild(block);
                }
                else {
                    block.replaceWith(p);
                }
                if (!p.textContent && p.childNodes.length === 0)
                    p.appendChild(document.createElement('br'));
                placeCaretStartIn(p);
            }
            this.afterManual();
            return true;
        }
        if (block.tagName === 'H1' || block.tagName === 'H2' || block.tagName === 'H3') {
            const prev = previousBlockSibling(block);
            if (!prev) {
                e.preventDefault();
                const p = document.createElement('P');
                while (block.firstChild)
                    p.appendChild(block.firstChild);
                block.replaceWith(p);
                placeCaretStartIn(p);
                this.afterManual();
                return true;
            }
        }
        const prev = previousBlockSibling(block);
        if (prev && prev.tagName !== 'BLOCKQUOTE' && prev.tagName !== 'HR') {
            e.preventDefault();
            mergeInto(prev, block);
            placeCaretEnd(prev);
            this.afterManual();
            return true;
        }
        return false;
    }
    /* -------- Tab indent -------- */
    indentList(dir) {
        const s = sel();
        if (!s)
            return;
        const block = blockOf(this.ed, s.getRangeAt(0).startContainer);
        if (!block || block.tagName !== 'LI')
            return;
        const list = block.parentElement;
        if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL'))
            return;
        if (dir === 1) {
            const targets = [];
            let cur = list.firstElementChild;
            while (cur && cur.tagName === 'LI') {
                targets.push(cur);
                if (cur === block)
                    break;
                cur = cur.nextElementSibling;
            }
            if (targets.length === 0)
                return;
            for (const li of targets) {
                li.appendChild(document.createElement(list.tagName === 'OL' ? 'OL' : 'UL'));
            }
            placeCaretEnd(block);
            this.afterManual();
        }
        else {
            if (block.parentElement !== list)
                return;
            const outerLi = list.parentElement;
            if (!outerLi || outerLi.tagName !== 'LI')
                return;
            outerLi.insertBefore(block, list);
            if (list.children.length === 0)
                list.remove();
            placeCaretEnd(block);
            this.afterManual();
        }
    }
    /* -------- paste -------- */
    onPaste(e) {
        e.preventDefault();
        const cd = e.clipboardData;
        if (!cd)
            return;
        const text = cd.getData('text/plain');
        const html = cd.getData('text/html');
        if (html && html.trim()) {
            const clean = (0, sanitize_1.sanitizeHTML)(html);
            if (clean.trim())
                document.execCommand('insertHTML', false, clean);
        }
        else if (text) {
            const block = blockOf(this.ed, caretNode());
            if (text.includes('\n') && block && (block.tagName === 'P' || blockIsCheck(block))) {
                const lines = text.split(/\r\n?|\n/);
                const fragHtml = lines.map(l => (l ? `<p>${escapeHTML(l)}</p>` : '<p><br></p>')).join('');
                document.execCommand('insertHTML', false, fragHtml);
            }
            else {
                document.execCommand('insertText', false, text);
            }
        }
        this.afterManual();
    }
    /* -------- toolbar state -------- */
    focusedInEditor() {
        const a = document.activeElement;
        return a === this.ed || (a instanceof HTMLElement && this.ed.contains(a));
    }
    refreshToolbar() {
        if (!this.onToolbar)
            return;
        const state = {
            bold: false, italic: false, underline: false, strike: false, code: false,
            h1: false, h2: false, h3: false, ul: false, ol: false, checklist: false, quote: false,
            active: false,
        };
        if (!this.focusedInEditor() || this.noteId == null) {
            this.onToolbar(state);
            return;
        }
        const s = sel();
        if (!s) {
            this.onToolbar(state);
            return;
        }
        const tags = new Set();
        for (const node of [s.anchorNode, s.focusNode]) {
            if (!node)
                continue;
            let n = node;
            while (n && n !== this.ed) {
                if (n.nodeType === Node.ELEMENT_NODE) {
                    const tag = n.tagName;
                    if (INLINE_SET.has(tag))
                        tags.add(tag);
                }
                n = n.parentElement;
            }
        }
        state.bold = tags.has('B') || tags.has('STRONG');
        state.italic = tags.has('I') || tags.has('EM');
        state.underline = tags.has('U');
        state.strike = tags.has('S') || tags.has('STRIKE');
        state.code = tags.has('CODE');
        const anchor = s.anchorNode || s.focusNode;
        const block = anchor ? blockOf(this.ed, anchor) : null;
        if (block) {
            state.h1 = block.tagName === 'H1';
            state.h2 = block.tagName === 'H2';
            state.h3 = block.tagName === 'H3';
            if (block.tagName === 'LI') {
                const list = block.parentElement;
                state.ul = list?.tagName === 'UL';
                state.ol = list?.tagName === 'OL';
            }
            state.checklist = blockIsCheck(block);
            state.quote = !!block.closest('BLOCKQUOTE');
        }
        state.active = true;
        this.onToolbar(state);
    }
}
// ============================================================
// helpers
// ============================================================
function caretNode() {
    const s = sel();
    return s ? s.getRangeAt(0).startContainer : document.createTextNode('');
}
function blockOf(ed, node) {
    let n = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
    while (n && n !== ed) {
        if (n.nodeType === Node.ELEMENT_NODE) {
            const tag = n.tagName;
            if (BLOCK_TAGS.has(tag))
                return n;
        }
        n = n.parentElement;
    }
    return null;
}
function blockIsCheck(el) {
    return el.tagName === 'LI' && el.dataset.check !== undefined;
}
function isCheckLi(el) {
    return el.tagName === 'LI' && el.dataset.check !== undefined;
}
function makeCheckRow(checked) {
    const li = document.createElement('li');
    li.dataset.check = checked ? 'true' : 'false';
    const label = document.createElement('label');
    label.className = 'ck-label';
    const cb = document.createElement('input');
    cb.type = 'checkbox';
    cb.className = 'ck-cb';
    cb.checked = checked;
    const box = document.createElement('span');
    box.className = 'ck-box';
    box.appendChild(cb);
    const span = document.createElement('span');
    span.className = 'ck-text';
    label.appendChild(box);
    label.appendChild(span);
    li.appendChild(label);
    return li;
}
function ensureParagraph(ed) {
    const last = ed.lastElementChild;
    if (!last || !BLOCK_TAGS.has(last.tagName) || isCheckLi(last)) {
        const p = document.createElement('P');
        p.appendChild(document.createElement('br'));
        ed.appendChild(p);
        return p;
    }
    return last;
}
function liIsEmpty(li) {
    const clone = li.cloneNode(true);
    for (const br of Array.from(clone.querySelectorAll('br')))
        br.remove();
    if (clone.querySelector('ul,ol'))
        return false;
    return (clone.textContent ?? '').trim() === '';
}
function tailEmpty(tail) {
    const text = tail.textContent ?? '';
    if (text.trim() !== '')
        return false;
    for (const n of Array.from(tail.childNodes)) {
        if (n.nodeType === Node.ELEMENT_NODE && n.tagName !== 'BR')
            return false;
    }
    return true;
}
function splitBlock(block, r) {
    const full = document.createRange();
    full.setStart(r.startContainer, r.startOffset);
    full.selectNodeContents(block);
    full.collapse(false); // caret -> end of block
    const tail = full.extractContents();
    return [block, tail];
}
function fillOrBr(el, frag) {
    el.appendChild(frag);
    if (!el.textContent && el.querySelectorAll('br').length === 0) {
        el.appendChild(document.createElement('br'));
    }
}
function ensureNotEmpty(el) {
    const text = el.textContent ?? '';
    if (text.trim() !== '' || el.querySelector('ul,ol,div[data-check]'))
        return;
    if (!el.querySelector('br'))
        el.appendChild(document.createElement('br'));
}
function ensureNotEmptySpan(span) {
    if (!span.textContent && !span.querySelector('br'))
        span.appendChild(document.createElement('br'));
}
function previousBlockSibling(block) {
    let sib = block.previousElementSibling;
    while (sib) {
        if (BLOCK_TAGS.has(sib.tagName))
            return sib;
        sib = sib.previousElementSibling;
    }
    const parent = block.parentElement;
    if (parent && (parent.tagName === 'UL' || parent.tagName === 'OL' || parent.tagName === 'BLOCKQUOTE')) {
        let sib2 = parent.previousElementSibling;
        while (sib2) {
            if (BLOCK_TAGS.has(sib2.tagName))
                return sib2;
            sib2 = sib2.previousElementSibling;
        }
    }
    return null;
}
function mergeInto(prev, next) {
    let dest = prev;
    if (blockIsCheck(prev))
        dest = prev.querySelector('.ck-text') ?? prev;
    const frag = document.createDocumentFragment();
    if (blockIsCheck(next)) {
        const label = next.querySelector('.ck-text');
        if (label) {
            while (label.firstChild)
                frag.appendChild(label.firstChild);
        }
    }
    else {
        while (next.firstChild)
            frag.appendChild(next.firstChild);
    }
    dest.appendChild(frag);
    next.remove();
    ensureNotEmpty(prev);
}
function listToParagraphs(list) {
    const parent = list.parentElement;
    if (!parent)
        return;
    const rows = Array.from(list.children);
    const firstP = [];
    for (const row of rows) {
        const p = document.createElement('P');
        if (blockIsCheck(row)) {
            const label = row.querySelector('.ck-text');
            if (label)
                p.appendChild(label.cloneNode(true));
        }
        else {
            const frag = document.createDocumentFragment();
            while (row.firstChild)
                frag.appendChild(row.firstChild);
            p.appendChild(frag);
        }
        ensureNotEmpty(p);
        firstP.push(p);
        parent.insertBefore(p, list);
    }
    list.remove();
    if (firstP.length)
        placeCaretStartIn(firstP[0]);
}
function escapeHTML(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function caretAtEndOf(r, el) {
    const t = document.createRange();
    t.selectNodeContents(el);
    t.collapse(false);
    const s = sel();
    if (!s)
        return true;
    const cr = s.getRangeAt(0);
    return cr.startContainer === t.endContainer && cr.startOffset === t.endOffset;
}
function placeCaretEnd(el) {
    const r = document.createRange();
    const last = el.lastElementChild;
    if (last && last.tagName === 'BR')
        r.setStartBefore(last);
    else {
        r.selectNodeContents(el);
        r.collapse(false);
    }
    r.collapse(true);
    const s = getSelection();
    s?.removeAllRanges();
    s?.addRange(r);
}
function placeCaretStartIn(el) {
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
    const t = walker.nextNode();
    const r = document.createRange();
    if (t) {
        r.setStart(t, 0);
        r.collapse(true);
    }
    else {
        r.selectNodeContents(el);
        r.collapse(true);
    }
    const s = getSelection();
    s?.removeAllRanges();
    s?.addRange(r);
}
function captureSelection(ed) {
    const s = sel();
    if (!s)
        return null;
    const r = s.getRangeAt(0);
    if (!ed.contains(r.startContainer) && r.startContainer !== ed)
        return null;
    return { s: selPath(ed, r.startContainer, r.startOffset), e: selPath(ed, r.endContainer, r.endOffset) };
}
function selPath(ed, node, offset) {
    const path = [offset];
    let cur = node;
    while (cur && cur !== ed) {
        const parentOf = cur.parentElement;
        if (!parentOf)
            break;
        let idx = 0;
        for (const ch of Array.from(parentOf.childNodes)) {
            if (ch === cur)
                break;
            idx++;
        }
        path.unshift(idx);
        cur = parentOf;
    }
    return path;
}
function restoreSelection(ed, d) {
    if (!d) {
        placeCaretStartIn(ed);
        return;
    }
    const a = resolvePath(ed, d.s);
    const b = resolvePath(ed, d.e) ?? a;
    if (!a || !b) {
        placeCaretStartIn(ed);
        return;
    }
    const r = document.createRange();
    try {
        r.setStart(a.node, a.offset);
        r.setEnd(b.node, b.offset);
    }
    catch {
        placeCaretStartIn(ed);
        return;
    }
    const s = getSelection();
    s?.removeAllRanges();
    s?.addRange(r);
}
function resolvePath(ed, p) {
    let node = ed;
    for (let i = 0; i < p.length - 1; i++) {
        const next = node.childNodes[p[i]];
        if (!next)
            return null;
        node = next;
    }
    const off = p[p.length - 1];
    if (node.nodeType === Node.TEXT_NODE) {
        return { node, offset: Math.max(0, Math.min(off, node.length)) };
    }
    return { node, offset: Math.max(0, Math.min(off, node.childNodes.length)) };
}
// ============================================================
// singleton
// ============================================================
let instance = null;
function getEditor() {
    if (!instance) {
        const ed = document.getElementById('editor-root');
        instance = new EditorEngine(ed);
    }
    return instance;
}
};

// ── module: src/view.ts ──
__mods["src/view.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// VIEW — DOM projection of the App state.
// All rendering here is idempotent: it reads the store and
// rebuilds the parts of the DOM that correspond.
// ============================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ICONS = void 0;
exports.getViewEls = getViewEls;
exports.buildView = buildView;
exports.renderRoot = renderRoot;
exports.renderSidebar = renderSidebar;
exports.renderList = renderList;
exports.flashSaved = flashSaved;
const constants_1 = require("./constants");
const utils_1 = require("./utils");
const sanitize_1 = require("./sanitize");
const S = __importStar(require("./state"));
const editor_1 = require("./editor");
let els = null;
let searchTimer = null;
let draggingNoteId = null;
function getViewEls() {
    return els;
}
/* ---------------------------------------------------------- */
/* icons                                                       */
/* ---------------------------------------------------------- */
exports.ICONS = {
    chevron: (0, utils_1.svgIcon)('<polyline points="6 9 12 15 18 9"/>', 14, 2),
    folder: (0, utils_1.svgIcon)('<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>', 15, 1.7),
    allNotes: (0, utils_1.svgIcon)('<path d="M4 4h16v16H4z"/><line x1="8" y1="9" x2="16" y2="9"/><line x1="8" y1="13" x2="13" y2="13"/>', 15, 1.7),
    trash: (0, utils_1.svgIcon)('<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>', 15, 1.7),
    tag: (0, utils_1.svgIcon)('<path d="M20.6 13.4 13.4 20.6a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8z"/><line x1="7" y1="7" x2="7.01" y2="7"/>', 14, 1.7),
    plus: (0, utils_1.svgIcon)('<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>', 15, 2),
    search: (0, utils_1.svgIcon)('<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.5" y2="16.5"/>', 15, 1.8),
    pin: (0, utils_1.svgIcon)('<path d="M12 17v5"/><path d="M9 3h6l1 7 2 2H6l2-2z"/>', 13, 1.8),
    dots: (0, utils_1.svgIcon)('<circle cx="12" cy="5" r="1.4"/><circle cx="12" cy="12" r="1.4"/><circle cx="12" cy="19" r="1.4"/>', 15, 2.4),
    theme: (0, utils_1.svgIcon)('<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>', 15, 1.8),
    restore: (0, utils_1.svgIcon)('<polyline points="1 4 1 10 7 10"/><path d="M3.5 15a9 9 0 1 0 2-9.4L1 10"/>', 13, 1.8),
    x: (0, utils_1.svgIcon)('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>', 13, 2),
    bold: (0, utils_1.svgIcon)('<path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z"/>', 15, 1.9),
    italic: (0, utils_1.svgIcon)('<line x1="19" y1="5" x2="10" y2="5"/><line x1="14" y1="19" x2="5" y2="19"/><line x1="15" y1="5" x2="9" y2="19"/>', 15, 1.9),
    underline: (0, utils_1.svgIcon)('<path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="5" y1="20" x2="19" y2="20"/>', 15, 1.9),
    strike: (0, utils_1.svgIcon)('<path d="M16 4H9a3 3 0 0 0-2.8 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" y1="12" x2="20" y2="12"/>', 15, 1.9),
    code: (0, utils_1.svgIcon)('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>', 15, 1.9),
    h1: (0, utils_1.svgIcon)('<path d="M4 5v14M10 5v14M4 12h6"/><path d="M14 13l2-3v8"/>', 15, 1.9),
    h2: (0, utils_1.svgIcon)('<path d="M4 5v14M10 5v14M4 12h6"/><path d="M15 10c-1.5.5-2 1.5-1.5 3 .8 2 4 1.5 4 4a3 3 0 0 1-6 .5"/>', 15, 1.9),
    h3: (0, utils_1.svgIcon)('<path d="M4 5v14M10 5v14M4 12h6"/><path d="M15 9.5c-1.8 0-2.5 1.2-2 2.5.5 1.3 3.5 1 3.5 3a2.6 2.6 0 0 1-5.2.3"/>', 15, 1.9),
    ul: (0, utils_1.svgIcon)('<line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4.5" cy="6" r="1.2"/><circle cx="4.5" cy="12" r="1.2"/><circle cx="4.5" cy="18" r="1.2"/>', 15, 1.9),
    ol: (0, utils_1.svgIcon)('<line x1="10" y1="6" x2="20" y2="6"/><line x1="10" y1="12" x2="20" y2="12"/><line x1="10" y1="18" x2="20" y2="18"/><path d="M4 6h2v4M4 10h3"/><path d="M6 18H4c0-1.5 2-1.5 2-3s-2-1.5-3 0"/>', 15, 1.9),
    check: (0, utils_1.svgIcon)('<rect x="3" y="3" width="18" height="18" rx="4"/><polyline points="8 12 11 15 16 9"/>', 15, 1.8),
    quote: (0, utils_1.svgIcon)('<path d="M9 7c-2.5 1-4 3-4 6v4h5v-5H7c0-2 1-3.5 2-4zM19 7c-2.5 1-4 3-4 6v4h5v-5h-3c0-2 1-3.5 2-4z"/>', 15, 1.6),
    hr: (0, utils_1.svgIcon)('<line x1="4" y1="12" x2="20" y2="12"/><circle cx="7" cy="8" r="0.8"/><circle cx="12" cy="8" r="0.8"/><circle cx="17" cy="8" r="0.8"/><circle cx="7" cy="16" r="0.8"/><circle cx="12" cy="16" r="0.8"/><circle cx="17" cy="16" r="0.8"/>', 15, 1.9),
};
/* ---------------------------------------------------------- */
/* boot / build                                                */
/* ---------------------------------------------------------- */
function buildView() {
    const root = document.getElementById('app');
    const e = {
        app: root,
        sidebar: (0, utils_1.el)('aside', { cls: 'pane sidebar' }),
        sidebarToggle: null,
        newNotebookBtn: null,
        allNotesRow: null,
        trashRow: null,
        tagsBlock: null,
        notebooksBlock: null,
        allCount: null,
        trashCount: null,
        listHeader: null,
        listTitle: null,
        listCount: null,
        searchInput: null,
        sortSelect: null,
        newNoteBtn: null,
        listScroll: null,
        noteItems: new Map(),
        editorPane: null,
        emptyState: null,
        noteView: null,
        toolbar: null,
        saved: null,
        noteMeta: null,
        editorRoot: null,
        tagRow: null,
        resizeHandle: null,
        savedTimer: null,
    };
    root.innerHTML = '';
    /* ----- sidebar ----- */
    const sbHead = (0, utils_1.el)('div', { cls: 'sidebar-head' });
    const brand = (0, utils_1.el)('div', { cls: 'brand' });
    brand.innerHTML = `<span class="brand-mark">N</span><span class="brand-name">Notely</span>`;
    e.newNotebookBtn = (0, utils_1.el)('button', { cls: 'icon-btn', title: 'New notebook', attrs: { 'aria-label': 'New notebook' } });
    e.newNotebookBtn.innerHTML = exports.ICONS.plus;
    e.newNotebookBtn.addEventListener('click', onNewNotebook);
    sbHead.append(brand, e.newNotebookBtn);
    e.sidebar.appendChild(sbHead);
    e.sidebar.appendChild((0, utils_1.el)('div', { cls: 'sb-section-label', text: 'Browse' }));
    e.allNotesRow = makeSideRow(exports.ICONS.allNotes, 'All Notes', 'all-notes');
    e.allCount = (0, utils_1.el)('span', { cls: 'row-count' });
    e.allNotesRow.querySelector('.row-label').after(e.allCount);
    e.allNotesRow.addEventListener('click', () => S.setView({ type: 'all' }));
    e.sidebar.appendChild(e.allNotesRow);
    e.tagsBlock = (0, utils_1.el)('div', { cls: 'tags-block' });
    e.sidebar.appendChild(e.tagsBlock);
    e.notebooksBlock = (0, utils_1.el)('div', { cls: 'notebooks-block' });
    e.sidebar.appendChild(e.notebooksBlock);
    const sbFoot = (0, utils_1.el)('div', { cls: 'sidebar-foot' });
    e.trashRow = makeSideRow(exports.ICONS.trash, 'Trash', 'trash');
    e.trashCount = (0, utils_1.el)('span', { cls: 'row-count' });
    e.trashRow.querySelector('.row-label').after(e.trashCount);
    e.trashRow.addEventListener('click', () => S.setView({ type: 'trash' }));
    sbFoot.append(e.trashRow);
    e.sidebar.appendChild(sbFoot);
    /* ----- list pane ----- */
    const list = (0, utils_1.el)('section', { cls: 'pane list-pane' });
    e.listHeader = (0, utils_1.el)('div', { cls: 'list-header' });
    const lhTop = (0, utils_1.el)('div', { cls: 'lh-top' });
    const lhTitleWrap = (0, utils_1.el)('div', { cls: 'lh-title-wrap' });
    e.listTitle = (0, utils_1.el)('div', { cls: 'list-title', text: 'All Notes' });
    e.listCount = (0, utils_1.el)('div', { cls: 'list-count' });
    lhTitleWrap.append(e.listTitle, e.listCount);
    const searchWrap = (0, utils_1.el)('div', { cls: 'search-wrap' });
    searchWrap.innerHTML = exports.ICONS.search;
    e.searchInput = (0, utils_1.el)('input', { cls: 'search-input', attrs: { type: 'text', placeholder: 'Search notes…', 'aria-label': 'Search notes' } });
    searchWrap.appendChild(e.searchInput);
    e.searchInput.addEventListener('input', onSearchInput);
    e.searchInput.addEventListener('keydown', (ev) => {
        if (ev.key === 'Escape')
            ev.target.blur();
    });
    const newNoteBtn = (0, utils_1.el)('button', { cls: 'icon-btn', title: 'New note', attrs: { 'aria-label': 'New note' } });
    newNoteBtn.innerHTML = exports.ICONS.plus;
    newNoteBtn.addEventListener('click', onNewNote);
    e.newNoteBtn = newNoteBtn;
    lhTop.append(lhTitleWrap, searchWrap, e.newNoteBtn);
    const lhBottom = (0, utils_1.el)('div', { cls: 'lh-bottom' });
    const sortLabel = (0, utils_1.el)('label', { cls: 'sort-label', text: 'Sort' });
    e.sortSelect = (0, utils_1.el)('select', { cls: 'sort-select', attrs: { 'aria-label': 'Sort order' } });
    for (const [v, l] of [['modified', 'Modified'], ['created', 'Created'], ['alpha', 'Alphabetical']]) {
        e.sortSelect.appendChild((0, utils_1.el)('option', { text: l, attrs: { value: v } }));
    }
    e.sortSelect.addEventListener('change', () => S.setSort(e.sortSelect.value));
    lhBottom.append(sortLabel, e.sortSelect);
    e.listHeader.append(lhTop, lhBottom);
    list.appendChild(e.listHeader);
    e.listScroll = (0, utils_1.el)('div', { cls: 'list-scroll' });
    list.appendChild(e.listScroll);
    e.editorPane = (0, utils_1.el)('section', { cls: 'pane editor-pane' });
    root.append(e.sidebar, list, e.editorPane);
    buildEditorPane(e);
    wireResizer(e);
    wireTheme(e);
    wireSidebarToggle(e);
    els = e;
}
function makeSideRow(icon, label, cls) {
    const row = (0, utils_1.el)('div', { cls: 'side-row ' + cls });
    const ic = (0, utils_1.el)('span', { cls: 'row-icon', html: icon });
    const lb = (0, utils_1.el)('span', { cls: 'row-label', text: label });
    row.append(ic, lb);
    return row;
}
function buildEditorPane(e) {
    e.emptyState = (0, utils_1.el)('div', { cls: 'empty-state' });
    e.emptyState.innerHTML = `
    <div class="empty-mark">${exports.ICONS.allNotes}</div>
    <div class="empty-title">Select or create a note</div>
    <div class="empty-sub">Your thoughts live here — beautifully kept.</div>
    <button class="primary-btn" data-act="new">New note</button>`;
    e.emptyState.querySelector('[data-act="new"]').addEventListener('click', onNewNote);
    e.noteView = (0, utils_1.el)('div', { cls: 'note-view' });
    e.noteView.hidden = true;
    e.toolbar = (0, utils_1.el)('div', { cls: 'toolbar', attrs: { role: 'toolbar', 'aria-label': 'Formatting' } });
    e.saved = (0, utils_1.el)('span', { cls: 'saved-ind', text: 'Saved' });
    e.noteMeta = (0, utils_1.el)('div', { cls: 'note-meta' });
    e.editorRoot = (0, utils_1.el)('div', {
        cls: 'editor-root',
        attrs: {
            id: 'editor-root',
            contenteditable: 'true',
            spellcheck: 'false',
            role: 'textbox',
            'aria-multiline': 'true',
            'aria-label': 'Note body',
        },
    });
    e.tagRow = (0, utils_1.el)('div', { cls: 'tag-row' });
    e.noteView.append(e.toolbar, e.saved, e.noteMeta, e.editorRoot, e.tagRow);
    e.editorPane.append(e.emptyState, e.noteView);
}
/* ---------------------------------------------------------- */
/* render: root dispatcher                                     */
/* ---------------------------------------------------------- */
function renderRoot() {
    if (!els)
        return;
    applyThemeClass();
    renderLayout();
    const ch = S.lastChange();
    switch (ch.kind) {
        case 'boot':
        case 'notebook':
        case 'view':
            renderSidebar();
            renderList();
            renderEditorSlot();
            break;
        case 'note':
            renderSidebar();
            renderList();
            renderEditorSlot();
            break;
        case 'layout':
            break;
    }
}
function renderEditorSlot() {
    if (!els)
        return;
    const sel = S.noteById(S.getState().ui.selectedNoteId);
    if (sel && !sel.trashedAt)
        renderNotePane(sel);
    else
        showEmptyState(!!sel && !!sel.trashedAt);
}
function showEmptyState(isTrash) {
    if (!els)
        return;
    (0, editor_1.getEditor)().clear();
    els.emptyState.hidden = false;
    els.noteView.hidden = true;
    const sub = els.emptyState.querySelector('.empty-sub');
    const title = els.emptyState.querySelector('.empty-title');
    if (isTrash) {
        title.textContent = 'Note in Trash';
        sub.textContent = 'Use the note menu in the list to restore it or delete it forever.';
    }
    else {
        title.textContent = 'Select or create a note';
        sub.textContent = 'Your thoughts live here — beautifully kept.';
    }
}
function applyThemeClass() {
    const theme = S.getState().ui.theme;
    document.documentElement.setAttribute('data-theme', theme);
    const pal = constants_1.C.PALETTE[theme];
    for (const [k, v] of Object.entries(pal)) {
        document.documentElement.style.setProperty('--' + k, v);
    }
}
function renderLayout() {
    if (!els)
        return;
    const ui = S.getState().ui;
    els.app.classList.toggle('sb-collapsed', !ui.sidebarOpen);
    if (ui.sidebarOpen)
        els.sidebar.style.width = ui.sidebarW + 'px';
    els.resizeHandle.style.left = (ui.sidebarOpen ? ui.sidebarW : 0) + 'px';
}
/* ---------------------------------------------------------- */
/* render: sidebar                                             */
/* ---------------------------------------------------------- */
function renderSidebar() {
    if (!els)
        return;
    const app = S.getState();
    const view = app.ui.view;
    const alive = app.notes.filter(n => !n.trashedAt);
    els.allCount.textContent = String(alive.length);
    els.trashCount.textContent = String(app.notes.length - alive.length);
    els.allNotesRow.classList.toggle('active', view.type === 'all');
    els.trashRow.classList.toggle('active', view.type === 'trash');
    // tags (global, from alive notes)
    const tagCounts = new Map();
    for (const n of alive)
        for (const t of n.tags)
            tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
    const tags = [...tagCounts.keys()].sort((a, b) => a.localeCompare(b)).slice(0, 30);
    els.tagsBlock.replaceChildren();
    if (tags.length) {
        const label = (0, utils_1.el)('div', { cls: 'sb-section-label tags-label' });
        label.innerHTML = `${exports.ICONS.tag} Tags`;
        els.tagsBlock.appendChild(label);
        for (const t of tags) {
            const chip = (0, utils_1.el)('button', { cls: 'tag-chip', text: '#' + t, title: 'Filter by #' + t });
            chip.classList.toggle('active', view.type === 'tag' && view.tag === t);
            chip.addEventListener('click', () => S.setView(view.type === 'tag' && view.tag === t ? { type: 'all' } : { type: 'tag', tag: t }));
            els.tagsBlock.appendChild(chip);
        }
    }
    // notebooks
    els.notebooksBlock.replaceChildren();
    for (const nb of app.notebooks) {
        els.notebooksBlock.appendChild(renderNotebookRow(nb, view));
    }
}
function renderNotebookRow(nb, view) {
    const active = view.type === 'notebook' && view.id === nb.id;
    const row = (0, utils_1.el)('div', { cls: 'nb-row' + (active ? ' active' : '') + (nb.collapsed ? ' collapsed' : '') });
    const head = (0, utils_1.el)('div', { cls: 'nb-head' });
    const chev = (0, utils_1.el)('button', { cls: 'nb-chev', title: 'Collapse / expand', attrs: { 'aria-label': 'Toggle notebook' } });
    chev.innerHTML = exports.ICONS.chevron;
    chev.addEventListener('click', (ev) => {
        ev.stopPropagation();
        S.toggleNotebookCollapsed(nb.id);
    });
    const icon = (0, utils_1.el)('span', { cls: 'nb-icon', html: exports.ICONS.folder });
    const name = (0, utils_1.el)('span', { cls: 'nb-name', text: nb.name });
    const count = (0, utils_1.el)('span', {
        cls: 'row-count',
        text: String(S.getState().notes.filter(n => !n.trashedAt && n.notebookId === nb.id).length),
    });
    const menu = (0, utils_1.el)('button', { cls: 'nb-menu icon-btn', title: 'Notebook menu', attrs: { 'aria-label': 'Notebook menu' } });
    menu.innerHTML = exports.ICONS.dots;
    menu.addEventListener('click', (ev) => {
        ev.stopPropagation();
        openNotebookMenu(nb, menu);
    });
    head.append(chev, icon, name, count, menu);
    row.appendChild(head);
    row.addEventListener('click', () => S.setView({ type: 'notebook', id: nb.id }));
    row.addEventListener('dblclick', (ev) => {
        ev.stopPropagation();
        startNotebookRename(nb, name);
    });
    row.addEventListener('dragover', (ev) => {
        if (!draggingNoteId)
            return;
        ev.preventDefault();
        if (ev.dataTransfer)
            ev.dataTransfer.dropEffect = 'move';
        row.classList.add('drop-target');
    });
    row.addEventListener('dragleave', () => row.classList.remove('drop-target'));
    row.addEventListener('drop', (ev) => {
        ev.preventDefault();
        row.classList.remove('drop-target');
        const id = (ev.dataTransfer?.getData('text/plain') || draggingNoteId);
        if (id)
            S.moveNote(id, nb.id);
        draggingNoteId = null;
    });
    if (!nb.collapsed) {
        const notes = S.getState().notes
            .filter(n => !n.trashedAt && n.notebookId === nb.id)
            .sort((a, b) => b.updatedAt - a.updatedAt);
        const body = (0, utils_1.el)('div', { cls: 'nb-notes' });
        if (notes.length === 0)
            body.appendChild((0, utils_1.el)('div', { cls: 'nb-empty', text: 'No notes' }));
        for (const n of notes)
            body.appendChild(renderMiniNote(n));
        row.appendChild(body);
    }
    return row;
}
function renderMiniNote(n) {
    const selected = S.getState().ui.selectedNoteId === n.id;
    const row = (0, utils_1.el)('div', { cls: 'mini-note' + (selected ? ' selected' : '') });
    const title = (0, utils_1.el)('span', { cls: 'mini-title', text: (0, sanitize_1.deriveTitle)(n.body) || 'Untitled' });
    row.append(title);
    if (n.pinned)
        row.appendChild((0, utils_1.el)('span', { cls: 'mini-pin', html: exports.ICONS.pin, title: 'Pinned' }));
    row.addEventListener('click', () => S.selectNote(n.id));
    return row;
}
/* ---------------------------------------------------------- */
/* render: note list                                           */
/* ---------------------------------------------------------- */
function onSearchInput() {
    if (!els)
        return;
    if (searchTimer)
        window.clearTimeout(searchTimer);
    searchTimer = window.setTimeout(() => renderList(), 80);
}
function renderList() {
    if (!els)
        return;
    const app = S.getState();
    const view = app.ui.view;
    const q = els.searchInput.value.trim().toLowerCase();
    if (view.type === 'all')
        els.listTitle.textContent = 'All Notes';
    else if (view.type === 'trash')
        els.listTitle.textContent = 'Trash';
    else if (view.type === 'tag')
        els.listTitle.textContent = '#' + view.tag;
    else {
        const nb = app.notebooks.find(n => n.id === view.id);
        els.listTitle.textContent = nb ? nb.name : 'All Notes';
    }
    els.sortSelect.value = app.ui.sort;
    let notes = S.visibleNotes();
    if (q)
        notes = filterSearch(notes, q);
    els.listCount.textContent = notes.length === 1 ? '1 note' : notes.length + ' notes';
    const scroll = els.listScroll;
    scroll.replaceChildren();
    els.noteItems.clear();
    if (notes.length === 0) {
        const empty = (0, utils_1.el)('div', { cls: 'list-empty' });
        if (q) {
            empty.innerHTML = `<div class="le-icon">${exports.ICONS.search}</div><div class="le-title">No matches for “${(0, utils_1.esc)(q)}”</div><div class="le-sub">Try a different search.</div>`;
        }
        else if (view.type === 'trash') {
            empty.innerHTML = `<div class="le-icon">${exports.ICONS.trash}</div><div class="le-title">Trash is empty</div><div class="le-sub">Deleted notes appear here until you delete them forever.</div>`;
        }
        else {
            empty.innerHTML = `<div class="le-icon">${exports.ICONS.plus}</div><div class="le-title">Nothing here yet</div><div class="le-sub">Click the + to start a note.</div>`;
        }
        scroll.appendChild(empty);
        return;
    }
    for (const n of notes) {
        const item = makeNoteItem(n, q);
        scroll.appendChild(item);
        els.noteItems.set(n.id, item);
    }
}
function filterSearch(notes, q) {
    return notes.filter(n => {
        if (((0, sanitize_1.deriveTitle)(n.body) || 'Untitled').toLowerCase().includes(q))
            return true;
        return (0, sanitize_1.extractText)(n.body).toLowerCase().includes(q);
    });
}
function makeNoteItem(n, q) {
    const selected = S.getState().ui.selectedNoteId === n.id;
    const item = (0, utils_1.el)('div', { cls: 'note-item' + (selected ? ' selected' : '') + (n.pinned ? ' pinned' : '') });
    item.draggable = true;
    item.setAttribute('data-note-id', n.id);
    item.setAttribute('role', 'button');
    item.setAttribute('tabindex', '0');
    const top = (0, utils_1.el)('div', { cls: 'ni-top' });
    const title = (0, utils_1.el)('div', { cls: 'ni-title' });
    if (q)
        title.innerHTML = highlightText((0, sanitize_1.deriveTitle)(n.body) || 'Untitled', q);
    else
        title.textContent = (0, sanitize_1.deriveTitle)(n.body) || 'Untitled';
    const time = (0, utils_1.el)('div', { cls: 'ni-time', text: (0, utils_1.relTime)(n.updatedAt), title: new Date(n.updatedAt).toLocaleString() });
    top.append(title, time);
    item.appendChild(top);
    const snipText = snippetFor(n);
    const snip = (0, utils_1.el)('div', { cls: 'ni-snippet' + (snipText ? '' : ' empty') });
    if (snipText) {
        if (q)
            snip.innerHTML = highlightText(snipText, q);
        else
            snip.textContent = snipText;
    }
    else
        snip.textContent = 'No additional text';
    item.appendChild(snip);
    const bottom = (0, utils_1.el)('div', { cls: 'ni-bottom' });
    for (const t of n.tags.slice(0, 4)) {
        const chip = (0, utils_1.el)('button', { cls: 'mini-tag', text: '#' + t });
        chip.addEventListener('click', (ev) => {
            ev.stopPropagation();
            S.setView({ type: 'tag', tag: t });
        });
        bottom.appendChild(chip);
    }
    if (n.tags.length > 4)
        bottom.appendChild((0, utils_1.el)('span', { cls: 'mini-tag more', text: '+' + (n.tags.length - 4) }));
    bottom.appendChild((0, utils_1.el)('span', { cls: 'ni-flex' }));
    if (n.pinned && !n.trashedAt)
        bottom.appendChild((0, utils_1.el)('span', { cls: 'ni-pin', html: exports.ICONS.pin, title: 'Pinned' }));
    const menuBtn = (0, utils_1.el)('button', { cls: 'ni-menu icon-btn', title: 'Note menu', attrs: { 'aria-label': 'Note menu' } });
    menuBtn.innerHTML = exports.ICONS.dots;
    menuBtn.addEventListener('click', (ev) => {
        ev.stopPropagation();
        openNoteMenu(n, menuBtn);
    });
    bottom.appendChild(menuBtn);
    item.appendChild(bottom);
    item.addEventListener('click', () => S.selectNote(n.id));
    item.addEventListener('keydown', (ev) => {
        if (ev.key === 'Enter' || ev.key === ' ') {
            ev.preventDefault();
            S.selectNote(n.id);
        }
    });
    item.addEventListener('dragstart', (ev) => {
        draggingNoteId = n.id;
        if (ev.dataTransfer) {
            ev.dataTransfer.setData('text/plain', n.id);
            ev.dataTransfer.effectAllowed = 'move';
        }
        item.classList.add('dragging');
    });
    item.addEventListener('dragend', () => {
        draggingNoteId = null;
        item.classList.remove('dragging');
    });
    return item;
}
function snippetFor(n) {
    const t = (0, sanitize_1.extractText)(n.body).replace(/\s+/g, ' ').trim();
    return t.length > constants_1.C.SNIPPET_LEN ? t.slice(0, constants_1.C.SNIPPET_LEN).trimEnd() + '…' : t;
}
function highlightText(text, q) {
    if (!q)
        return (0, utils_1.esc)(text);
    const idx = text.toLowerCase().indexOf(q.toLowerCase());
    if (idx < 0)
        return (0, utils_1.esc)(text);
    return (0, utils_1.esc)(text.slice(0, idx)) + '<mark>' + (0, utils_1.esc)(text.slice(idx, idx + q.length)) + '</mark>' + (0, utils_1.esc)(text.slice(idx + q.length));
}
/* ---------------------------------------------------------- */
/* render: editor pane                                         */
/* ---------------------------------------------------------- */
function renderNotePane(n) {
    if (!els)
        return;
    els.emptyState.hidden = true;
    els.noteView.hidden = false;
    const ed = (0, editor_1.getEditor)();
    const keep = ed.isActive() && ed.noteId === n.id;
    ed.load(n.id, n.body, keep);
    renderMeta(n);
    renderTagRow(n);
    els.saved.classList.remove('show');
}
function renderMeta(n) {
    if (!els)
        return;
    const app = S.getState();
    const bits = [];
    const nb = n.notebookId ? app.notebooks.find(x => x.id === n.notebookId) : null;
    bits.push(nb ? nb.name : 'All Notes');
    bits.push('Edited ' + (0, utils_1.relTime)(n.updatedAt));
    const words = (0, sanitize_1.extractText)(n.body).split(/\s+/).filter(Boolean).length;
    if (words)
        bits.push(words + (words === 1 ? ' word' : ' words'));
    if (n.pinned)
        bits.push('Pinned');
    els.noteMeta.textContent = bits.join('  ·  ');
}
function renderTagRow(n) {
    if (!els)
        return;
    const row = els.tagRow;
    row.replaceChildren();
    for (const t of n.tags) {
        const chip = (0, utils_1.el)('span', { cls: 'tag-chip', title: 'Filter by #' + t });
        const labelPart = (0, utils_1.el)('button', { cls: 'tag-chip-label', text: '#' + t });
        labelPart.addEventListener('click', () => S.setView({ type: 'tag', tag: t }));
        const x = (0, utils_1.el)('button', { cls: 'tag-x', title: 'Remove tag', attrs: { 'aria-label': 'Remove tag' } });
        x.innerHTML = exports.ICONS.x;
        x.addEventListener('click', () => S.setNoteTags(n.id, n.tags.filter(t2 => t2 !== t)));
        chip.append(labelPart, x);
        row.appendChild(chip);
    }
    const add = (0, utils_1.el)('button', { cls: 'tag-chip add', text: '+ tag', title: 'Add a tag' });
    add.addEventListener('click', () => promptAddTag(n));
    row.appendChild(add);
}
function promptAddTag(n) {
    const raw = window.prompt('Add tag(s) — separate several with commas:', '');
    if (raw == null)
        return;
    const parts = raw.split(',').map(t => t.trim().replace(/^#/, '').toLowerCase()).filter(Boolean);
    const merged = [...n.tags];
    for (const p of parts)
        if (p && !merged.includes(p))
            merged.push(p);
    if (merged.length !== n.tags.length)
        S.setNoteTags(n.id, merged);
}
/* ---------------------------------------------------------- */
/* menus                                                       */
/* ---------------------------------------------------------- */
let openMenu = null;
let menuCleanups = [];
function closeMenu() {
    for (const fn of menuCleanups)
        fn();
    menuCleanups = [];
    if (openMenu)
        openMenu.remove();
    openMenu = null;
}
function menuShell() {
    const m = (0, utils_1.el)('div', { cls: 'ctx-menu' });
    openMenu = m;
    const onDown = (ev) => {
        if (openMenu && !openMenu.contains(ev.target))
            closeMenu();
    };
    const onKey = (ev) => {
        if (ev.key === 'Escape')
            closeMenu();
    };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown', onKey);
    menuCleanups.push(() => document.removeEventListener('mousedown', onDown));
    menuCleanups.push(() => document.removeEventListener('keydown', onKey));
    return m;
}
function menuRow(label, opts = {}) {
    const b = (0, utils_1.el)('button', {
        cls: 'menu-row' + (opts.danger ? ' danger' : '') + (opts.disabled ? ' disabled' : ''),
        text: label,
    });
    b.disabled = !!opts.disabled;
    return b;
}
function placeMenu(m, anchor, alignRight) {
    m.style.position = 'fixed';
    const r = anchor.getBoundingClientRect();
    m.style.top = Math.min(window.innerHeight - m.offsetHeight - 8, r.bottom + 4) + 'px';
    m.style.left = Math.max(8, alignRight ? Math.min(r.right - m.offsetWidth, window.innerWidth - m.offsetWidth - 8) : r.left) + 'px';
}
function openNoteMenu(n, anchor) {
    closeMenu();
    const m = menuShell();
    if (n.trashedAt) {
        const restore = menuRow('Restore to original notebook');
        restore.addEventListener('click', () => {
            S.restoreNote(n.id);
            closeMenu();
        });
        const destroy = menuRow('Delete forever', { danger: true });
        destroy.addEventListener('click', () => {
            if (window.confirm('Delete this note permanently? This cannot be undone.')) {
                S.destroyNote(n.id);
                closeMenu();
            }
        });
        m.append(restore, destroy);
    }
    else {
        const pin = menuRow(n.pinned ? 'Unpin note' : 'Pin to top');
        pin.addEventListener('click', () => {
            S.setPinned(n.id, !n.pinned);
            closeMenu();
        });
        const dup = menuRow('Duplicate');
        dup.addEventListener('click', () => {
            S.duplicateNote(n.id);
            closeMenu();
        });
        m.append(pin, dup);
        m.appendChild((0, utils_1.el)('div', { cls: 'menu-label', text: 'Move to notebook' }));
        const all = menuRow('All Notes (no notebook)');
        all.addEventListener('click', () => {
            S.moveNote(n.id, null);
            closeMenu();
        });
        m.appendChild(all);
        for (const nb of S.getState().notebooks) {
            const row = menuRow(nb.name + (nb.id === n.notebookId ? '  ✓' : ''));
            row.disabled = nb.id === n.notebookId;
            row.addEventListener('click', () => {
                S.moveNote(n.id, nb.id);
                closeMenu();
            });
            m.appendChild(row);
        }
        m.appendChild((0, utils_1.el)('div', { cls: 'menu-sep' }));
        const trash = menuRow('Move to Trash', { danger: true });
        trash.addEventListener('click', () => {
            S.trashNote(n.id);
            closeMenu();
        });
        m.appendChild(trash);
    }
    document.body.appendChild(m);
    placeMenu(m, anchor, true);
}
function openNotebookMenu(nb, anchor) {
    closeMenu();
    const m = menuShell();
    const rename = menuRow('Rename notebook');
    rename.addEventListener('click', () => {
        const row = anchor.closest('.nb-row');
        const nameEl = row ? row.querySelector('.nb-name') : null;
        closeMenu();
        if (nameEl)
            startNotebookRename(nb, nameEl);
    });
    const del = menuRow('Delete notebook', { danger: true });
    del.addEventListener('click', () => {
        const ok = window.confirm(`Delete “${nb.name}”? Its notes stay in All Notes.`);
        if (ok) {
            S.deleteNotebook(nb.id);
            closeMenu();
        }
    });
    m.append(rename, del);
    document.body.appendChild(m);
    placeMenu(m, anchor, false);
}
function startNotebookRename(nb, nameEl) {
    const input = (0, utils_1.el)('input', { cls: 'inline-rename', attrs: { value: nb.name } });
    nameEl.replaceWith(input);
    input.focus();
    input.select();
    let done = false;
    const finish = (save) => {
        if (done)
            return;
        done = true;
        if (save && input.value.trim())
            S.renameNotebook(nb.id, input.value);
        renderSidebar();
    };
    input.addEventListener('keydown', (ev) => {
        if (ev.key === 'Enter') {
            ev.preventDefault();
            finish(true);
        }
        if (ev.key === 'Escape') {
            ev.preventDefault();
            finish(false);
        }
    });
    input.addEventListener('blur', () => finish(true));
}
/* ---------------------------------------------------------- */
/* actions                                                     */
/* ---------------------------------------------------------- */
function onNewNote() {
    const v = S.getState().ui.view;
    if (v.type === 'trash')
        return;
    const nbId = v.type === 'notebook' ? v.id : null;
    S.createNote(nbId);
}
function onNewNotebook() {
    const name = window.prompt('Notebook name:', 'New Notebook');
    if (name == null)
        return;
    S.createNotebook(name.trim() || 'New Notebook');
}
/* ---------------------------------------------------------- */
/* theme + sidebar toggle                                      */
/* ---------------------------------------------------------- */
function wireTheme(e) {
    const btn = (0, utils_1.el)('button', { cls: 'icon-btn theme-btn', title: 'Toggle theme', attrs: { 'aria-label': 'Toggle theme' } });
    btn.innerHTML = exports.ICONS.theme;
    btn.addEventListener('click', () => S.setTheme(S.getState().ui.theme === 'dark' ? 'light' : 'dark'));
    e.editorPane.appendChild(btn);
}
function wireSidebarToggle(e) {
    const btn = (0, utils_1.el)('button', { cls: 'sb-toggle', title: 'Show notebooks', attrs: { 'aria-label': 'Show sidebar' } });
    btn.innerHTML = exports.ICONS.folder;
    btn.addEventListener('click', () => S.setSidebarOpen(true));
    e.editorPane.appendChild(btn);
    e.sidebarToggle = btn;
}
/* ---------------------------------------------------------- */
/* saved indicator                                             */
/* ---------------------------------------------------------- */
function flashSaved() {
    if (!els)
        return;
    const s = els.saved;
    s.classList.add('show');
    if (els.savedTimer)
        window.clearTimeout(els.savedTimer);
    els.savedTimer = window.setTimeout(() => s.classList.remove('show'), 1600);
}
/* ---------------------------------------------------------- */
/* resizer                                                     */
/* ---------------------------------------------------------- */
function wireResizer(e) {
    e.resizeHandle = (0, utils_1.el)('div', { cls: 'resize-handle', attrs: { 'aria-hidden': 'true' } });
    e.app.appendChild(e.resizeHandle);
    let dragging = false;
    e.resizeHandle.addEventListener('mousedown', (ev) => {
        dragging = true;
        document.body.classList.add('resizing');
        ev.preventDefault();
    });
    document.addEventListener('mousemove', (ev) => {
        if (!dragging)
            return;
        S.setPaneWidth('sidebar', (0, utils_1.clamp)(ev.clientX, constants_1.C.SIDEBAR.min, constants_1.C.SIDEBAR.max));
    });
    document.addEventListener('mouseup', () => {
        if (!dragging)
            return;
        dragging = false;
        document.body.classList.remove('resizing');
        S.commitLayout();
    });
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"unsloth/Qwen3.8-27B-GGUF","provider":"brigid","persona":"typescript","sessionId":"ac55e9b5-294e-4b57-8d70-0af349472068","tokensIn":6099830,"tokensOut":309769,"tokensTotal":6409599,"cost":0,"turns":69,"toolCalls":99,"failedToolCalls":5,"timestamp":"2026-08-15T15:36:32.686Z"} -->