← Notes results

Notes

Qwen 3.6 27B MTP GGUF UD Q5 K XL · 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 88,651 bytes · SHA-256 cdf93a9fb11f
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes — A Polished Notes Application</title>
<style>
/* ── css: layout ── */
/* Base & Reset */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
  height: 100%; overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
body {
  background: var(--bg, #ffffff);
  color: var(--text, #1a1a2e);
  transition: background 0.3s, color 0.3s;
}
#app { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }

/* Header */
#app-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 16px; background: var(--bg-secondary, #f8f9fa);
  border-bottom: 1px solid var(--border, #e0e2e6); height: 44px;
  flex-shrink: 0; z-index: 100;
}
.app-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
#dark-toggle {
  background: none; border: 1px solid var(--border, #e0e2e6);
  border-radius: 8px; padding: 4px 10px; cursor: pointer; font-size: 15px;
  transition: background 0.15s, transform 0.15s;
}
#dark-toggle:hover { background: var(--bg-hover, #e8eaed); transform: scale(1.05); }

/* Main layout */
#main-content { display: flex; flex: 1; overflow: hidden; }

/* Sidebar */
#sidebar {
  background: var(--bg-secondary, #f8f9fa);
  border-right: 1px solid var(--border, #e0e2e6);
  overflow: hidden;
  transition: width 0.25s cubic-bezier(0.4,0,0.2,1), min-width 0.25s cubic-bezier(0.4,0,0.2,1), max-width 0.25s;
  position: relative;
}
.sidebar-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px 10px; border-bottom: 1px solid var(--border, #e0e2e6); flex-shrink: 0;
}
.sidebar-logo { font-size: 14px; font-weight: 700; letter-spacing: -0.02em; }
.sidebar-toggle {
  background: none; border: none; cursor: pointer; font-size: 11px;
  color: var(--text-tertiary, #9aa0a6); padding: 2px 4px; border-radius: 4px;
  transition: background 0.15s, color 0.15s;
}
.sidebar-toggle:hover { background: var(--bg-hover, #e8eaed); color: var(--text, #1a1a2e); }
.sidebar-btn {
  width: 100%; padding: 6px 14px; margin: 6px 0 0; background: none; border: none;
  text-align: left; font-size: 12px; color: var(--text-secondary, #5f6368);
  cursor: pointer; border-radius: 6px; transition: background 0.15s, color 0.15s; font-weight: 500;
}
.sidebar-btn:hover { background: var(--bg-hover, #e8eaed); color: var(--text, #1a1a2e); }
.sidebar-scroll { flex: 1; overflow-y: auto; padding: 8px 0; }
.sidebar-scroll::-webkit-scrollbar { width: 4px; }
.sidebar-scroll::-webkit-scrollbar-thumb { background: var(--border, #e0e2e6); border-radius: 2px; }

.nav-group-item {
  display: flex; align-items: center; padding: 6px 14px; cursor: pointer;
  font-size: 13px; border-left: 3px solid transparent;
  transition: background 0.12s, border-color 0.12s; gap: 8px; user-select: none;
}
.nav-group-item:hover { background: var(--bg-hover, #e8eaed); }
.nav-group-item.active { background: var(--bg-active, #dde1e7); font-weight: 500; }
.nav-group-icon { font-size: 15px; width: 20px; text-align: center; }
.nav-group-label { flex: 1; }
.nav-group-count { font-size: 11px; color: var(--text-tertiary, #9aa0a6); min-width: 16px; text-align: right; }
.sidebar-separator { height: 1px; background: var(--border, #e0e2e6); margin: 8px 14px; }

/* Notebooks */
.notebook-group { margin: 0; }
.notebook-header {
  display: flex; align-items: center; padding: 5px 14px; cursor: pointer;
  font-size: 13px; border-left: 3px solid transparent;
  transition: background 0.12s, border-color 0.12s; gap: 6px; user-select: none;
}
.notebook-header:hover { background: var(--bg-hover, #e8eaed); }
.notebook-header.active { font-weight: 500; }
.notebook-arrow { font-size: 8px; color: var(--text-tertiary, #9aa0a6); width: 14px; text-align: center; }
.notebook-icon { font-size: 14px; }
.notebook-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notebook-count { font-size: 11px; color: var(--text-tertiary, #9aa0a6); min-width: 16px; text-align: right; }
.notebook-more {
  background: none; border: none; cursor: pointer; font-size: 14px;
  color: var(--text-tertiary, #9aa0a6); padding: 2px 4px; border-radius: 4px;
  opacity: 0; transition: opacity 0.15s, background 0.15s;
}
.notebook-header:hover .notebook-more { opacity: 1; }
.notebook-more:hover { background: var(--bg-active, #dde1e7); }

/* Sidebar expand button */
.sidebar-expand-btn {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 20px; height: 60px; border: none; background: var(--accent, #5b6abf);
  color: white; cursor: pointer; border-radius: 0 4px 4px 0; z-index: 11;
  font-size: 10px; transition: opacity 0.2s;
}

/* Resize handles */
.resize-handle { transition: background 0.2s; }

/* Note list */
#notelist {
  background: var(--bg, #ffffff);
  border-right: 1px solid var(--border, #e0e2e6);
  overflow: hidden;
  transition: width 0.25s cubic-bezier(0.4,0,0.2,1);
}
.notelist-header { padding: 12px 14px; border-bottom: 1px solid var(--border, #e0e2e6); flex-shrink: 0; }
.notelist-title-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.notelist-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.new-note-btn {
  width: 28px; height: 28px; border-radius: 50%; border: none;
  background: var(--accent, #5b6abf); color: white; font-size: 18px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background 0.15s, transform 0.15s; line-height: 1;
}
.new-note-btn:hover { background: var(--accent-hover, #4a59a8); transform: scale(1.1); }

.notelist-controls { display: flex; flex-direction: column; gap: 6px; }
.search-box {
  display: flex; align-items: center; background: var(--bg-secondary, #f8f9fa);
  border: 1px solid var(--border, #e0e2e6); border-radius: 8px;
  padding: 0 8px; transition: border-color 0.15s, box-shadow 0.15s;
}
.search-box:focus-within {
  border-color: var(--accent, #5b6abf);
  box-shadow: 0 0 0 2px var(--accent-light, #e8eaff);
}
.search-icon { font-size: 13px; opacity: 0.5; margin-right: 6px; }
.search-input {
  border: none; background: none; outline: none; font-size: 13px;
  padding: 6px 0; width: 100%; color: var(--text, #1a1a2e); font-family: inherit;
}
.search-input::placeholder { color: var(--text-tertiary, #9aa0a6); }

.active-tag-filter {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; background: var(--tag-bg, #e8eaff);
  color: var(--tag-text, #5b6abf); border-radius: 12px; font-size: 11px;
}
.clear-tag-filter {
  background: none; border: none; cursor: pointer; font-size: 13px;
  color: var(--tag-text, #5b6abf); padding: 0 2px;
}
.sort-controls { display: flex; gap: 2px; }
.sort-btn {
  padding: 3px 8px; border: none; background: none; font-size: 11px;
  color: var(--text-tertiary, #9aa0a6); cursor: pointer; border-radius: 4px;
  transition: background 0.15s, color 0.15s;
}
.sort-btn:hover { background: var(--bg-hover, #e8eaed); color: var(--text, #1a1a2e); }
.sort-btn.active { color: var(--accent, #5b6abf); font-weight: 500; }

/* Note items */
.notelist-items { flex: 1; overflow-y: auto; }
.notelist-items::-webkit-scrollbar { width: 4px; }
.notelist-items::-webkit-scrollbar-thumb { background: var(--border, #e0e2e6); border-radius: 2px; }

.note-item {
  padding: 10px 14px; cursor: pointer; border-left: 3px solid transparent;
  border-bottom: 1px solid var(--border-light, #eef0f2);
  transition: background 0.12s, border-color 0.12s;
}
.note-item:hover { background: var(--bg-hover, #e8eaed); }
.note-item.active { font-weight: 500; }

.note-item-header { display: flex; align-items: center; gap: 4px; margin-bottom: 3px; }
.note-item-title {
  font-size: 13px; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; flex: 1;
}
.pin-badge { font-size: 11px; opacity: 0.7; }
.note-item-menu {
  background: none; border: none; cursor: pointer; font-size: 14px;
  color: var(--text-tertiary, #9aa0a6); padding: 2px 4px; border-radius: 4px;
  opacity: 0; transition: opacity 0.15s, background 0.15s; flex-shrink: 0;
}
.note-item:hover .note-item-menu { opacity: 1; }
.note-item-menu:hover { background: var(--bg-active, #dde1e7); }

.note-item-snippet {
  font-size: 12px; color: var(--text-secondary, #5f6368); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 4px;
}
.note-item-footer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.note-item-time { font-size: 11px; color: var(--text-tertiary, #9aa0a6); }
.note-item-tags { display: flex; gap: 4px; flex-wrap: wrap; }
.note-tag {
  display: inline-block; padding: 1px 6px; background: var(--tag-bg, #e8eaff);
  color: var(--tag-text, #5b6abf); border-radius: 8px; font-size: 10px;
  cursor: pointer; transition: opacity 0.15s;
}
.note-tag:hover { opacity: 0.7; }

/* Empty state */
.notelist-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 40px 20px; text-align: center;
}
.empty-icon { font-size: 36px; margin-bottom: 12px; opacity: 0.5; }
.notelist-empty p { font-size: 13px; color: var(--text-secondary, #5f6368); line-height: 1.5; }
.empty-hint { font-size: 12px !important; color: var(--text-tertiary, #9aa0a6) !important; margin-top: 4px; }

/* Editor */
#editor {
  flex: 1; display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg-editor, #ffffff); position: relative;
}
.editor-toolbar {
  display: flex; align-items: center; padding: 4px 12px; gap: 1px;
  flex-wrap: wrap; border-bottom: 1px solid var(--border, #e0e2e6);
  flex-shrink: 0; min-height: 38px;
}
.toolbar-btn {
  min-width: 28px; height: 28px; display: flex; align-items: center;
  justify-content: center; padding: 0 6px; border: none; border-radius: 4px;
  cursor: pointer; font-size: 12px; transition: background 0.12s, color 0.12s;
  user-select: none;
}
.toolbar-btn:hover { opacity: 0.85; }
.toolbar-btn.active { box-shadow: inset 0 0 0 1px currentColor; }

.editor-title {
  border: none; outline: none; font-size: 24px; font-weight: 600;
  padding: 16px 28px 4px; width: 100%; flex-shrink: 0;
  background: transparent; letter-spacing: -0.02em;
}
.editor-title::placeholder { color: var(--text-tertiary, #9aa0a6); }

.editor-tags {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 28px 8px; flex-wrap: wrap;
}
.tag-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 12px; font-size: 12px; cursor: default;
  transition: opacity 0.15s;
}
.tag-remove { cursor: pointer; font-size: 14px; line-height: 1; opacity: 0.6; transition: opacity 0.15s; }
.tag-remove:hover { opacity: 1; }
.tag-input { border: none; background: transparent; outline: none; font-size: 12px; }
.tag-input::placeholder { color: var(--text-tertiary, #9aa0a6); }

.editor-content {
  flex: 1; overflow-y: auto; padding: 8px 28px 60px; outline: none;
  font-family: Georgia, Cambria, 'Times New Roman', serif;
  font-size: 15px; line-height: 1.7; color: var(--text, #1a1a2e);
}
.editor-content::-webkit-scrollbar { width: 6px; }
.editor-content::-webkit-scrollbar-thumb { background: var(--border, #e0e2e6); border-radius: 3px; }
.editor-content::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary, #9aa0a6); }

/* Editor typography */
.editor-content h1 { font-size: 1.8em; font-weight: 700; margin: 0.5em 0 0.3em; letter-spacing: -0.02em; line-height: 1.3; }
.editor-content h2 { font-size: 1.4em; font-weight: 600; margin: 0.5em 0 0.3em; letter-spacing: -0.01em; line-height: 1.3; }
.editor-content h3 { font-size: 1.15em; font-weight: 600; margin: 0.4em 0 0.2em; line-height: 1.4; }
.editor-content p { margin: 0.4em 0; }
.editor-content ul, .editor-content ol { padding-left: 1.5em; margin: 0.3em 0; }
.editor-content li { margin: 0.2em 0; }
.editor-content blockquote {
  border-left: 3px solid var(--accent, #5b6abf); margin: 0.5em 0;
  padding: 0.3em 0 0.3em 1em; color: var(--text-secondary, #5f6368); font-style: italic;
}
.editor-content hr { border: none; border-top: 1px solid var(--border, #e0e2e6); margin: 1em 0; }
.editor-content code {
  background: var(--bg-tertiary, #f0f1f3); padding: 2px 5px; border-radius: 4px;
  font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; font-size: 0.88em;
  color: var(--danger, #d93025);
}
.editor-content pre {
  background: var(--bg-tertiary, #f0f1f3); padding: 12px 16px; border-radius: 8px;
  overflow-x: auto; font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
  font-size: 0.88em; line-height: 1.5; margin: 0.5em 0;
}

.editor-content:empty::before {
  content: attr(data-placeholder); color: var(--text-tertiary, #9aa0a6);
  pointer-events: none; font-style: italic;
}

.editor-empty-state {
  display: none; flex-direction: column; align-items: center; justify-content: center;
  flex: 1; padding: 40px; text-align: center;
}
.empty-state-icon { font-size: 56px; margin-bottom: 16px; opacity: 0.3; }
.editor-empty-state h3 { font-size: 18px; font-weight: 500; color: var(--text-secondary, #5f6368); margin-bottom: 8px; }
.editor-empty-state p { font-size: 14px; color: var(--text-tertiary, #9aa0a6); line-height: 1.5; }

/* Save indicator */
.save-indicator {
  position: absolute; bottom: 8px; right: 20px; font-size: 11px;
  color: var(--text-tertiary, #9aa0a6); transition: opacity 0.3s;
  opacity: 0; pointer-events: none; z-index: 10;
}

/* Context menu */
.context-menu { animation: contextMenuFadeIn 0.12s ease-out; }
@keyframes contextMenuFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
.context-menu-item:hover { filter: brightness(0.95); }

/* Checklist checkboxes */
.checklist-checkbox {
  margin-right: 6px; cursor: pointer; user-select: none;
  display: inline-block; vertical-align: middle; font-size: 15px; line-height: 1;
}
.checklist-checkbox.checked { color: var(--accent, #5b6abf); }

/* Search highlight */
.editor-content mark { background: var(--search-highlight, #fff3cd); color: inherit; padding: 0 2px; border-radius: 2px; }

/* Dark theme overrides */
.dark-theme .editor-content blockquote { border-left-color: var(--accent, #7c8cf8); }
.dark-theme .editor-content code { background: #2a2a40; color: #f25555; }
.dark-theme .editor-content pre { background: #2a2a40; }
.dark-theme .editor-empty-state h3 { color: var(--text-secondary, #a0a0b8); }
.dark-theme .editor-empty-state p { color: var(--text-tertiary, #6e6e88); }

/* Smooth transitions */
#app-inner, #sidebar, #notelist, #editor, .notelist-header,
.sidebar-header, .editor-toolbar, .search-box {
  transition: background-color 0.3s, border-color 0.3s;
}

/* Selection */
.editor-content ::selection { background: var(--accent, #5b6abf); color: white; opacity: 0.3; }

/* Scrollbar for note list and sidebar */
.notelist-items::-webkit-scrollbar { width: 4px; }
.notelist-items::-webkit-scrollbar-thumb { background: var(--border, #e0e2e6); border-radius: 2px; }
</style>
</head>
<body>
<div id="app-inner">
  <header id="app-header">
    <h1 class="app-title">📝 Notes</h1>
    <button id="dark-toggle" title="Toggle dark mode">🌙</button>
  </header>
  <div id="main-content">
    <div id="sidebar"></div>
    <div id="notelist"></div>
    <div id="editor"></div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./sidebar":"src/sidebar.ts","./notelist":"src/notelist.ts","./editor":"src/editor.ts","./state":"src/state.ts","./resizer":"src/resizer.ts","./constants":"src/constants.ts"},"src/model.ts":{"./constants":"src/constants.ts"},"src/utils.ts":{"./constants":"src/constants.ts"},"src/state.ts":{"./model":"src/model.ts","./utils":"src/utils.ts","./constants":"src/constants.ts"},"src/sidebar.ts":{"./state":"src/state.ts","./constants":"src/constants.ts"},"src/notelist.ts":{"./state":"src/state.ts","./utils":"src/utils.ts","./constants":"src/constants.ts","./model":"src/model.ts"},"src/editor.ts":{"./state":"src/state.ts","./utils":"src/utils.ts","./constants":"src/constants.ts"},"src/resizer.ts":{"./state":"src/state.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";
Object.defineProperty(exports, "__esModule", { value: true });
// Entry module — wires everything together
const sidebar_1 = require("./sidebar");
const notelist_1 = require("./notelist");
const editor_1 = require("./editor");
const state_1 = require("./state");
const resizer_1 = require("./resizer");
const constants_1 = require("./constants");
// Expose actions to window for inline handlers and testing
window.setSidebarCollapsed = state_1.setSidebarCollapsed;
window.createNewNote = state_1.createNewNote;
window.renameNotebook = state_1.renameNotebook;
window.togglePin = state_1.togglePin;
window.restoreNote = state_1.restoreNote;
window.permanentDelete = state_1.permanentDelete;
window.moveNoteTo = state_1.moveNote;
window.forceSave = state_1.forceSave;
window.setDarkMode = state_1.setDarkMode;
window.trashNote = state_1.trashNote;
window.directUpdateNoteContent = state_1.directUpdateNoteContent;
// Wait for DOM to be ready
function init() {
    const sidebar = document.getElementById('sidebar');
    const notelist = document.getElementById('notelist');
    const editor = document.getElementById('editor');
    const darkToggle = document.getElementById('dark-toggle');
    // Init panels
    (0, sidebar_1.initSidebar)(sidebar);
    (0, notelist_1.initNoteList)(notelist);
    (0, editor_1.initEditor)(editor);
    // Resizable panes
    (0, resizer_1.initResizable)();
    // Drag and drop for notes
    (0, resizer_1.initDragDrop)();
    // Dark mode toggle
    darkToggle.addEventListener('click', () => {
        const state = (0, state_1.getState)();
        window.setDarkMode?.(!state.darkMode);
    });
    // Keyboard shortcuts
    document.addEventListener('keydown', (e) => {
        if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
            e.preventDefault();
            (0, state_1.createNewNote)();
            setTimeout(editor_1.focusTitle, 50);
        }
        if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
            e.preventDefault();
            const searchInput = document.querySelector('.search-input');
            if (searchInput) {
                searchInput.focus();
                searchInput.select();
            }
        }
    });
    // Apply initial theme
    applyTheme();
    (0, state_1.subscribe)(applyTheme);
}
function applyTheme() {
    const state = (0, state_1.getState)();
    const dark = state.darkMode;
    document.body.className = dark ? 'dark-theme' : 'light-theme';
    const colors = dark ? constants_1.COLORS.dark : constants_1.COLORS.light;
    document.documentElement.style.setProperty('--bg', colors.bg);
    document.documentElement.style.setProperty('--bg-secondary', colors.bgSecondary);
    document.documentElement.style.setProperty('--text', colors.text);
    document.documentElement.style.setProperty('--text-secondary', colors.textSecondary);
    document.documentElement.style.setProperty('--border', colors.border);
    document.documentElement.style.setProperty('--accent', colors.accent);
    const toggle = document.getElementById('dark-toggle');
    if (toggle) {
        toggle.textContent = dark ? '☀️' : '🌙';
    }
}
// Start
init();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ALLOWED_ATTRS = exports.SUPPORTED_HTML_TAGS = exports.DEFAULT_NOTEBOOKS = exports.STORAGE_KEY = exports.TRASH_AUTO_DELETE_DAYS = exports.SNIPPET_LENGTH = exports.AUTOSAVE_DELAY = exports.FONTS = exports.COLORS = void 0;
// Tunable constants
exports.COLORS = {
    light: {
        bg: '#ffffff',
        bgSecondary: '#f8f9fa',
        bgTertiary: '#f0f1f3',
        bgHover: '#e8eaed',
        bgActive: '#dde1e7',
        bgEditor: '#ffffff',
        text: '#1a1a2e',
        textSecondary: '#5f6368',
        textTertiary: '#9aa0a6',
        border: '#e0e2e6',
        borderLight: '#eef0f2',
        accent: '#5b6abf',
        accentHover: '#4a59a8',
        accentLight: '#e8eaff',
        danger: '#d93025',
        dangerHover: '#c5221f',
        success: '#1e8e3e',
        tagBg: '#e8eaff',
        tagText: '#5b6abf',
        pinColor: '#f9a825',
        shadow: 'rgba(0,0,0,0.08)',
        toolbarBg: '#fafbfc',
        checkboxUnchecked: '#dadce0',
        checkboxChecked: '#5b6abf',
        searchHighlight: '#fff3cd',
        sidebarWidth: 240,
        noteListWidth: 280,
        editorWidth: 'auto',
    },
    dark: {
        bg: '#1e1e2e',
        bgSecondary: '#252537',
        bgTertiary: '#2a2a3d',
        bgHover: '#33334d',
        bgActive: '#3a3a55',
        bgEditor: '#1e1e2e',
        text: '#e0e0f0',
        textSecondary: '#a0a0b8',
        textTertiary: '#6e6e88',
        border: '#363650',
        borderLight: '#2e2e45',
        accent: '#7c8cf8',
        accentHover: '#6b7bef',
        accentLight: '#2a2a50',
        danger: '#f25555',
        dangerHover: '#e04040',
        success: '#4ade80',
        tagBg: '#2a2a50',
        tagText: '#7c8cf8',
        pinColor: '#fbbf24',
        shadow: 'rgba(0,0,0,0.3)',
        toolbarBg: '#252537',
        checkboxUnchecked: '#444460',
        checkboxChecked: '#7c8cf8',
        searchHighlight: '#4a4a20',
        sidebarWidth: 240,
        noteListWidth: 280,
        editorWidth: 'auto',
    },
};
exports.FONTS = {
    ui: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    editor: "'Georgia', 'Cambria', 'Times New Roman', serif",
    editorSans: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    mono: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
};
exports.AUTOSAVE_DELAY = 500;
exports.SNIPPET_LENGTH = 120;
exports.TRASH_AUTO_DELETE_DAYS = 30;
exports.STORAGE_KEY = 'notes-app-data';
exports.DEFAULT_NOTEBOOKS = [
    { id: 'nb-default', name: 'My Notes', icon: '📝' },
    { id: 'nb-personal', name: 'Personal', icon: '📓' },
    { id: 'nb-work', name: 'Work', icon: '💼' },
];
exports.SUPPORTED_HTML_TAGS = new Set([
    'p', 'br', 'strong', 'b', 'em', 'i', 'u', 's', 'strike', 'del',
    'code', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
    'ul', 'ol', 'li', 'blockquote', 'hr', 'div', 'span',
    'a', 'sub', 'sup',
]);
exports.ALLOWED_ATTRS = new Set([
    'href', 'title', 'target', 'rel', 'checked',
    'class', 'contenteditable', 'data-handler', 'data-placeholder',
]);
};

// ── module: src/model.ts ──
__mods["src/model.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createId = createId;
exports.createNote = createNote;
exports.createNotebook = createNotebook;
exports.buildDefaultState = buildDefaultState;
exports.saveState = saveState;
exports.loadState = loadState;
// Data model — single source of truth
const constants_1 = require("./constants");
function createId() {
    return Date.now().toString(36) + Math.random().toString(36).slice(2, 9);
}
function createNote(notebookId) {
    const now = Date.now();
    return {
        id: createId(),
        notebookId,
        originalNotebookId: notebookId,
        title: '',
        content: '',
        tags: [],
        pinned: false,
        created: now,
        updated: now,
        trashed: false,
    };
}
function createNotebook(name, icon) {
    return {
        id: createId(),
        name,
        icon,
        collapsed: false,
    };
}
function buildDefaultState() {
    const nb = constants_1.DEFAULT_NOTEBOOKS.map(n => ({ ...n, collapsed: false }));
    const defaultNbId = nb[0]?.id ?? '';
    const welcomeNote = {
        id: createId(),
        notebookId: defaultNbId,
        originalNotebookId: defaultNbId,
        title: 'Welcome to Notes',
        content: '<h2>Welcome! 🎉</h2><p>This is a <strong>rich text editor</strong>. Try the formatting toolbar above.</p><p>Some things you can do:</p><ul><li>Write <strong>bold</strong>, <em>italic</em>, and <u>underlined</u> text</li><li>Create bulleted and numbered lists</li><li>Use <code>inline code</code></li><li>Add checklists with checkboxes</li></ul><p><strong>Markdown shortcuts:</strong></p><ul><li>Type <code># </code> for a heading</li><li>Type <code>- </code> for a bullet</li><li>Type <code>[] </code> for a checklist</li><li>Type <code>&gt; </code> for a blockquote</li></ul><hr><p><em>Start typing to create your first note!</em></p>',
        tags: ['welcome', 'getting-started'],
        pinned: true,
        created: Date.now(),
        updated: Date.now(),
        trashed: false,
    };
    return {
        notebooks: nb,
        notes: [welcomeNote],
        activeNotebookId: 'all',
        activeNoteId: welcomeNote.id,
        tagFilter: null,
        sortBy: 'modified',
        darkMode: false,
        sidebarCollapsed: false,
        paneSizes: { sidebar: 240, noteList: 280 },
        searchQuery: '',
    };
}
function saveState(state) {
    try {
        localStorage.setItem(constants_1.STORAGE_KEY, JSON.stringify(state));
    }
    catch {
        // Storage full or unavailable
    }
}
function loadState() {
    try {
        const raw = localStorage.getItem(constants_1.STORAGE_KEY);
        if (raw) {
            const parsed = JSON.parse(raw);
            // Ensure all fields exist (migration safety)
            return {
                ...buildDefaultState(),
                ...parsed,
                paneSizes: {
                    ...buildDefaultState().paneSizes,
                    ...(parsed.paneSizes ?? {}),
                },
            };
        }
    }
    catch {
        // Invalid data — start fresh
    }
    return buildDefaultState();
}
};

// ── module: src/utils.ts ──
__mods["src/utils.ts"] = function (exports, require, module) {
"use strict";
// Pure text extraction and sanitization utilities
Object.defineProperty(exports, "__esModule", { value: true });
exports.stripHtml = stripHtml;
exports.getSnippet = getSnippet;
exports.sanitizeHtml = sanitizeHtml;
exports.searchText = searchText;
exports.highlightText = highlightText;
exports.formatTime = formatTime;
exports.formatFullDate = formatFullDate;
const constants_1 = require("./constants");
// Strip all HTML tags, decode entities — pure text only
function stripHtml(html) {
    // Create a temporary element to parse HTML
    const div = document.createElement('div');
    div.innerHTML = html;
    return (div.textContent ?? div.innerText ?? '') || '';
}
// Extract text snippet for the note list
function getSnippet(html, length = constants_1.SNIPPET_LENGTH) {
    const text = stripHtml(html);
    if (text.length <= length)
        return text;
    const end = text.lastIndexOf(' ', length);
    const cut = end > 0 ? end : length;
    return text.slice(0, cut) + '…';
}
// Sanitize HTML — remove unsupported tags and attributes
function sanitizeHtml(html) {
    if (!html || typeof html !== 'string')
        return '';
    try {
        // Use a div approach instead of DOMParser to avoid issues with body being null
        const div = document.createElement('div');
        div.innerHTML = html;
        // Sanitize in place
        sanitizeNodeInPlace(div);
        return div.innerHTML;
    }
    catch {
        // Fallback: strip all HTML
        return html.replace(/<[^>]*>/g, '');
    }
}
function sanitizeNodeInPlace(parent) {
    const children = Array.from(parent.childNodes);
    for (let i = children.length - 1; i >= 0; i--) {
        const node = children[i];
        if (node.nodeType !== Node.ELEMENT_NODE)
            continue;
        const element = node;
        const tag = element.tagName.toLowerCase();
        // Remove dangerous/unsupported tags
        if (DANGEROUS_TAGS.has(tag)) {
            // Replace with text content
            const text = element.textContent;
            if (text) {
                parent.insertBefore(document.createTextNode(text), element);
            }
            parent.removeChild(element);
            continue;
        }
        if (!constants_1.SUPPORTED_HTML_TAGS.has(tag)) {
            // Unwrap: move children before the element
            const nodeChildren = Array.from(element.childNodes);
            for (let j = nodeChildren.length - 1; j >= 0; j--) {
                parent.insertBefore(nodeChildren[j], element);
            }
            parent.removeChild(element);
            // Re-process the inserted children (they're now siblings)
            // Since we go backwards, they're already handled above
            continue;
        }
        // Sanitize attributes
        const attrs = Array.from(element.attributes);
        for (const attr of attrs) {
            if (!constants_1.ALLOWED_ATTRS.has(attr.name)) {
                element.removeAttribute(attr.name);
            }
            else if (attr.name === 'href' || attr.name === 'src') {
                if (/^(javascript:|data:|vbscript:)/i.test(attr.value)) {
                    element.removeAttribute(attr.name);
                }
            }
        }
        // Recurse
        sanitizeNodeInPlace(element);
    }
}
const DANGEROUS_TAGS = new Set([
    'script', 'style', 'iframe', 'object', 'embed', 'form',
    'input', 'textarea', 'select', 'link', 'meta', 'base', 'head', 'html',
]);
function sanitizeNode(doc, node) {
    if (node.nodeType !== Node.ELEMENT_NODE) {
        // For text nodes and comments, nothing to sanitize
        return;
    }
    const element = node;
    const tag = element.tagName.toLowerCase();
    if (tag === 'script' || tag === 'style' || tag === 'iframe' || tag === 'object' || tag === 'embed' || tag === 'form' || tag === 'input' || tag === 'textarea' || tag === 'select' || tag === 'link' || tag === 'meta' || tag === 'base' || tag === 'head' || tag === 'html') {
        // Remove dangerous tags entirely — replace with text content
        const parent = element.parentNode;
        if (!parent)
            return; // Already detached
        const text = element.textContent;
        if (text) {
            parent.insertBefore(doc.createTextNode(text), element);
        }
        parent.removeChild(element);
        return;
    }
    if (!constants_1.SUPPORTED_HTML_TAGS.has(tag)) {
        // Unsupported tag — unwrap (keep children, discard tag)
        const parent = element.parentNode;
        if (!parent)
            return;
        const children = Array.from(element.childNodes);
        for (let i = children.length - 1; i >= 0; i--) {
            parent.insertBefore(children[i], element);
        }
        parent.removeChild(element);
        return;
    }
    // Sanitize attributes
    const attrs = Array.from(element.attributes);
    for (const attr of attrs) {
        if (!constants_1.ALLOWED_ATTRS.has(attr.name)) {
            element.removeAttribute(attr.name);
        }
        else if (attr.name === 'href' || attr.name === 'src') {
            const val = attr.value;
            if (/^(javascript:|data:|vbscript:)/i.test(val)) {
                element.removeAttribute(attr.name);
            }
        }
    }
    // Recursively process children (iterate in reverse to handle mutations safely)
    const children = Array.from(element.childNodes);
    for (let i = children.length - 1; i >= 0; i--) {
        sanitizeNode(doc, children[i]);
    }
}
// Extract plain text from HTML for search — ignores tag names
function searchText(html) {
    return stripHtml(html).toLowerCase();
}
// Highlight matched text in a string for display
function highlightText(text, query) {
    if (!query)
        return escapeHtml(text);
    const escaped = escapeHtml(text);
    const escapedQuery = escapeHtml(query);
    const regex = new RegExp(`(${escapedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
    return escaped.replace(regex, '<mark>$1</mark>');
}
function escapeHtml(str) {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// Format a timestamp nicely
function formatTime(timestamp) {
    const now = Date.now();
    const diff = now - timestamp;
    const minute = 60 * 1000;
    const hour = 60 * minute;
    const day = 24 * hour;
    if (diff < minute)
        return 'Just now';
    if (diff < hour)
        return `${Math.floor(diff / minute)}m ago`;
    if (diff < day)
        return `${Math.floor(diff / hour)}h ago`;
    if (diff < 7 * day)
        return `${Math.floor(diff / day)}d ago`;
    const d = new Date(timestamp);
    return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: d.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined });
}
function formatFullDate(timestamp) {
    return new Date(timestamp).toLocaleDateString(undefined, {
        year: 'numeric', month: 'long', day: 'numeric',
        hour: '2-digit', minute: '2-digit',
    });
}
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.subscribe = subscribe;
exports.getState = getState;
exports.forceSave = forceSave;
exports.directUpdateNoteContent = directUpdateNoteContent;
exports.getNoteById = getNoteById;
exports.setActiveNotebook = setActiveNotebook;
exports.setActiveNote = setActiveNote;
exports.setSortBy = setSortBy;
exports.setDarkMode = setDarkMode;
exports.setSidebarCollapsed = setSidebarCollapsed;
exports.setPaneSizes = setPaneSizes;
exports.setSearchQuery = setSearchQuery;
exports.setTagFilter = setTagFilter;
exports.createNewNote = createNewNote;
exports.updateNote = updateNote;
exports.renameNotebook = renameNotebook;
exports.deleteNotebook = deleteNotebook;
exports.toggleNotebookCollapse = toggleNotebookCollapse;
exports.trashNote = trashNote;
exports.restoreNote = restoreNote;
exports.permanentDelete = permanentDelete;
exports.duplicateNote = duplicateNote;
exports.moveNote = moveNote;
exports.getFilteredNotes = getFilteredNotes;
exports.getActiveNote = getActiveNote;
exports.getNoteCount = getNoteCount;
exports.getTrashCount = getTrashCount;
exports.togglePin = togglePin;
exports.addNotebook = addNotebook;
// State management — actions, subscriptions, and state mutations
const model_1 = require("./model");
const utils_1 = require("./utils");
let state = (0, model_1.loadState)();
let autosaveTimer = null;
const subscribers = [];
function subscribe(fn) {
    subscribers.push(fn);
    return () => {
        const idx = subscribers.indexOf(fn);
        if (idx >= 0)
            subscribers.splice(idx, 1);
    };
}
function getState() {
    return state;
}
function notify() {
    for (const fn of subscribers)
        fn();
}
function scheduleSave() {
    if (autosaveTimer !== null)
        clearTimeout(autosaveTimer);
    autosaveTimer = setTimeout(() => {
        (0, model_1.saveState)(state);
        autosaveTimer = null;
    }, 200);
}
function forceSave() {
    if (autosaveTimer !== null)
        clearTimeout(autosaveTimer);
    (0, model_1.saveState)(state);
    autosaveTimer = null;
}
// Debug: expose raw state mutation for testing
function directUpdateNoteContent(noteId, content) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    note.content = (0, utils_1.sanitizeHtml)(content);
    note.updated = Date.now();
    notify();
    scheduleSave();
}
function getNoteById(noteId) {
    return state.notes.find(n => n.id === noteId);
}
// --- Actions ---
function setActiveNotebook(notebookId) {
    state.activeNotebookId = notebookId;
    state.activeNoteId = null;
    state.tagFilter = null;
    notify();
    scheduleSave();
}
function setActiveNote(noteId) {
    state.activeNoteId = noteId;
    notify();
}
function setSortBy(sortBy) {
    state.sortBy = sortBy;
    notify();
    scheduleSave();
}
function setDarkMode(dark) {
    state.darkMode = dark;
    notify();
    scheduleSave();
}
function setSidebarCollapsed(collapsed) {
    state.sidebarCollapsed = collapsed;
    notify();
    scheduleSave();
}
function setPaneSizes(paneSizes) {
    state.paneSizes = { ...paneSizes };
    notify();
    scheduleSave();
}
function setSearchQuery(query) {
    state.searchQuery = query;
    notify();
}
function setTagFilter(tag) {
    state.tagFilter = tag;
    notify();
}
function createNewNote(notebookId) {
    const targetNotebook = notebookId ?? state.activeNotebookId;
    if (targetNotebook === 'all' || targetNotebook === 'trash') {
        // Create in the first real notebook
        const firstReal = state.notebooks[0];
        if (!firstReal)
            return (0, model_1.createNote)('');
        state.activeNotebookId = firstReal.id;
    }
    const note = (0, model_1.createNote)(state.activeNotebookId);
    state.notes.push(note);
    state.activeNoteId = note.id;
    notify();
    scheduleSave();
    return note;
}
function updateNote(noteId, updates) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    if (updates.title !== undefined)
        note.title = updates.title;
    if (updates.content !== undefined) {
        note.content = (0, utils_1.sanitizeHtml)(updates.content);
    }
    if (updates.tags !== undefined)
        note.tags = updates.tags;
    if (updates.pinned !== undefined)
        note.pinned = updates.pinned;
    note.updated = Date.now();
    notify();
    scheduleSave();
}
function renameNotebook(notebookId, name) {
    const nb = state.notebooks.find(n => n.id === notebookId);
    if (!nb)
        return;
    nb.name = name;
    notify();
    scheduleSave();
}
function deleteNotebook(notebookId) {
    const idx = state.notebooks.findIndex(n => n.id === notebookId);
    if (idx < 0)
        return;
    state.notebooks.splice(idx, 1);
    // Move notes to the first remaining notebook, or trash them
    const remaining = state.notebooks[0];
    const notes = state.notes.filter(n => n.notebookId === notebookId && !n.trashed);
    for (const note of notes) {
        if (remaining) {
            note.notebookId = remaining.id;
        }
        else {
            trashNote(note.id);
        }
    }
    if (state.activeNotebookId === notebookId) {
        state.activeNotebookId = remaining?.id ?? 'all';
        state.activeNoteId = null;
    }
    notify();
    scheduleSave();
}
function toggleNotebookCollapse(notebookId) {
    const nb = state.notebooks.find(n => n.id === notebookId);
    if (!nb)
        return;
    nb.collapsed = !nb.collapsed;
    notify();
    scheduleSave();
}
function trashNote(noteId) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    note.trashed = true;
    if (state.activeNoteId === noteId)
        state.activeNoteId = null;
    notify();
    scheduleSave();
}
function restoreNote(noteId) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    note.trashed = false;
    // Restore to original notebook if it exists, otherwise first notebook
    if (!state.notebooks.find(n => n.id === note.originalNotebookId)) {
        const first = state.notebooks[0];
        if (first)
            note.notebookId = first.id;
    }
    else {
        note.notebookId = note.originalNotebookId;
    }
    notify();
    scheduleSave();
}
function permanentDelete(noteId) {
    const idx = state.notes.findIndex(n => n.id === noteId);
    if (idx < 0)
        return;
    state.notes.splice(idx, 1);
    if (state.activeNoteId === noteId)
        state.activeNoteId = null;
    notify();
    scheduleSave();
}
function duplicateNote(noteId) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    const copy = {
        ...note,
        id: (0, model_1.createId)(),
        title: note.title + ' (copy)',
        tags: [...note.tags],
        pinned: false,
        trashed: false,
        created: Date.now(),
        updated: Date.now(),
    };
    state.notes.push(copy);
    state.activeNoteId = copy.id;
    notify();
    scheduleSave();
}
function moveNote(noteId, targetNotebookId) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    note.notebookId = targetNotebookId;
    note.updated = Date.now();
    notify();
    scheduleSave();
}
function getFilteredNotes() {
    let notes;
    if (state.activeNotebookId === 'all') {
        notes = state.notes.filter(n => !n.trashed);
    }
    else if (state.activeNotebookId === 'trash') {
        notes = state.notes.filter(n => n.trashed);
    }
    else {
        notes = state.notes.filter(n => n.notebookId === state.activeNotebookId && !n.trashed);
    }
    // Tag filter
    if (state.tagFilter) {
        const tag = state.tagFilter;
        notes = notes.filter(n => n.tags.includes(tag));
    }
    // Search
    if (state.searchQuery) {
        const q = state.searchQuery.toLowerCase();
        notes = notes.filter(n => n.title.toLowerCase().includes(q) ||
            (n.content && stripHtmlForSearch(n.content).includes(q)));
    }
    // Sort: pinned first, then by sort order
    const pinned = notes.filter(n => n.pinned);
    const unpinned = notes.filter(n => !n.pinned);
    const sortFn = (a, b) => {
        switch (state.sortBy) {
            case 'modified': return b.updated - a.updated;
            case 'created': return b.created - a.created;
            case 'alphabetical': return a.title.localeCompare(b.title);
            default: return b.updated - a.updated;
        }
    };
    pinned.sort(sortFn);
    unpinned.sort(sortFn);
    return [...pinned, ...unpinned];
}
function stripHtmlForSearch(html) {
    const div = document.createElement('div');
    div.innerHTML = html;
    return (div.textContent ?? '').toLowerCase();
}
function getActiveNote() {
    if (!state.activeNoteId)
        return null;
    return state.notes.find(n => n.id === state.activeNoteId) ?? null;
}
function getNoteCount(notebookId) {
    return state.notes.filter(n => n.notebookId === notebookId && !n.trashed).length;
}
function getTrashCount() {
    return state.notes.filter(n => n.trashed).length;
}
function togglePin(noteId) {
    const note = state.notes.find(n => n.id === noteId);
    if (!note)
        return;
    note.pinned = !note.pinned;
    note.updated = Date.now();
    notify();
    scheduleSave();
}
function addNotebook(name, icon) {
    const nb = (0, model_1.createNotebook)(name, icon);
    state.notebooks.push(nb);
    state.activeNotebookId = nb.id;
    notify();
    scheduleSave();
    return nb;
}
};

// ── module: src/sidebar.ts ──
__mods["src/sidebar.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initSidebar = initSidebar;
exports.destroySidebar = destroySidebar;
// Sidebar rendering — notebooks list, All Notes, Trash
const state_1 = require("./state");
const constants_1 = require("./constants");
let container;
let unsubscribe;
function initSidebar(el) {
    container = el;
    render();
    unsubscribe = (0, state_1.subscribe)(render);
}
function destroySidebar() {
    unsubscribe();
}
function render() {
    const state = (0, state_1.getState)();
    const collapsed = state.sidebarCollapsed;
    container.innerHTML = '';
    container.style.width = collapsed ? '0' : `${state.paneSizes.sidebar}px`;
    container.style.minWidth = collapsed ? '0' : `${state.paneSizes.sidebar}px`;
    container.style.maxWidth = collapsed ? '0' : `${state.paneSizes.sidebar}px`;
    container.style.overflow = 'hidden';
    if (collapsed)
        return;
    container.style.display = 'flex';
    container.style.flexDirection = 'column';
    container.style.height = '100%';
    // Header
    const header = document.createElement('div');
    header.className = 'sidebar-header';
    header.innerHTML = `
    <div class="sidebar-logo">📝 Notes</div>
    <button class="sidebar-toggle" title="Collapse sidebar">◀</button>
  `;
    header.querySelector('.sidebar-toggle')?.addEventListener('click', () => {
        window.setSidebarCollapsed?.(true);
    });
    container.append(header);
    // New notebook button
    const newNbBtn = document.createElement('button');
    newNbBtn.className = 'sidebar-btn new-notebook-btn';
    newNbBtn.textContent = '+ New Notebook';
    newNbBtn.addEventListener('click', handleNewNotebook);
    container.append(newNbBtn);
    // Scrollable area
    const scrollArea = document.createElement('div');
    scrollArea.className = 'sidebar-scroll';
    // All Notes
    scrollArea.append(makeNavGroupItem('all', '📋', 'All Notes', state.notes.filter(n => !n.trashed).length));
    scrollArea.append(makeNavGroupItem('trash', '🗑️', 'Trash', (0, state_1.getTrashCount)()));
    // Separator
    const sep = document.createElement('div');
    sep.className = 'sidebar-separator';
    scrollArea.append(sep);
    // Notebooks
    for (const nb of state.notebooks) {
        scrollArea.append(makeNotebookItem(nb));
    }
    container.append(scrollArea);
}
function makeNavGroupItem(id, icon, label, count) {
    const state = (0, state_1.getState)();
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    const active = state.activeNotebookId === id;
    const item = document.createElement('div');
    item.className = `nav-group-item${active ? ' active' : ''}`;
    if (active)
        item.style.borderLeftColor = colors.accent;
    item.innerHTML = `<span class="nav-group-icon">${icon}</span><span class="nav-group-label">${label}</span><span class="nav-group-count">${count}</span>`;
    item.addEventListener('click', () => (0, state_1.setActiveNotebook)(id));
    return item;
}
function makeNotebookItem(nb) {
    const state = (0, state_1.getState)();
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    const active = state.activeNotebookId === nb.id;
    const count = (0, state_1.getNoteCount)(nb.id);
    const wrapper = document.createElement('div');
    wrapper.className = 'notebook-group';
    const header = document.createElement('div');
    header.className = `notebook-header${active ? ' active' : ''}`;
    if (active) {
        header.style.backgroundColor = colors.bgActive;
        header.style.borderLeftColor = colors.accent;
    }
    header.innerHTML = `
    <span class="notebook-arrow">${nb.collapsed ? '▶' : '▼'}</span>
    <span class="notebook-icon">${nb.icon}</span>
    <span class="notebook-name">${escapeHtml(nb.name)}</span>
    <span class="notebook-count">${count}</span>
    <button class="notebook-more" title="More options">⋯</button>
  `;
    header.addEventListener('click', () => (0, state_1.setActiveNotebook)(nb.id));
    header.querySelector('.notebook-more')?.addEventListener('click', (e) => {
        e.stopPropagation();
        const me = e;
        handleNotebookMenu(nb.id, me);
    });
    wrapper.append(header);
    return wrapper;
}
function handleNotebookMenu(notebookId, e) {
    const state = (0, state_1.getState)();
    const nb = state.notebooks.find(n => n.id === notebookId);
    if (!nb)
        return;
    // Remove existing
    document.querySelector('.context-menu')?.remove();
    const menu = document.createElement('div');
    menu.className = 'context-menu';
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    Object.assign(menu.style, {
        position: 'fixed', zIndex: '1000', minWidth: '160px',
        backgroundColor: colors.bgSecondary,
        border: `1px solid ${colors.border}`,
        borderRadius: '8px',
        boxShadow: `0 4px 12px ${colors.shadow}`,
        fontFamily: constants_1.FONTS.ui, fontSize: '13px',
        top: `${e.clientY}px`, left: `${e.clientX}px`,
    });
    const items = [
        { label: 'Rename', action: () => renameNotebookDialog(notebookId) },
        { label: 'New Note', action: () => { (0, state_1.setActiveNotebook)(notebookId); window.createNewNote?.(); } },
        { label: 'Delete', danger: true, action: () => { if (confirm(`Delete "${nb.name}"? Notes will be moved to the first notebook.`))
                (0, state_1.deleteNotebook)(notebookId); } },
    ];
    for (const item of items) {
        const btn = document.createElement('div');
        btn.className = 'context-menu-item';
        btn.textContent = item.label;
        btn.style.cssText = `padding:8px 12px;cursor:pointer;color:${item.danger ? colors.danger : 'inherit'}`;
        btn.addEventListener('mouseenter', () => btn.style.backgroundColor = colors.bgHover);
        btn.addEventListener('mouseleave', () => btn.style.backgroundColor = 'transparent');
        btn.addEventListener('click', () => { item.action(); menu.remove(); });
        menu.append(btn);
    }
    document.body.append(menu);
    setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
}
function renameNotebookDialog(notebookId) {
    const state = (0, state_1.getState)();
    const nb = state.notebooks.find(n => n.id === notebookId);
    if (!nb)
        return;
    const name = prompt('Notebook name:', nb.name);
    if (name && name.trim()) {
        // Need to import renameNotebook
        window.renameNotebook?.(notebookId, name.trim());
    }
}
function handleNewNotebook() {
    const name = prompt('Notebook name:', 'New Notebook');
    if (name && name.trim()) {
        (0, state_1.addNotebook)(name.trim(), '📒');
    }
}
function escapeHtml(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
};

// ── module: src/notelist.ts ──
__mods["src/notelist.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initNoteList = initNoteList;
exports.destroyNoteList = destroyNoteList;
exports.getContainer = getContainer;
// Note list pane — displays notes in the selected notebook
const state_1 = require("./state");
const utils_1 = require("./utils");
const constants_1 = require("./constants");
let container;
let unsubscribe;
function initNoteList(el) {
    container = el;
    render();
    unsubscribe = (0, state_1.subscribe)(render);
}
function destroyNoteList() {
    unsubscribe();
}
function getContainer() {
    return container;
}
function render() {
    const state = (0, state_1.getState)();
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    const collapsed = state.sidebarCollapsed;
    const notes = (0, state_1.getFilteredNotes)();
    const activeNoteId = state.activeNoteId;
    container.innerHTML = '';
    container.style.width = `${state.paneSizes.noteList}px`;
    container.style.minWidth = '180px';
    container.style.maxWidth = '600px';
    container.style.display = 'flex';
    container.style.flexDirection = 'column';
    container.style.height = '100%';
    // Header
    const header = document.createElement('div');
    header.className = 'notelist-header';
    const activeNb = state.notebooks.find(n => n.id === state.activeNotebookId);
    let headerTitle = 'All Notes';
    if (state.activeNotebookId === 'trash')
        headerTitle = 'Trash';
    else if (activeNb)
        headerTitle = activeNb.name;
    header.innerHTML = `
    <div class="notelist-title-row">
      <h2 class="notelist-title">${escapeHtml(headerTitle)}</h2>
      ${state.activeNotebookId !== 'trash' && state.activeNotebookId !== 'all' ? `<button class="new-note-btn" title="New Note">+</button>` : ''}
    </div>
    <div class="notelist-controls">
      <div class="search-box">
        <span class="search-icon">🔍</span>
        <input type="text" class="search-input" placeholder="Search notes..." value="${escapeHtml(state.searchQuery)}" />
      </div>
      ${state.tagFilter ? `<span class="active-tag-filter">Tag: ${escapeHtml(state.tagFilter)} <button class="clear-tag-filter">×</button></span>` : ''}
      <div class="sort-controls">
        <button class="sort-btn${state.sortBy === 'modified' ? ' active' : ''}" data-sort="modified">Modified</button>
        <button class="sort-btn${state.sortBy === 'created' ? ' active' : ''}" data-sort="created">Created</button>
        <button class="sort-btn${state.sortBy === 'alphabetical' ? ' active' : ''}" data-sort="alphabetical">A-Z</button>
      </div>
    </div>
  `;
    // New note button
    header.querySelector('.new-note-btn')?.addEventListener('click', () => {
        window.createNewNote?.();
    });
    // Search input
    const searchInput = header.querySelector('.search-input');
    searchInput.addEventListener('input', () => (0, state_1.setSearchQuery)(searchInput.value));
    // Clear tag filter
    header.querySelector('.clear-tag-filter')?.addEventListener('click', () => (0, state_1.setTagFilter)(null));
    // Sort buttons
    header.querySelectorAll('.sort-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const sort = btn.dataset.sort;
            if (sort)
                (0, state_1.setSortBy)(sort);
        });
    });
    container.append(header);
    // Notes
    const notesContainer = document.createElement('div');
    notesContainer.className = 'notelist-items';
    if (notes.length === 0) {
        const empty = document.createElement('div');
        empty.className = 'notelist-empty';
        empty.innerHTML = state.searchQuery
            ? '<div class="empty-icon">🔍</div><p>No notes found</p>'
            : state.activeNotebookId === 'trash'
                ? '<div class="empty-icon">🗑️</div><p>Trash is empty</p>'
                : '<div class="empty-icon">📝</div><p>No notes yet</p><p class="empty-hint">Click + to create one</p>';
        notesContainer.append(empty);
    }
    else {
        for (const note of notes) {
            notesContainer.append(makeNoteItem(note, activeNoteId ?? null, colors));
        }
    }
    container.append(notesContainer);
}
function makeNoteItem(note, activeNoteId, colors) {
    const state = (0, state_1.getState)();
    const item = document.createElement('div');
    item.className = `note-item${note.id === activeNoteId ? ' active' : ''}`;
    if (note.id === activeNoteId) {
        item.style.backgroundColor = colors.accentLight;
        item.style.borderLeftColor = colors.accent;
    }
    const displayTitle = note.title || 'Untitled';
    const snippet = (0, utils_1.getSnippet)(note.content, constants_1.SNIPPET_LENGTH);
    let tagsHtml = '';
    if (note.tags.length > 0 && state.activeNotebookId !== 'trash') {
        tagsHtml = note.tags.map(t => `<span class="note-tag" data-tag="${escapeHtml(t)}">#${escapeHtml(t)}</span>`).join('');
    }
    item.innerHTML = `
    <div class="note-item-header">
      <span class="note-item-title">${escapeHtml(displayTitle)}</span>
      ${note.pinned ? '<span class="pin-badge" title="Pinned">📌</span>' : ''}
      ${note.id !== activeNoteId ? `<button class="note-item-menu" title="More options">⋯</button>` : ''}
    </div>
    <div class="note-item-snippet">${escapeHtml(snippet)}</div>
    <div class="note-item-footer">
      <span class="note-item-time">${(0, utils_1.formatTime)(note.updated)}</span>
      ${tagsHtml ? `<div class="note-item-tags">${tagsHtml}</div>` : ''}
    </div>
  `;
    // Click to select
    item.addEventListener('click', (e) => {
        if (e.target.closest('.note-item-menu'))
            return;
        if (e.target.closest('.note-tag'))
            return;
        (0, state_1.setActiveNote)(note.id);
    });
    // Tag click
    item.querySelectorAll('.note-tag').forEach(tagEl => {
        tagEl.addEventListener('click', (e) => {
            e.stopPropagation();
            const tag = e.target.dataset.tag;
            if (tag)
                (0, state_1.setTagFilter)(tag);
        });
    });
    // Menu button
    item.querySelector('.note-item-menu')?.addEventListener('click', (e) => {
        e.stopPropagation();
        showNoteMenu(note, e);
    });
    // Drag support
    item.draggable = true;
    item.addEventListener('dragstart', (e) => {
        e.dataTransfer.setData('text/plain', note.id);
        item.style.opacity = '0.5';
    });
    item.addEventListener('dragend', () => { item.style.opacity = '1'; });
    return item;
}
function showNoteMenu(note, e) {
    const state = (0, state_1.getState)();
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    document.querySelector('.context-menu')?.remove();
    const menu = document.createElement('div');
    menu.className = 'context-menu';
    Object.assign(menu.style, {
        position: 'fixed', zIndex: '1000', minWidth: '160px',
        backgroundColor: colors.bgSecondary,
        border: `1px solid ${colors.border}`,
        borderRadius: '8px',
        boxShadow: `0 4px 12px ${colors.shadow}`,
        fontFamily: constants_1.FONTS.ui, fontSize: '13px',
        top: `${e.clientY}px`, left: `${e.clientX}px`,
    });
    const actions = [
        { label: note.pinned ? 'Unpin' : 'Pin', action: () => window.togglePin?.(note.id) },
        { label: 'Duplicate', action: () => (0, state_1.duplicateNote)(note.id) },
        { label: 'Delete', danger: true, action: () => (0, state_1.trashNote)(note.id) },
    ];
    if (state.activeNotebookId !== 'trash') {
        // Add move options
        for (const nb of state.notebooks) {
            if (nb.id !== note.notebookId) {
                actions.push({ label: `Move to ${nb.name}`, action: () => { window.moveNoteTo?.(note.id, nb.id); } });
            }
        }
    }
    else {
        actions.push({ label: 'Restore', action: () => window.restoreNote?.(note.id) }, { label: 'Delete Forever', danger: true, action: () => window.permanentDelete?.(note.id) });
    }
    for (const item of actions) {
        const btn = document.createElement('div');
        btn.className = 'context-menu-item';
        btn.textContent = item.label;
        btn.style.cssText = `padding:8px 12px;cursor:pointer;color:${item.danger ? colors.danger : 'inherit'}`;
        btn.addEventListener('mouseenter', () => btn.style.backgroundColor = colors.bgHover);
        btn.addEventListener('mouseleave', () => btn.style.backgroundColor = 'transparent');
        btn.addEventListener('click', () => { item.action(); menu.remove(); });
        menu.append(btn);
    }
    document.body.append(menu);
    setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
}
function escapeHtml(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
};

// ── module: src/editor.ts ──
__mods["src/editor.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initEditor = initEditor;
exports.destroyEditor = destroyEditor;
exports.focusEditor = focusEditor;
exports.focusTitle = focusTitle;
// Rich text editor with contenteditable, toolbar, markdown shortcuts, paste sanitization
const state_1 = require("./state");
const utils_1 = require("./utils");
const constants_1 = require("./constants");
let container;
let editorEl;
let toolbarEl;
let titleEl;
let tagsContainer;
let saveIndicator;
let unsubscribe;
let saveTimeout = null;
let savedContent = '';
let suppressNextInput = false;
let isSaving = false; // Prevent re-render from overwriting during save
function initEditor(el) {
    container = el;
    buildEditorDOM();
    attachEditorEvents();
    render();
    unsubscribe = (0, state_1.subscribe)(render);
}
function destroyEditor() {
    unsubscribe();
}
function buildEditorDOM() {
    container.innerHTML = '';
    container.className = 'editor-pane';
    container.style.display = 'flex';
    container.style.flexDirection = 'column';
    container.style.height = '100%';
    // Toolbar
    toolbarEl = document.createElement('div');
    toolbarEl.className = 'editor-toolbar';
    container.append(toolbarEl);
    // Title input
    titleEl = document.createElement('input');
    titleEl.type = 'text';
    titleEl.className = 'editor-title';
    titleEl.placeholder = 'Note title...';
    container.append(titleEl);
    // Tags row
    tagsContainer = document.createElement('div');
    tagsContainer.className = 'editor-tags';
    container.append(tagsContainer);
    // Empty state overlay
    const emptyState = document.createElement('div');
    emptyState.className = 'editor-empty-state';
    emptyState.innerHTML = `
    <div class="empty-state-icon">📝</div>
    <h3>Select or create a note</h3>
    <p>Choose a note from the sidebar or create a new one to get started.</p>
  `;
    container.append(emptyState);
    // Editor area
    editorEl = document.createElement('div');
    editorEl.className = 'editor-content';
    editorEl.contentEditable = 'true';
    editorEl.spellcheck = true;
    editorEl.setAttribute('data-placeholder', 'Start writing...');
    container.append(editorEl);
    // Save indicator
    saveIndicator = document.createElement('div');
    saveIndicator.className = 'save-indicator';
    saveIndicator.textContent = 'Saved';
    container.append(saveIndicator);
}
function render() {
    const state = (0, state_1.getState)();
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    const note = (0, state_1.getActiveNote)();
    // Apply theme
    container.style.backgroundColor = colors.bgEditor;
    container.style.color = colors.text;
    // Title input
    titleEl.style.backgroundColor = colors.bgEditor;
    titleEl.style.color = colors.text;
    titleEl.style.border = `1px solid transparent`;
    titleEl.style.fontFamily = constants_1.FONTS.ui;
    // Show/hide elements
    const hasNote = note !== null;
    titleEl.hidden = !hasNote;
    tagsContainer.hidden = !hasNote;
    editorEl.hidden = !hasNote;
    toolbarEl.hidden = !hasNote;
    saveIndicator.hidden = !hasNote;
    container.querySelector('.editor-empty-state').style.display = hasNote ? 'none' : 'flex';
    if (!hasNote)
        return;
    // Update title
    const oldTitle = titleEl.value;
    titleEl.value = note.title;
    // Don't steal focus if title hasn't changed
    if (oldTitle === note.title) {
        // Keep cursor position
    }
    // Only update editor content if user isn't currently typing (isSaving = false)
    // and the content differs from what was last saved
    if (!isSaving) {
        const currentContent = editorEl.innerHTML;
        if (currentContent === '' && note.content) {
            // Empty editor but note has content — load it
            editorEl.innerHTML = note.content;
            savedContent = note.content;
        }
        else if (currentContent !== note.content && currentContent !== savedContent) {
            // Content diverged (external change) — sync
            editorEl.innerHTML = note.content;
            savedContent = note.content;
        }
    }
    // Restore checklist checkbox handlers after any content change
    restoreChecklistHandlers();
    // Tags
    renderTags(note);
    // Toolbar
    renderToolbar();
}
function renderToolbar() {
    const state = (0, state_1.getState)();
    const colors = state.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
    const currentBlock = getCurrentBlockFormat();
    const currentInline = getCurrentInlineFormats();
    toolbarEl.style.backgroundColor = colors.toolbarBg;
    toolbarEl.style.borderBottom = `1px solid ${colors.border}`;
    const buttons = [
        { cmd: 'bold', icon: 'B', title: 'Bold (Ctrl+B)', active: currentInline.has('strong') || currentInline.has('b') },
        { cmd: 'italic', icon: 'I', title: 'Italic (Ctrl+I)', active: currentInline.has('em') || currentInline.has('i') },
        { cmd: 'underline', icon: 'U', title: 'Underline (Ctrl+U)', active: currentInline.has('u') },
        { cmd: 'strikeThrough', icon: 'S̶', title: 'Strikethrough', active: currentInline.has('s') || currentInline.has('strike') || currentInline.has('del') },
        { separator: true },
        { cmd: 'formatBlock', value: 'H1', icon: 'H1', title: 'Heading 1', active: currentBlock === 'H1' },
        { cmd: 'formatBlock', value: 'H2', icon: 'H2', title: 'Heading 2', active: currentBlock === 'H2' },
        { cmd: 'formatBlock', value: 'H3', icon: 'H3', title: 'Heading 3', active: currentBlock === 'H3' },
        { cmd: 'formatBlock', value: 'P', icon: '¶', title: 'Paragraph', active: currentBlock === 'P' },
        { separator: true },
        { cmd: 'insertUnorderedList', icon: '•≡', title: 'Bulleted List', active: false }, // We handle list detection below
        { cmd: 'insertOrderedList', icon: '1≡', title: 'Numbered List', active: false },
        { separator: true },
        { cmd: 'formatBlock', value: 'BLOCKQUOTE', icon: '❝', title: 'Blockquote', active: currentBlock === 'BLOCKQUOTE' },
        { cmd: 'insertHorizontalRule', icon: '—', title: 'Horizontal Rule', active: false },
        { cmd: 'code', icon: '<>', title: 'Inline Code', active: isInCode() },
        { separator: true },
        { cmd: 'undo', icon: '↩', title: 'Undo (Ctrl+Z)', active: false },
        { cmd: 'redo', icon: '↪', title: 'Redo (Ctrl+Shift+Z)', active: false },
    ];
    toolbarEl.innerHTML = '';
    for (const btn of buttons) {
        if (btn.separator) {
            const sep = document.createElement('div');
            sep.className = 'toolbar-separator';
            sep.style.cssText = `width:1px;height:20px;background:${colors.border};margin:0 4px;align-self:center;`;
            toolbarEl.append(sep);
            continue;
        }
        const b = btn;
        const button = document.createElement('button');
        button.className = `toolbar-btn${b.active ? ' active' : ''}`;
        button.textContent = b.icon;
        button.title = b.title;
        button.style.cssText = `
      padding: 6px 8px;
      border: none;
      background: ${b.active ? colors.accentLight : 'transparent'};
      color: ${b.active ? colors.accent : colors.textSecondary};
      border-radius: 4px;
      cursor: pointer;
      font-size: 13px;
      font-weight: ${b.cmd === 'bold' ? 'bold' : 'normal'};
      font-style: ${b.cmd === 'italic' ? 'italic' : 'normal'};
      text-decoration: ${b.cmd === 'underline' ? 'underline' : 'none'};
      transition: all 0.15s;
      line-height: 1;
    `;
        button.addEventListener('mouseenter', () => {
            if (!b.active)
                button.style.backgroundColor = colors.bgHover;
        });
        button.addEventListener('mouseleave', () => {
            if (!b.active)
                button.style.backgroundColor = 'transparent';
        });
        button.addEventListener('click', () => {
            editorEl.focus();
            if (b.cmd === 'code') {
                toggleInlineCode();
            }
            else if (b.cmd === 'insertHorizontalRule') {
                document.execCommand('insertHorizontalRule', false, undefined);
                onEditorChange();
            }
            else if (b.cmd === 'formatBlock' && b.value) {
                document.execCommand('formatBlock', false, b.value.toLowerCase());
                onEditorChange();
            }
            else {
                document.execCommand(b.cmd, false, undefined);
                onEditorChange();
            }
        });
        toolbarEl.append(button);
    }
}
function getCurrentBlockFormat() {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0)
        return 'P';
    let node = sel.anchorNode;
    while (node && node !== editorEl) {
        if (node.nodeType === Node.ELEMENT_NODE) {
            const el = node;
            const tag = el.tagName;
            if (tag === 'H1' || tag === 'H2' || tag === 'H3' || tag === 'H4' || tag === 'H5' || tag === 'H6' || tag === 'BLOCKQUOTE' || tag === 'P' || tag === 'DIV') {
                return tag;
            }
        }
        node = node.parentNode;
    }
    return 'P';
}
function getCurrentInlineFormats() {
    const formats = new Set();
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0)
        return formats;
    let node = sel.anchorNode;
    while (node && node !== editorEl) {
        if (node.nodeType === Node.ELEMENT_NODE) {
            const tag = node.tagName.toLowerCase();
            formats.add(tag);
        }
        node = node.parentNode;
    }
    return formats;
}
function isInCode() {
    const sel = window.getSelection();
    if (!sel)
        return false;
    let node = sel.anchorNode;
    while (node && node !== editorEl) {
        if (node.nodeType === Node.ELEMENT_NODE) {
            if (node.tagName.toLowerCase() === 'code')
                return true;
        }
        node = node.parentNode;
    }
    return false;
}
function toggleInlineCode() {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0)
        return;
    const range = sel.getRangeAt(0);
    // Check if selection is already in a code element
    let node = range.startContainer;
    while (node && node !== editorEl) {
        if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'code') {
            // Remove code wrapper
            const parent = node.parentNode;
            const fc = node.firstChild;
            while (fc) {
                parent?.insertBefore(fc, node);
            }
            node.remove();
            onEditorChange();
            return;
        }
        node = node.parentNode;
    }
    document.execCommand('wrapCodeToCode', false, 'code');
    // Fallback: manual wrapping
    const selectedNode = range.cloneContents();
    const codeEl = document.createElement('code');
    codeEl.style.cssText = 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace;font-size:0.9em;';
    if (selectedNode.childNodes.length > 0) {
        for (let i = 0; i < selectedNode.childNodes.length; i++) {
            codeEl.appendChild(selectedNode.childNodes[i].cloneNode(true));
        }
    }
    else {
        codeEl.textContent = sel.toString() || '';
    }
    range.deleteContents();
    range.insertNode(codeEl);
    // Move cursor after the code
    const newRange = document.createRange();
    newRange.setStartAfter(codeEl);
    newRange.collapse(true);
    sel.removeAllRanges();
    sel.addRange(newRange);
    onEditorChange();
}
function renderTags(note) {
    tagsContainer.innerHTML = '';
    if (note.tags.length > 0) {
        for (const tag of note.tags) {
            const tagEl = document.createElement('span');
            tagEl.className = 'tag-chip';
            tagEl.textContent = `#${tag}`;
            tagEl.style.cssText = `
        display: inline-flex; align-items: center; gap: 4px;
        padding: 2px 8px; border-radius: 12px;
        background: ${getColorState().tagBg};
        color: ${getColorState().tagText};
        font-size: 12px; cursor: pointer;
      `;
            tagsContainer.append(tagEl);
            const removeBtn = document.createElement('span');
            removeBtn.className = 'tag-remove';
            removeBtn.textContent = '×';
            removeBtn.style.cursor = 'pointer';
            removeBtn.addEventListener('click', () => {
                (0, state_1.updateNote)(note.id, { tags: note.tags.filter(t => t !== tag) });
            });
            tagEl.append(removeBtn);
        }
    }
    // Add tag input
    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'tag-input';
    input.placeholder = '+ Add tag';
    input.style.cssText = `
    border: none; background: transparent; outline: none;
    font-size: 12px; color: ${getColorState().textSecondary};
    font-family: ${constants_1.FONTS.ui}; width: 80px;
  `;
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            const val = input.value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '');
            if (val && !note.tags.includes(val)) {
                (0, state_1.updateNote)(note.id, { tags: [...note.tags, val] });
            }
            input.value = '';
        }
    });
    tagsContainer.append(input);
}
function getColorState() {
    const s = (0, state_1.getState)();
    return s.darkMode ? constants_1.COLORS.dark : constants_1.COLORS.light;
}
function attachEditorEvents() {
    // Title change
    titleEl.addEventListener('input', () => {
        const note = (0, state_1.getActiveNote)();
        if (!note)
            return;
        (0, state_1.updateNote)(note.id, { title: titleEl.value });
    });
    // Editor input
    editorEl.addEventListener('input', () => {
        if (suppressNextInput) {
            suppressNextInput = false;
            // Still save the content — suppress was only to prevent double-save
            onEditorChange();
            return;
        }
        onEditorChange();
    });
    // Keydown for markdown shortcuts and list handling
    editorEl.addEventListener('keydown', handleEditorKeydown);
    // Selection change for toolbar update
    document.addEventListener('selectionchange', () => {
        const sel = window.getSelection();
        if (sel && editorEl.contains(sel.anchorNode)) {
            renderToolbar();
        }
    });
    // Click in editor to focus
    editorEl.addEventListener('focus', () => {
        renderToolbar();
    });
    // Paste sanitization
    editorEl.addEventListener('paste', (e) => {
        e.preventDefault();
        const html = e.clipboardData?.getData('text/html');
        const text = e.clipboardData?.getData('text/plain');
        let content;
        if (html) {
            content = (0, utils_1.sanitizeHtml)(html);
        }
        else if (text) {
            content = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }
        else {
            return;
        }
        document.execCommand('insertHTML', false, content);
        onEditorChange();
    });
    // Title focus when no note
    titleEl.addEventListener('focus', () => {
        const note = (0, state_1.getActiveNote)();
        if (!note) {
            titleEl.blur();
        }
    });
}
function onEditorChange() {
    const note = (0, state_1.getActiveNote)();
    if (!note)
        return;
    isSaving = true;
    const content = editorEl.innerHTML;
    savedContent = content;
    (0, state_1.updateNote)(note.id, { content });
    isSaving = false;
    // Show saving indicator
    saveIndicator.textContent = 'Saving...';
    saveIndicator.style.opacity = '0.6';
    clearTimeout(saveTimeout);
    saveTimeout = setTimeout(() => {
        saveIndicator.textContent = 'Saved';
        saveIndicator.style.opacity = '0';
    }, 1000);
}
function handleEditorKeydown(e) {
    const note = (0, state_1.getActiveNote)();
    if (!note)
        return;
    // Ctrl/Cmd+B/I/U shortcuts
    if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
        if (e.key === 'b' || e.key === 'B') {
            e.preventDefault();
            document.execCommand('bold');
            onEditorChange();
            return;
        }
        if (e.key === 'i' || e.key === 'I') {
            e.preventDefault();
            document.execCommand('italic');
            onEditorChange();
            return;
        }
        if (e.key === 'u' || e.key === 'U') {
            e.preventDefault();
            document.execCommand('underline');
            onEditorChange();
            return;
        }
    }
    // Markdown shortcuts: detect patterns at line start on space key
    if (e.key === ' ') {
        const sel = window.getSelection();
        if (!sel || sel.rangeCount === 0)
            return;
        const range = sel.getRangeAt(0);
        // Get the text node content before the cursor
        // At keydown, the space hasn't been inserted yet, so we check for patterns WITHOUT the trailing space
        let textBefore = '';
        if (range.startContainer.nodeType === Node.TEXT_NODE) {
            const textNode = range.startContainer;
            textBefore = textNode.textContent?.slice(0, range.startOffset) ?? '';
        }
        // Check for line start patterns (without trailing space — the space is what the user is typing now)
        // Patterns: # , ## , ### , - , * , [] , >
        // We need to check the text ends with the pattern prefix and a space is being typed
        const lineMatch = textBefore.match(/(^|\n)(#{1,3}|-|\*|\[\]|\[|>)$/);
        if (lineMatch) {
            const prefix = lineMatch[1];
            const pattern = lineMatch[2];
            // Get the text node and remove the pattern
            const textNode = range.startContainer;
            const textContent = textNode.textContent ?? '';
            const removeLen = pattern.length;
            const newText = textContent.slice(0, textContent.length - removeLen);
            textNode.textContent = newText;
            // If there was a newline prefix, handle it
            if (prefix === '\n') {
                const prevNode = textNode.previousSibling;
                if (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
                    const prevText = prevNode.textContent ?? '';
                    prevNode.textContent = prevText.slice(0, -1);
                }
            }
            e.preventDefault();
            // Apply formatting based on pattern
            switch (pattern) {
                case '#':
                    document.execCommand('formatBlock', false, 'h1');
                    break;
                case '##':
                    document.execCommand('formatBlock', false, 'h2');
                    break;
                case '###':
                    document.execCommand('formatBlock', false, 'h3');
                    break;
                case '-':
                case '*':
                    document.execCommand('insertUnorderedList');
                    break;
                case '[]':
                    insertChecklistItem();
                    onEditorChange();
                    return;
                case '>':
                    document.execCommand('formatBlock', false, 'blockquote');
                    break;
                case '[':
                    // Half-typed [], ignore
                    break;
            }
            onEditorChange();
            return;
        }
    }
    // Enter key: list continuation / exit
    if (e.key === 'Enter') {
        const sel = window.getSelection();
        if (!sel || sel.rangeCount === 0)
            return;
        let node = sel.anchorNode;
        let inList = false;
        let parentLi = null;
        while (node && node !== editorEl) {
            if (node.nodeType === Node.ELEMENT_NODE) {
                const tag = node.tagName;
                if (tag === 'LI') {
                    inList = true;
                    parentLi = node;
                }
            }
            node = node.parentNode;
        }
        if (inList && parentLi) {
            // Check if list item is empty
            const isEmpty = parentLi.textContent?.trim() === '' || parentLi.textContent?.trim() === '•' || parentLi.textContent?.trim() === '☐';
            if (isEmpty) {
                // Exit the list
                e.preventDefault();
                const parentUl = parentLi.parentElement;
                // Remove the empty list item
                const p = document.createElement('p');
                p.innerHTML = '<br>';
                if (parentLi.nextSibling) {
                    parentLi.parentNode?.insertBefore(p, parentLi.nextSibling);
                }
                else {
                    parentLi.parentNode?.appendChild(p);
                }
                parentLi.remove();
                // If UL is empty, remove it too
                if (parentUl && !parentUl.children.length) {
                    parentUl.remove();
                }
                // Place cursor in the new paragraph
                const newRange = document.createRange();
                newRange.setStart(p, 0);
                newRange.collapse(true);
                sel.removeAllRanges();
                sel.addRange(newRange);
                onEditorChange();
                return;
            }
            // Let browser handle list continuation normally
        }
    }
}
function insertChecklistItem(checked = false) {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0)
        return;
    // Use span-based checkboxes for proper serialization
    const checkbox = document.createElement('span');
    checkbox.className = 'checklist-checkbox';
    checkbox.setAttribute('contenteditable', 'false');
    checkbox.textContent = checked ? '☑' : '☐';
    checkbox.style.cssText = 'margin-right:6px;cursor:pointer;user-select:none;';
    checkbox.addEventListener('click', () => {
        checkbox.textContent = checkbox.textContent === '☑' ? '☐' : '☑';
        checkbox.className = checkbox.textContent === '☑' ? 'checklist-checkbox checked' : 'checklist-checkbox';
        onEditorChange();
    });
    const br = document.createElement('br');
    const fragment = document.createDocumentFragment();
    fragment.appendChild(checkbox);
    fragment.appendChild(br);
    sel.getRangeAt(0).insertNode(fragment);
    // Move cursor after the checkbox
    const newRange = document.createRange();
    newRange.setStartAfter(br);
    newRange.collapse(true);
    sel.removeAllRanges();
    sel.addRange(newRange);
    suppressNextInput = true;
    onEditorChange();
    restoreChecklistHandlers();
}
function restoreChecklistHandlers() {
    editorEl.querySelectorAll('.checklist-checkbox').forEach(cb => {
        const span = cb;
        if (span.dataset.handler)
            return;
        span.dataset.handler = '1';
        span.style.cursor = 'pointer';
        span.style.userSelect = 'none';
        span.addEventListener('click', () => {
            span.textContent = span.textContent === '☑' ? '☐' : '☑';
            span.className = span.textContent === '☑' ? 'checklist-checkbox checked' : 'checklist-checkbox';
            onEditorChange();
        });
    });
}
function focusEditor() {
    editorEl.focus();
}
function focusTitle() {
    titleEl.focus();
    titleEl.select();
}
};

// ── module: src/resizer.ts ──
__mods["src/resizer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initResizable = initResizable;
exports.initDragDrop = initDragDrop;
// Resizable panes and drag-drop for notes
const state_1 = require("./state");
function initResizable() {
    // Create resize handles dynamically
    const mainContent = document.getElementById('main-content');
    if (!mainContent)
        return;
    // Sidebar resize handle
    const sidebarHandle = document.createElement('div');
    sidebarHandle.className = 'resize-handle sidebar-handle';
    sidebarHandle.style.cssText = `
    width:4px;cursor:col-resize;background:transparent;
    transition:background 0.2s;position:relative;z-index:10;
    flex-shrink:0;
  `;
    sidebarHandle.addEventListener('mouseenter', () => { sidebarHandle.style.background = 'var(--accent, #5b6abf)'; });
    sidebarHandle.addEventListener('mouseleave', () => { sidebarHandle.style.background = 'transparent'; });
    // Note list resize handle
    const noteListHandle = document.createElement('div');
    noteListHandle.className = 'resize-handle notelist-handle';
    noteListHandle.style.cssText = `
    width:4px;cursor:col-resize;background:transparent;
    transition:background 0.2s;position:relative;z-index:10;
    flex-shrink:0;
  `;
    noteListHandle.addEventListener('mouseenter', () => { noteListHandle.style.background = 'var(--accent, #5b6abf)'; });
    noteListHandle.addEventListener('mouseleave', () => { noteListHandle.style.background = 'transparent'; });
    mainContent.insertBefore(sidebarHandle, mainContent.children[1]);
    mainContent.insertBefore(noteListHandle, mainContent.children[2]);
    // Sidebar collapse/expand button
    const sidebar = document.getElementById('sidebar');
    if (sidebar) {
        const collapseBtn = document.createElement('button');
        collapseBtn.className = 'sidebar-expand-btn';
        collapseBtn.style.cssText = `
      position:absolute;left:0;top:50%;transform:translateY(-50%);
      width:20px;height:60px;border:none;background:var(--accent, #5b6abf);
      color:white;cursor:pointer;border-radius:0 4px 4px 0;z-index:11;
      font-size:10px;display:none;
    `;
        collapseBtn.textContent = '▶';
        collapseBtn.addEventListener('click', () => {
            const state = (0, state_1.getState)();
            (0, state_1.setSidebarCollapsed)(!state.sidebarCollapsed);
        });
        sidebar.appendChild(collapseBtn);
        // Subscribe to sidebar state
        const unsub = (0, state_1.subscribe)(() => {
            const s = (0, state_1.getState)();
            collapseBtn.style.display = s.sidebarCollapsed ? 'block' : 'none';
        });
    }
    // Make sidebar handle draggable
    makeDraggable(sidebarHandle, 'sidebar');
    // Make notelist handle draggable
    makeDraggable(noteListHandle, 'noteList');
}
function makeDraggable(handle, target) {
    let startX = 0;
    let startWidth = 0;
    handle.addEventListener('mousedown', (e) => {
        e.preventDefault();
        startX = e.clientX;
        const state = (0, state_1.getState)();
        startWidth = target === 'sidebar' ? state.paneSizes.sidebar : state.paneSizes.noteList;
        const onMove = (ev) => {
            const dx = ev.clientX - startX;
            const newWidth = Math.max(150, Math.min(500, startWidth + dx));
            (0, state_1.setPaneSizes)({
                sidebar: target === 'sidebar' ? newWidth : state.paneSizes.sidebar,
                noteList: target === 'noteList' ? newWidth : state.paneSizes.noteList,
            });
        };
        const onUp = () => {
            document.removeEventListener('mousemove', onMove);
            document.removeEventListener('mouseup', onUp);
        };
        document.addEventListener('mousemove', onMove);
        document.addEventListener('mouseup', onUp);
    });
}
function initDragDrop() {
    // Drop targets: sidebar notebooks
    const sidebar = document.getElementById('sidebar');
    if (!sidebar)
        return;
    sidebar.addEventListener('dragover', (e) => {
        e.preventDefault();
        e.stopPropagation();
    });
    sidebar.addEventListener('drop', (e) => {
        e.preventDefault();
        e.stopPropagation();
        const noteId = e.dataTransfer.getData('text/plain');
        if (!noteId)
            return;
        // Find which notebook was dropped on
        const notebooks = Array.from(sidebar.querySelectorAll('.notebook-header'));
        const target = e.target;
        const nbEl = target.closest('.notebook-header');
        if (nbEl) {
            // Find notebook by name matching
            const nameEl = nbEl.querySelector('.notebook-name');
            if (nameEl) {
                const state = (0, state_1.getState)();
                const nb = state.notebooks.find(n => n.name === nameEl.textContent);
                if (nb) {
                    (0, state_1.moveNote)(noteId, nb.id);
                }
            }
        }
    });
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"unsloth/Qwen3.6-27B-MTP-GGUF-UD-Q5_K_XL","provider":"brigid","persona":"typescript","sessionId":"90b65969-adb4-4d9c-9d11-3fef20e2093a","tokensIn":8935989,"tokensOut":93858,"tokensTotal":9029847,"cost":0,"turns":147,"toolCalls":149,"failedToolCalls":5,"timestamp":"2026-08-04T13:35:44.219Z"} -->