Notes
Qwen 3.7 Flash · typescript

Browser check: Artifact runtime error. This is a technical check, not a quality review.
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 82,287 bytes · SHA-256 7692519e528f
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes</title>
<style>
/* ── css: layout ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; font-size: 14px; line-height: 1.5; }
#app { display: flex; width: 100%; height: 100%; user-select: none; }
body { -webkit-user-select: none; }
input, textarea, [contenteditable="true"] { user-select: text; -webkit-user-select: text; }
/* Main container */
.app-main { display: flex; flex: 1; min-width: 0; height: 100%; }
/* Sidebar pane */
.sidebar { width: 240px; min-width: 180px; max-width: 400px; background: var(--sidebar-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; transition: width 0.2s ease, min-width 0.2s ease, opacity 0.2s ease; overflow: hidden; position: relative; }
.sidebar.collapsed { width: 0; min-width: 0; border-right-width: 0; opacity: 0; pointer-events: none; }
.sidebar-header { padding: 12px 14px 8px; display: flex; align-items: center; gap: 6px; }
.sidebar-header h2 { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); flex: 1; }
.sidebar-search { margin: 0 10px 8px; padding: 6px 10px; border: 1px solid var(--border-color); border-radius: 6px; background: var(--input-bg); color: var(--text-primary); font-size: 13px; outline: none; transition: border-color 0.15s; }
.sidebar-search:focus { border-color: var(--accent-color); }
.sidebar-search::placeholder { color: var(--text-muted); }
.sidebar-list { flex: 1; overflow-y: auto; padding: 0 6px 12px; }
/* Notebook items */
.notebook-item { user-select: none; }
.notebook-header { display: flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text-primary); transition: background 0.1s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.notebook-header:hover { background: var(--hover-bg); }
.notebook-arrow { font-size: 9px; transition: transform 0.15s ease; flex-shrink: 0; width: 14px; text-align: center; color: var(--text-secondary); }
.notebook-arrow.open { transform: rotate(90deg); }
.notebook-name-input { flex: 1; background: var(--input-bg); border: 1px solid var(--accent-color); border-radius: 3px; padding: 1px 4px; font-size: 13px; outline: none; color: var(--text-primary); user-select: text; }
.notebook-children { margin-left: 12px; overflow: hidden; }
.notebook-actions { display: inline-flex; gap: 2px; opacity: 0; transition: opacity 0.15s; }
.notebook-header:hover .notebook-actions, .notebook-header:focus-within .notebook-actions { opacity: 1; }
/* All Notes & Trash virtual notebooks */
.virtual-notebook { display: flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text-primary); transition: background 0.1s; }
.virtual-notebook:hover { background: var(--hover-bg); }
.virtual-notebook.active { background: var(--active-bg); }
.virtual-notebook-icon { font-size: 15px; }
/* Note list pane */
.notelist { width: 260px; min-width: 200px; max-width: 450px; background: var(--notelist-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; overflow: hidden; position: relative; }
.notelist-header { padding: 10px 14px 6px; display: flex; align-items: center; justify-content: space-between; }
.notelist-header h3 { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); }
.sort-btn { background: none; border: none; cursor: pointer; padding: 3px 6px; border-radius: 4px; font-size: 11px; color: var(--text-secondary); transition: background 0.1s; }
.sort-btn:hover { background: var(--hover-bg); color: var(--text-primary); }
.notelist-search-wrap { margin: 0 10px 6px; position: relative; }
.notelist-search { width: 100%; padding: 6px 10px; padding-right: 26px; border: 1px solid var(--border-color); border-radius: 6px; background: var(--input-bg); color: var(--text-primary); font-size: 13px; outline: none; transition: border-color 0.15s; }
.notelist-search:focus { border-color: var(--accent-color); }
.notelist-search::placeholder { color: var(--text-muted); }
.notelist-count { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 11px; color: var(--text-muted); pointer-events: none; }
.notelist-body { flex: 1; overflow-y: auto; padding: 0 6px 12px; }
/* Note cards */
.note-card { padding: 10px 10px; border-radius: 6px; cursor: pointer; transition: background 0.1s; margin-bottom: 2px; border: 1.5px solid transparent; position: relative; }
.note-card:hover { background: var(--hover-bg); }
.note-card.selected { background: var(--active-bg); border-color: var(--accent-color); }
.note-card-title { font-size: 13px; font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; }
.note-card .snippet { font-size: 12px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.note-card .snippet mark { background: #ffe082; color: inherit; border-radius: 2px; padding: 0 1px; }
.note-card-time { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
.note-card-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.note-card-tag { font-size: 10px; padding: 1px 6px; border-radius: 10px; background: var(--tag-bg); color: var(--tag-text); cursor: pointer; transition: background 0.1s; }
.note-card-tag:hover { background: var(--tag-hover); }
.note-card-tag.active { background: var(--accent-color); color: white; }
.note-pin { font-size: 12px; color: var(--warning-color); margin-right: 4px; flex-shrink: 0; }
/* Resize handle */
.resize-handle { width: 4px; cursor: col-resize; background: transparent; transition: background 0.15s; position: relative; z-index: 5; flex-shrink: 0; }
.resize-handle:hover, .resize-handle.active { background: var(--accent-color); }
/* Editor pane */
.editor-pane { flex: 1; min-width: 200px; display: flex; flex-direction: column; background: var(--editor-bg); overflow: hidden; }
.editor-toolbar { display: flex; flex-wrap: wrap; gap: 2px; padding: 6px 10px; border-bottom: 1px solid var(--border-color); background: var(--toolbar-bg); align-items: center; }
.toolbar-btn { background: none; border: 1px solid transparent; cursor: pointer; padding: 4px 7px; border-radius: 4px; font-size: 13px; color: var(--text-secondary); transition: all 0.1s; display: flex; align-items: center; justify-content: center; min-width: 28px; height: 28px; font-family: inherit; }
.toolbar-btn:hover { background: var(--hover-bg); color: var(--text-primary); }
.toolbar-btn.active { background: var(--active-bg); color: var(--accent-color); border-color: var(--accent-color); }
.toolbar-sep { width: 1px; height: 20px; background: var(--border-color); margin: 0 4px; flex-shrink: 0; }
.toolbar-group { display: flex; gap: 1px; align-items: center; }
.toolbar-btn svg { width: 14px; height: 14px; fill: currentColor; }
/* Editor area */
.editor-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.editor-title-area { padding: 20px 32px 0; }
.editor-title { width: 100%; border: none; outline: none; font-size: 26px; font-weight: 700; color: var(--text-primary); background: transparent; font-family: var(--font-editor); line-height: 1.3; }
.editor-title::placeholder { color: var(--text-muted); }
.editor-meta { display: flex; align-items: center; gap: 8px; padding: 6px 32px 0; font-size: 11px; color: var(--text-muted); flex-wrap: wrap; }
.editor-tag-input-wrap { display: inline-flex; align-items: center; gap: 3px; background: var(--input-bg); border: 1px solid var(--border-color); border-radius: 12px; padding: 1px 6px; font-size: 11px; }
.editor-tag-input { border: none; outline: none; background: transparent; font-size: 11px; width: 70px; color: var(--text-primary); }
.editor-tag-chips { display: inline-flex; flex-wrap: wrap; gap: 3px; }
.tag-chip { display: inline-flex; align-items: center; gap: 3px; padding: 2px 8px; border-radius: 10px; background: var(--tag-bg); color: var(--tag-text); font-size: 11px; cursor: default; }
.tag-chip-remove { cursor: pointer; font-size: 12px; opacity: 0.7; margin-left: 2px; }
.tag-chip-remove:hover { opacity: 1; }
.editor-body-area { flex: 1; overflow-y: auto; padding: 16px 32px 40px; }
.editor-body { font-size: 15px; line-height: 1.7; color: var(--text-primary); font-family: var(--font-editor); min-height: 100%; outline: none; }
.editor-body:empty::before { content: attr(data-placeholder); color: var(--text-muted); pointer-events: none; }
/* Editor body styles */
.editor-body p { margin-bottom: 8px; }
.editor-body h1 { font-size: 24px; font-weight: 700; margin: 16px 0 8px; padding-bottom: 6px; border-bottom: 2px solid var(--border-color); }
.editor-body h2 { font-size: 20px; font-weight: 600; margin: 14px 0 6px; }
.editor-body h3 { font-size: 17px; font-weight: 600; margin: 12px 0 4px; }
.editor-body blockquote { border-left: 3px solid var(--accent-color); padding: 4px 14px; margin: 8px 0; color: var(--text-secondary); background: var(--hover-bg); border-radius: 0 4px 4px 0; }
.editor-body ul, .editor-body ol { padding-left: 24px; margin: 6px 0; }
.editor-body li { margin-bottom: 3px; }
.editor-body .checklist-item { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 3px; list-style: none; padding-left: 0; }
.editor-body .checklist-item input[type="checkbox"] { margin-top: 5px; accent-color: var(--accent-color); cursor: pointer; flex-shrink: 0; }
.editor-body .checklist-item.checked span { text-decoration: line-through; color: var(--text-muted); }
.editor-body hr { border: none; border-top: 2px solid var(--border-color); margin: 16px 0; }
.editor-body code { background: var(--hover-bg); padding: 1px 5px; border-radius: 3px; font-family: var(--font-mono); font-size: 0.9em; }
/* Empty state */
.empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--text-muted); gap: 8px; }
.empty-state-icon { font-size: 48px; opacity: 0.4; }
.empty-state-text { font-size: 15px; font-weight: 500; }
.empty-state-hint { font-size: 13px; }
/* Context menu / dropdown */
.context-menu { position: fixed; background: var(--menu-bg); border: 1px solid var(--border-color); border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.15); padding: 4px; min-width: 180px; z-index: 1000; }
.context-menu-item { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 5px; font-size: 13px; color: var(--text-primary); cursor: pointer; transition: background 0.08s; }
.context-menu-item:hover { background: var(--hover-bg); }
.context-menu-item.danger { color: #e53e3e; }
.context-menu-sep { height: 1px; background: var(--border-color); margin: 4px 8px; }
.context-menu-shortcut { margin-left: auto; font-size: 11px; color: var(--text-muted); }
/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 4px; padding: 5px 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--btn-bg); color: var(--text-primary); font-size: 12px; cursor: pointer; transition: all 0.1s; font-family: inherit; }
.btn:hover { background: var(--hover-bg); }
.btn-sm { padding: 3px 7px; font-size: 11px; }
.btn-accent { background: var(--accent-color); color: white; border-color: var(--accent-color); }
.btn-accent:hover { opacity: 0.9; }
/* Save indicator */
.save-indicator { position: absolute; bottom: 8px; right: 12px; font-size: 11px; color: var(--text-muted); opacity: 0; transition: opacity 0.3s; pointer-events: none; }
.save-indicator.visible { opacity: 1; }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scrollbar-bg); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); }
/* Light theme */
:root.light {
--bg-primary: #ffffff;
--sidebar-bg: #fafafa;
--notelist-bg: #f7f7f7;
--editor-bg: #ffffff;
--toolbar-bg: #fcfcfc;
--border-color: #e8e8e8;
--hover-bg: #eaeaea;
--active-bg: #e8f0fe;
--accent-color: #3b82f6;
--text-primary: #1a1a1a;
--text-secondary: #555555;
--text-muted: #999999;
--input-bg: #f0f0f0;
--tag-bg: #e8f0fe;
--tag-text: #2563eb;
--tag-hover: #d0e2fd;
--menu-bg: #ffffff;
--btn-bg: #f0f0f0;
--warning-color: #f59e0b;
--scrollbar-bg: #d0d0d0;
--scrollbar-hover: #b0b0b0;
--font-editor: 'Georgia', 'Palatino Linotype', 'Book Antiqua', serif;
}
/* Dark theme */
:root.dark {
--bg-primary: #1e1e2e;
--sidebar-bg: #181825;
--notelist-bg: #1e1e2e;
--editor-bg: #1e1e2e;
--toolbar-bg: #1e1e2e;
--border-color: #313244;
--hover-bg: #2a2a3c;
--active-bg: #313649;
--accent-color: #7aa2f7;
--text-primary: #cdd6f4;
--text-secondary: #a6adc8;
--text-muted: #585b70;
--input-bg: #2a2a3c;
--tag-bg: #313649;
--tag-text: #7aa2f7;
--tag-hover: #45475a;
--menu-bg: #242438;
--btn-bg: #2a2a3c;
--warning-color: #fab387;
--scrollbar-bg: #45475a;
--scrollbar-hover: #585b70;
--font-editor: 'IBM Plex Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
body { background: var(--bg-primary); }
</style>
</head>
<body>
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<h2>Notes</h2>
<button class="btn btn-sm" id="add-notebook-btn" title="New Notebook">+</button>
</div>
<input type="text" class="sidebar-search" id="sidebar-search" placeholder="Search notes…">
<div class="sidebar-list" id="sidebar-list"></div>
</div>
<div class="resize-handle" id="resize-sidebar"></div>
<div class="notelist" id="notelist">
<div class="notelist-header">
<h3 id="notelist-title">All Notes</h3>
<button class="sort-btn" id="sort-btn" title="Sort order">Modified ▾</button>
</div>
<div class="notelist-search-wrap">
<input type="text" class="notelist-search" id="notelist-search" placeholder="Filter…">
<span class="notelist-count" id="notelist-count"></span>
</div>
<div class="notelist-body" id="notelist-body"></div>
</div>
<div class="resize-handle" id="resize-notelist"></div>
<div class="editor-pane" id="editor-pane">
<div class="editor-toolbar" id="editor-toolbar" style="display:none;">
<div class="toolbar-group">
<button class="toolbar-btn" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
<button class="toolbar-btn" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
<button class="toolbar-btn" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
<button class="toolbar-btn" data-cmd="strikeThrough" title="Strikethrough"><s>S</s></button>
</div>
<div class="toolbar-sep"></div>
<div class="toolbar-group">
<button class="toolbar-btn" data-cmd="formatBlock" data-val="H1" title="Heading 1">H1</button>
<button class="toolbar-btn" data-cmd="formatBlock" data-val="H2" title="Heading 2">H2</button>
<button class="toolbar-btn" data-cmd="formatBlock" data-val="H3" title="Heading 3">H3</button>
</div>
<div class="toolbar-sep"></div>
<div class="toolbar-group">
<button class="toolbar-btn" data-cmd="insertUnorderedList" title="Bullet list">•≡</button>
<button class="toolbar-btn" data-cmd="insertOrderedList" title="Numbered list">1.</button>
</div>
<div class="toolbar-sep"></div>
<div class="toolbar-group">
<button class="toolbar-btn" data-action="checklist" title="Checklist item">☑</button>
<button class="toolbar-btn" data-cmd="formatBlock" data-val="BLOCKQUOTE" title="Quote">❝</button>
<button class="toolbar-btn" data-cmd="horizontalRule" title="Horizontal rule">—</button>
</div>
<div class="toolbar-sep"></div>
<div class="toolbar-group">
<button class="toolbar-btn" data-cmd="removeFormat" title="Clear formatting">✕</button>
</div>
</div>
<div class="editor-wrapper" id="editor-wrapper">
<div class="empty-state" id="empty-state">
<div class="empty-state-icon">📝</div>
<div class="empty-state-text">Select or create a note</div>
<div class="empty-state-hint">Choose a note from the list or press Ctrl+N to create one</div>
</div>
<div id="editor-active" style="display:none; flex:1; display:flex; flex-direction:column;">
<div class="editor-title-area">
<input type="text" class="editor-title" id="editor-title" placeholder="Untitled">
</div>
<div class="editor-meta" id="editor-meta">
<span id="editor-time"></span>
<div class="editor-tag-chips" id="editor-tags"></div>
<div class="editor-tag-input-wrap">
<input type="text" class="editor-tag-input" id="editor-tag-input" placeholder="+ Tag">
</div>
</div>
<div class="editor-body-area">
<div class="editor-body" id="editor-body" contenteditable="true" data-placeholder="Start writing…"></div>
</div>
<div class="save-indicator" id="save-indicator">Saved</div>
</div>
</div>
</div>
<button class="btn btn-sm" id="toggle-sidebar-btn" title="Toggle sidebar" style="position:fixed;left:8px;top:8px;z-index:10;display:none;background:var(--btn-bg);border:1px solid var(--border-color);width:28px;height:28px;font-size:16px;padding:0;">☰</button>
<div class="context-menu" id="context-menu" style="display:none;"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./model":"src/model.ts","./store":"src/store.ts","./sidebar":"src/sidebar.ts","./notelist":"src/notelist.ts","./editor":"src/editor.ts","./layout":"src/layout.ts"},"src/store.ts":{"./types":"src/types.ts","./constants":"src/constants.ts"},"src/model.ts":{"./types":"src/types.ts","./constants":"src/constants.ts"},"src/editor.ts":{"./types":"src/types.ts","./constants":"src/constants.ts","./model":"src/model.ts","./store":"src/store.ts"},"src/sidebar.ts":{"./types":"src/types.ts","./model":"src/model.ts","./store":"src/store.ts"},"src/notelist.ts":{"./types":"src/types.ts","./model":"src/model.ts","./sidebar":"src/sidebar.ts","./store":"src/store.ts","./editor":"src/editor.ts"},"src/layout.ts":{"./store":"src/store.ts","./constants":"src/constants.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 — Application Entry Point & Wiring
// ============================================
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 model_1 = require("./model");
const store_1 = require("./store");
const sidebar_1 = require("./sidebar");
const notelist_1 = require("./notelist");
const editor_1 = require("./editor");
const layout_1 = require("./layout");
const NOTELIST_BODY = document.getElementById("notelist-body");
const SORT_BTN = document.getElementById("sort-btn");
function start() {
// Initialize state
const saved = (0, store_1.loadAppData)();
if (saved) {
(0, model_1.setState)(saved);
}
else {
const defaultNBs = (0, model_1.DEFAULT_NOTEBOOKS)();
const nb1 = defaultNBs[0].id;
const nb2 = defaultNBs[1]?.id ?? nb1;
const now = Date.now();
// Create sample notes
const sampleNotes = [
{ id: "sample_1", title: "Welcome to Notes!", bodyHTML: "<p>This is your new <b>notes app</b>.</p><p>You can organize notes into <i>notebooks</i>, format text with the toolbar, and use <u>markdown shortcuts</u>.</p>", tags: ["getting-started"], pinned: true, notebookId: nb1, created: now - 60000, updated: now },
{ id: "sample_2", title: "Keyboard Shortcuts", bodyHTML: "<h2>Formatting</h2><ul><li><b>Bold</b> — Ctrl+B</li><li><i>Italic</i> — Ctrl+I</li><li><u>Underline</u> — Ctrl+U</li></ul><h2>Markdown Shortcuts</h2><ol><li>Type <code># </code> then Space → Heading 1</li><li>Type <code>- </code> then Space → Bullet list</li><li>Type <code>> </code> then Space → Blockquote</li><li>Type <code>[] </code> after <code>- </code> → Checklist</li></ol><h2>Navigation</h2><ul><li>Ctrl+N — New note</li><li>Ctrl+/ — Toggle sidebar</li><li>Double-click empty area — Quick new note</li></ul>", tags: ["tips"], pinned: false, notebookId: nb1, created: now - 120000, updated: now - 30000 },
{ id: "sample_3", title: "Project Ideas", bodyHTML: "<p>Things to build:</p><ul><li class='checklist-item checked'><input type='checkbox' checked tabindex='-1'><span>Build a notes app</span></li><li class='checklist-item'><input type='checkbox' tabindex='-1'><span>Add real-time sync</span></li><li class='checklist-item'><input type='checkbox' tabindex='-1'><span>Add export/import</span></li></ul>", tags: ["projects", "todo"], pinned: false, notebookId: nb2, created: now - 180000, updated: now - 60000 },
];
const state = {
notebooks: defaultNBs,
notes: sampleNotes,
activeNotebookId: null,
selectedNoteId: "sample_1",
sortOption: "modified",
currentView: "all",
};
(0, model_1.setState)(state);
}
// Get DOM refs
const editorBody = document.getElementById("editor-body");
const titleInput = document.getElementById("editor-title");
const saveIndicator = document.getElementById("save-indicator");
const tagChips = document.getElementById("editor-tags");
const tagInput = document.getElementById("editor-tag-input");
// Set theme
const theme = localStorage.getItem("notes_app_theme");
if (theme === "dark") {
document.documentElement.classList.add("dark");
document.documentElement.classList.remove("light");
}
else {
document.documentElement.classList.add("light");
}
// Theme toggle via double-click on empty area
const appEl = document.getElementById("app");
appEl?.addEventListener("dblclick", (e) => {
if (e.target.closest("#app") === e.currentTarget &&
!e.target.closest(".editor-body, .editor-title, .note-card, .virtual-notebook")) {
toggleTheme();
}
});
function toggleTheme() {
const isDark = document.documentElement.classList.contains("dark");
document.documentElement.classList.toggle("dark");
document.documentElement.classList.toggle("light");
localStorage.setItem("notes_app_theme", isDark ? "light" : "dark");
}
// Setup components
(0, editor_1.initEditor)(editorBody, titleInput, saveIndicator, tagChips, tagInput);
(0, sidebar_1.initSidebar)();
(0, layout_1.initLayout)();
sidebarCallbacks();
notelistCallbacks();
// Keyboard shortcuts
addKeyboardShortcuts();
// Initial render
(0, sidebar_1.renderSidebar)();
(0, notelist_1.renderNoteList)();
// Restore selection
const state = (0, model_1.getState)();
if (state.selectedNoteId) {
const note = state.notes.find(n => n.id === state.selectedNoteId);
if (note) {
if ((state.currentView === "trash" && note.notebookId !== "__trash__") ||
(state.currentView !== "trash" && note.notebookId === "__trash__")) {
(0, model_1.selectNote)(null);
}
else {
(0, editor_1.loadNote)(note);
}
}
}
// Double-click empty notelist area to create note
if (NOTELIST_BODY)
NOTELIST_BODY.addEventListener("dblclick", (e) => {
if (!e.target.closest(".note-card"))
handleNewNote();
});
// App initialized
}
// Auto-start
start();
// ---- Callback Registration ----
function sidebarCallbacks() {
Promise.resolve().then(() => __importStar(require("./sidebar"))).then(m => {
m.setSidebarCallbacks(() => (0, notelist_1.renderNoteList)(), () => (0, editor_1.unloadEditor)());
});
}
function notelistCallbacks() {
Promise.resolve().then(() => __importStar(require("./notelist"))).then(m => {
m.setNoteListCallbacks((noteId) => {
const note = (0, model_1.getState)().notes.find(n => n.id === noteId);
if (note)
(0, editor_1.loadNote)(note);
});
});
}
// ---- Note Actions ----
function handleNewNote() {
const state = (0, model_1.getState)();
let notebookId;
if (state.activeNotebookId) {
notebookId = state.activeNotebookId;
}
else if (state.currentView !== "trash" && state.notebooks.length > 0) {
notebookId = state.notebooks[0].id;
}
const note = (0, model_1.createNote)(notebookId);
(0, store_1.saveAppData)((0, model_1.getState)());
(0, sidebar_1.renderSidebar)();
(0, notelist_1.renderNoteList)();
(0, editor_1.loadNote)(note);
}
// ---- Keyboard Shortcuts ----
function addKeyboardShortcuts() {
document.addEventListener("keydown", (e) => {
// Ctrl+N or Cmd+N: New note
if ((e.ctrlKey || e.metaKey) && e.key === "n") {
e.preventDefault();
handleNewNote();
return;
}
// Escape: Clear search/filter
if (e.key === "Escape") {
const sidebarSearch = document.getElementById("sidebar-search");
if (document.activeElement === sidebarSearch) {
sidebarSearch.value = "";
sidebarSearch.blur();
(0, sidebar_1.renderSidebar)();
(0, notelist_1.renderNoteList)();
return;
}
const filterInput = document.getElementById("notelist-search");
if (document.activeElement === filterInput) {
filterInput.value = "";
filterInput.blur();
(0, notelist_1.renderNoteList)();
return;
}
const tagInput = document.getElementById("editor-tag-input");
if (document.activeElement === tagInput) {
tagInput.blur();
return;
}
}
// Ctrl+/ Toggle sidebar
if ((e.ctrlKey || e.metaKey) && e.key === "/") {
e.preventDefault();
const sidebar = document.getElementById("sidebar");
const toggleBtn = document.getElementById("toggle-sidebar-btn");
if (sidebar?.classList.contains("collapsed")) {
sidebar.classList.remove("collapsed");
toggleBtn && (toggleBtn.style.display = "none");
}
else {
sidebar?.classList.add("collapsed");
toggleBtn && (toggleBtn.style.display = "flex");
}
(0, layout_1.initLayout)();
}
});
}
};
// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Tunable Constants
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.SORT_OPTIONS = exports.COLORS = exports.LAYOUT_STORAGE_KEY = exports.STORAGE_KEY = exports.SNIPPET_LENGTH = exports.AUTOSAVE_DEBOUNCE_MS = void 0;
exports.AUTOSAVE_DEBOUNCE_MS = 400;
exports.SNIPPET_LENGTH = 120;
exports.STORAGE_KEY = "notes_app_data_v2";
exports.LAYOUT_STORAGE_KEY = "notes_app_layout";
exports.COLORS = {
accent: "#3b82f6",
};
exports.SORT_OPTIONS = ["modified", "created", "alphabetical"];
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Types — Data Model
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
};
// ── module: src/store.ts ──
__mods["src/store.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Store — Serialization & Persistence
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveAppData = saveAppData;
exports.loadAppData = loadAppData;
exports.saveLayout = saveLayout;
exports.loadLayout = loadLayout;
const constants_1 = require("./constants");
function saveAppData(state) {
try {
localStorage.setItem(constants_1.STORAGE_KEY, JSON.stringify(state));
}
catch { /* quota exceeded – ignore */ }
}
function loadAppData() {
try {
const raw = localStorage.getItem(constants_1.STORAGE_KEY);
if (!raw)
return null;
const parsed = JSON.parse(raw);
// Basic validation
if (!parsed.notebooks || !Array.isArray(parsed.notes))
return null;
return parsed;
}
catch {
return null;
}
}
function saveLayout(layout) {
try {
localStorage.setItem(constants_1.LAYOUT_STORAGE_KEY, JSON.stringify(layout));
}
catch { /* ignore */ }
}
function loadLayout() {
try {
const raw = localStorage.getItem(constants_1.LAYOUT_STORAGE_KEY);
if (!raw)
return null;
return JSON.parse(raw);
}
catch {
return null;
}
}
};
// ── module: src/model.ts ──
__mods["src/model.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Model — Data Operations (read/write)
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_NOTEBOOKS = void 0;
exports.getState = getState;
exports.setState = setState;
exports.addNotebook = addNotebook;
exports.renameNotebook = renameNotebook;
exports.deleteNotebook = deleteNotebook;
exports.toggleNotebookCollapse = toggleNotebookCollapse;
exports.getNotebook = getNotebook;
exports.createNote = createNote;
exports.updateNote = updateNote;
exports.moveNote = moveNote;
exports.duplicateNote = duplicateNote;
exports.deleteNote = deleteNote;
exports.restoreNote = restoreNote;
exports.permanentlyDeleteNote = permanentlyDeleteNote;
exports.isNoteInTrash = isNoteInTrash;
exports.addTag = addTag;
exports.removeTag = removeTag;
exports.selectNote = selectNote;
exports.setSortOption = setSortOption;
exports.setCurrentView = setCurrentView;
exports.getPinnedNotes = getPinnedNotes;
exports.getUnpinnedNotes = getUnpinnedNotes;
exports.getTrashNotes = getTrashNotes;
exports.filterActiveNotes = filterActiveNotes;
exports.sortNotes = sortNotes;
exports.searchAll = searchAll;
exports.extractText = extractText;
exports.escapeHTML = escapeHTML;
exports.sanitizeHTML = sanitizeHTML;
exports.formatTime = formatTime;
exports.formatDate = formatDate;
exports.getSnippet = getSnippet;
const constants_1 = require("./constants");
let _state;
function getState() { return _state; }
function setState(s) { _state = s; }
// ---- Notebook CRUD ----
const DEFAULT_NOTEBOOKS = () => [
{ id: nbId(), name: "General", collapsed: false },
{ id: nbId(), name: "Work", collapsed: false },
];
exports.DEFAULT_NOTEBOOKS = DEFAULT_NOTEBOOKS;
function addNotebook(name) {
const id = nbId();
_state.notebooks.push({ id, name, collapsed: false });
return id;
}
function renameNotebook(id, name) {
const nb = _state.notebooks.find(n => n.id === id);
if (nb)
nb.name = name;
}
function deleteNotebook(id) {
// Move notes to first remaining notebook or General
const fallback = _state.notebooks.length > 1
? _state.notebooks.find(n => n.id !== id)?.id ?? null
: (_state.notebooks[0]?.id ?? nbId());
for (const note of _state.notes) {
if (note.notebookId === id && fallback) {
note.notebookId = fallback;
}
}
_state.notebooks = _state.notebooks.filter(n => n.id !== id);
if (_state.activeNotebookId === id)
_state.activeNotebookId = null;
}
function toggleNotebookCollapse(id) {
const nb = _state.notebooks.find(n => n.id === id);
if (nb)
nb.collapsed = !nb.collapsed;
}
function getNotebook(id) {
return _state.notebooks.find(n => n.id === id);
}
// ---- Note CRUD ----
function createNote(notebookId) {
const id = nbId();
const target = notebookId || _state.activeNotebookId
|| _state.notebooks[0]?.id
|| (addNotebook("General"), _state.notebooks[0].id);
const now = Date.now();
const note = {
id, title: "", bodyHTML: "", tags: [], pinned: false,
notebookId: target, created: now, updated: now,
};
_state.notes.unshift(note);
_state.selectedNoteId = id;
return note;
}
function updateNote(id, patch) {
const note = _state.notes.find(n => n.id === id);
if (note) {
Object.assign(note, patch, { updated: Date.now() });
}
}
function moveNote(noteId, targetNotebookId) {
const note = _state.notes.find(n => n.id === noteId);
if (note && !isInTrash(note)) {
note.notebookId = targetNotebookId;
note.updated = Date.now();
}
}
function duplicateNote(noteId) {
const src = _state.notes.find(n => n.id === noteId);
if (!src)
return createNote();
const now = Date.now();
const dup = {
...src, id: nbId(), title: src.title + " (copy)",
created: now, updated: now, pinned: false, tags: [...src.tags],
};
_state.notes.unshift(dup);
_state.selectedNoteId = dup.id;
return dup;
}
function deleteNote(noteId) {
const note = _state.notes.find(n => n.id === noteId);
if (note) {
note.updated = Date.now();
note.notebookId = "__trash__";
}
}
function restoreNote(noteId, originalNotebookId) {
const note = _state.notes.find(n => n.id === noteId);
if (note) {
note.notebookId = originalNotebookId;
note.updated = Date.now();
}
}
function permanentlyDeleteNote(noteId) {
_state.notes = _state.notes.filter(n => n.id !== noteId);
if (_state.selectedNoteId === noteId)
_state.selectedNoteId = null;
}
function isInTrash(note) {
return note.notebookId === "__trash__";
}
function isNoteInTrash(noteId) {
const note = _state.notes.find(n => n.id === noteId);
return !!note && isInTrash(note);
}
// ---- Tags ----
function addTag(noteId, tag) {
const note = _state.notes.find(n => n.id === noteId);
if (note && !note.tags.includes(tag.toLowerCase())) {
note.tags.push(tag.toLowerCase());
note.updated = Date.now();
}
}
function removeTag(noteId, tag) {
const note = _state.notes.find(n => n.id === noteId);
if (note) {
note.tags = note.tags.filter(t => t !== tag);
note.updated = Date.now();
}
}
// ---- Selection & Sorting ----
function selectNote(id) {
_state.selectedNoteId = id;
}
function setSortOption(opt) {
_state.sortOption = opt;
}
function setCurrentView(view) {
_state.currentView = view;
}
// ---- Filtering & Listing ----
function getPinnedNotes() {
return filterActiveNotes().filter(n => n.pinned);
}
function getUnpinnedNotes() {
return filterActiveNotes().filter(n => !n.pinned);
}
function getTrashNotes() {
return _state.notes.filter(isInTrash).sort((a, b) => b.updated - a.updated);
}
function filterActiveNotes(options) {
let notes = _state.notes;
if (!_state.currentView || _state.currentView === "all") {
// Inactive (non-trash)
notes = notes.filter(n => !isInTrash(n));
if (_state.activeNotebookId) {
notes = notes.filter(n => n.notebookId === _state.activeNotebookId);
}
}
else {
// trash view
notes = notes.filter(isInTrash);
}
if (options?.query) {
const q = options.query.toLowerCase();
notes = notes.filter(n => n.title.toLowerCase().includes(q) || extractText(n.bodyHTML).toLowerCase().includes(q));
}
return sortNotes(notes, _state.sortOption);
}
function sortNotes(notes, option) {
return [...notes].sort((a, b) => {
switch (option) {
case "modified": return b.updated - a.updated;
case "created": return b.created - a.created;
case "alphabetical": return (a.title || "z").localeCompare(b.title || "z");
}
});
}
// ---- Search ----
function searchAll(query) {
if (!query.trim())
return [];
const q = query.toLowerCase();
const results = [];
for (const note of _state.notes) {
if (note.title.toLowerCase().includes(q)) {
results.push({ note, type: "title", snippet: highlightMatch(extractText(note.title), q) });
continue;
}
const text = extractText(note.bodyHTML);
if (text.toLowerCase().includes(q)) {
const idx = text.toLowerCase().indexOf(q);
const start = Math.max(0, idx - 40);
const end = Math.min(text.length, idx + q.length + 60);
let snippet = text.substring(start, end);
if (start > 0)
snippet = "…" + snippet;
if (end < text.length)
snippet += "…";
results.push({ note, type: "body", snippet: highlightMatch(snippet, q) });
}
}
return results;
}
function highlightMatch(text, query) {
const idx = text.toLowerCase().indexOf(query.toLowerCase());
if (idx < 0)
return escapeHTML(text);
const before = escapeHTML(text.substring(0, idx));
const match = escapeHTML(text.substring(idx, idx + query.length));
const after = escapeHTML(text.substring(idx + query.length));
return before + '<mark>' + match + '</mark>' + after;
}
// ---- Text extraction (strips HTML tags for search/snippets) ----
function extractText(html) {
const div = document.createElement("div");
div.innerHTML = html;
return div.textContent || div.innerText || "";
}
function escapeHTML(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
}
// ---- Sanitize HTML (strip scripts, foreign content) ----
function sanitizeHTML(html) {
// Remove script tags and their contents
let result = html.replace(/<script[\s\S]*?<\/script>/gi, "");
result = result.replace(/on\w+="[^"]*"/gi, "");
result = result.replace(/on\w+='[^']*'/gi, "");
result = result.replace(/javascript:/gi, "");
// Allow only safe elements
const safe = new Set([
"b", "strong", "i", "em", "u", "s", "strike", "sub", "sup",
"h1", "h2", "h3", "h4", "h5", "h6",
"p", "br", "div", "span", "blockquote",
"ul", "ol", "li", "hr",
"code", "pre", "del", "ins",
"a",
]);
// Basic tag filtering: keep only safe tags
const stripped = result.replace(/<[^>]+>/g, (tag) => {
const m = tag.match(/^<(\w+)/);
if (m && safe.has(m[1]))
return tag;
return "";
});
return stripped.trim();
}
// ---- Utility ----
function formatTime(ts) {
const d = new Date(ts);
const now = new Date();
if (d.toDateString() === now.toDateString()) {
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
return d.toLocaleDateString([], { month: "short", day: "numeric" });
}
function formatDate(ts) {
const d = new Date(ts);
const now = new Date();
if (d.toDateString() === now.toDateString()) {
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
const diffDays = Math.floor((now.getTime() - d.getTime()) / 86400000);
if (diffDays === 1)
return "Yesterday";
if (diffDays < 7)
return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()];
return d.toLocaleDateString([], { month: "short", day: "numeric" });
}
function getSnippet(note) {
const text = extractText(note.bodyHTML);
if (text.length <= constants_1.SNIPPET_LENGTH)
return escapeHTML(text);
return escapeHTML(text.substring(0, constants_1.SNIPPET_LENGTH)) + "…";
}
let _idCounter = 0;
function nbId() {
return "id_" + (++_idCounter) + "_" + Date.now().toString(36);
}
};
// ── module: src/editor.ts ──
__mods["src/editor.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Editor — ContentEditable Rich Text
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.initEditor = initEditor;
exports.refreshToolbarState = refreshToolbarState;
exports.loadNote = loadNote;
exports.unloadEditor = unloadEditor;
exports.addTagFromUI = addTagFromUI;
exports.removeTagFromUI = removeTagFromUI;
const constants_1 = require("./constants");
const model_1 = require("./model");
const store_1 = require("./store");
let _editor = null;
let _titleInput = null;
let _saveIndicator = null;
let _activeNoteId = null;
let _savedBody = "";
let _savedTitle = "";
let _tagChipContainer = null;
let _tagInput = null;
let _autosaveTimer = null;
function initEditor(editor, title, saveIndicator, tagChips, tagInput) {
_editor = editor;
_titleInput = title;
_saveIndicator = saveIndicator;
_tagChipContainer = tagChips;
_tagInput = tagInput;
setupToolbar();
setupEditorEvents();
setupTagInput();
}
// ---- Toolbar ----
function setupToolbar() {
const toolbar = document.getElementById("editor-toolbar");
if (!toolbar)
return;
toolbar.querySelectorAll(".toolbar-btn").forEach(btn => {
btn.addEventListener("mousedown", (e) => {
e.preventDefault();
const cmd = btn.dataset.cmd;
const val = btn.dataset.val ?? "";
const action = btn.dataset.action;
if (action === "checklist") {
insertChecklist();
return;
}
switch (cmd) {
case "formatBlock":
document.execCommand("formatBlock", false, val);
break;
case "removeFormat":
document.execCommand("removeFormat", false, undefined);
break;
case "horizontalRule":
document.execCommand("insertHorizontalRule", false, undefined);
break;
default:
document.execCommand(cmd, false, val || undefined);
break;
}
refreshToolbarState();
});
});
}
function refreshToolbarState() {
const toolbar = document.getElementById("editor-toolbar");
if (!toolbar)
return;
toolbar.querySelectorAll(".toolbar-btn[data-cmd]").forEach(btn => {
const cmd = btn.dataset.cmd;
let active = false;
try {
if (["bold", "italic", "underline", "strikeThrough"].includes(cmd)) {
active = document.queryCommandState(cmd);
}
else if (cmd === "formatBlock") {
const block = document.queryCommandValue("formatBlock");
active = block.toLowerCase() === (btn.dataset.val?.toLowerCase() ?? "");
}
else if (cmd === "insertUnorderedList" || cmd === "insertOrderedList") {
active = document.queryCommandState(cmd);
}
}
catch { /* ignore */ }
btn.classList.toggle("active", active);
});
}
// ---- Editor Events ----
function setupEditorEvents() {
if (!_editor)
return;
_editor.addEventListener("input", () => { scheduleAutoSave(); refreshToolbarState(); });
_editor.addEventListener("keyup", () => refreshToolbarState());
_editor.addEventListener("mouseup", () => refreshToolbarState());
_editor.addEventListener("keydown", handleEditorKeydown);
_editor.addEventListener("paste", handlePaste);
}
function handlePaste(e) {
e.preventDefault();
const html = e.clipboardData?.getData("text/html") ?? "";
const text = e.clipboardData?.getData("text/plain") ?? "";
const sanitized = (0, model_1.sanitizeHTML)(html);
const content = sanitized || text.replace(/\n/g, "<br>");
document.execCommand("insertHTML", false, content);
}
// ---- Auto-save ----
function scheduleAutoSave() {
if (_autosaveTimer)
clearTimeout(_autosaveTimer);
showSaving();
_autosaveTimer = setTimeout(() => commitSave(), constants_1.AUTOSAVE_DEBOUNCE_MS);
}
function commitSave() {
if (!_activeNoteId || !_editor || !_titleInput)
return;
const curHTML = _editor.innerHTML;
const curTitle = _titleInput.value;
if (curHTML === _savedBody && curTitle === _savedTitle) {
showSaved();
return;
}
_savedBody = curHTML;
_savedTitle = curTitle;
(0, model_1.updateNote)(_activeNoteId, { bodyHTML: curHTML, title: curTitle, tags: getCurrentTags() });
(0, store_1.saveAppData)((0, model_1.getState)());
showSaved();
}
function showSaving() {
if (_saveIndicator) {
_saveIndicator.textContent = "Saving…";
_saveIndicator.classList.add("visible");
}
}
function showSaved() {
if (_saveIndicator) {
_saveIndicator.textContent = "Saved";
_saveIndicator.classList.add("visible");
setTimeout(() => _saveIndicator?.classList.remove("visible"), 1500);
}
}
function getCurrentTags() {
const chips = _tagChipContainer?.querySelectorAll(".tag-chip");
const tags = [];
chips?.forEach(c => { const t = c.querySelector("span")?.textContent; if (t)
tags.push(t); });
return tags;
}
// ---- Markdown Shortcuts & Enter Handling ----
function handleEditorKeydown(e) {
if (!_editor)
return;
if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
if (handleEnterKey()) {
e.preventDefault();
return;
}
}
if (e.key === " ") {
if (checkMarkdownShortcut(" ")) {
e.preventDefault();
return;
}
}
if (e.key === "Escape")
clearMarkdownShortcuts();
}
function handleEnterKey() {
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0)
return false;
const range = sel.getRangeAt(0);
let node = range.startContainer;
while (node && node !== _editor) {
if (node.nodeType === Node.ELEMENT_NODE) {
const el = node;
if (el.tagName === "LI") {
const li = el;
const parentUl = li.parentElement;
const isCI = li.classList.contains("checklist-item");
const span = isCI ? li.querySelector("span") : null;
const txt = span ? span.textContent ?? "" : li.textContent;
if ((isCI && txt.trim() === "") || (!isCI && txt.trim() === "")) {
exitListItem(li, parentUl, isCI);
return true;
}
// Continue list
const newLi = createListContinuation(isCI);
parentUl.insertBefore(newLi, li.nextSibling);
const r = document.createRange();
const s = isCI ? newLi.querySelector("span") ?? newLi : newLi;
r.setStart((s.firstChild ?? s), 0);
r.collapse(true);
sel.removeAllRanges();
sel.addRange(r);
return true;
}
}
node = node.parentElement;
}
return false;
}
function exitListItem(li, ul, isChecklist) {
const sel = window.getSelection();
if (!sel)
return;
const p = document.createElement("p");
if (ul.children.length === 1) {
ul.replaceWith(p);
}
else {
ul.after(p);
}
const r = document.createRange();
r.setStart(p, 0);
r.collapse(true);
sel.removeAllRanges();
sel.addRange(r);
}
function createListContinuation(isChecklist) {
if (isChecklist) {
const li = document.createElement("li");
li.className = "checklist-item";
const cb = document.createElement("input");
cb.type = "checkbox";
cb.tabIndex = -1;
cb.addEventListener("change", () => {
li.classList.toggle("checked", cb.checked);
if (cb.checked)
cb.setAttribute("checked", "");
else
cb.removeAttribute("checked");
});
const span = document.createElement("span");
li.append(cb, span);
return li;
}
return document.createElement("li");
}
const MARKDOWN_RULES = [
{ trigger: /^#\s+/, exec: () => document.execCommand("formatBlock", false, "h1") },
{ trigger: /^##\s+/, exec: () => document.execCommand("formatBlock", false, "h2") },
{ trigger: /^###\s+/, exec: () => document.execCommand("formatBlock", false, "h3") },
{ trigger: /^\- \[\] /, exec: () => wrapAsChecklist(false) },
{ trigger: /^\- \[x\] /, exec: () => wrapAsChecklist(true) },
{ trigger: /^-\s+/, exec: () => document.execCommand("insertUnorderedList", false, undefined) },
{ trigger: /^\d+\.\s+/, exec: () => document.execCommand("insertOrderedList", false, undefined) },
{ trigger: /^>\s+/, exec: () => document.execCommand("formatBlock", false, "blockquote") },
];
function checkMarkdownShortcut(char) {
if (char !== " ")
return false;
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0)
return false;
const range = sel.getRangeAt(0);
let element = range.startContainer.nodeType === Node.TEXT_NODE
? range.startContainer.parentElement
: range.startContainer;
if (!element)
return false;
let block = element;
const BLOCKS = ["P", "H1", "H2", "H3", "BLOCKQUOTE", "LI"];
while (block && block !== _editor && block.nodeType === Node.ELEMENT_NODE) {
if (BLOCKS.includes(block.tagName))
break;
block = block.parentElement;
}
if (!block || block === _editor)
return false;
for (const rule of MARKDOWN_RULES) {
if (rule.trigger.test(block.textContent ?? "")) {
rule.exec();
return true;
}
}
return false;
}
function clearMarkdownShortcuts() { }
function wrapAsChecklist(checked) {
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0)
return;
const range = sel.getRangeAt(0);
let element = range.startContainer.nodeType === Node.TEXT_NODE
? range.startContainer.parentElement
: range.startContainer;
if (!element)
return;
const BTE = ["P", "H1", "H2", "H3", "DIV"];
while (element !== _editor && element !== null && element.nodeType === Node.ELEMENT_NODE) {
const hE = element;
if (BTE.includes(hE.tagName))
break;
element = hE.parentElement;
}
if (!element || element === _editor)
return;
const parentUl = element.parentElement?.tagName === "UL" ? element.parentElement : null;
const li = document.createElement("li");
li.className = "checklist-item" + (checked ? " checked" : "");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.checked = checked;
cb.tabIndex = -1;
cb.addEventListener("change", () => {
li.classList.toggle("checked", cb.checked);
if (cb.checked)
cb.setAttribute("checked", "");
else
cb.removeAttribute("checked");
scheduleAutoSave();
});
const span = document.createElement("span");
span.textContent = element.textContent ?? "";
li.append(cb, span);
if (parentUl) {
parentUl.insertBefore(li, element);
element.remove();
if (parentUl.children.length === 0)
parentUl.remove();
}
else {
const ul = document.createElement("ul");
ul.appendChild(li);
element.replaceWith(ul);
}
const r = document.createRange();
r.setStart(span, 0);
r.collapse(true);
sel.removeAllRanges();
sel.addRange(r);
}
function insertChecklist() {
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0)
return;
const range = sel.getRangeAt(0);
const li = document.createElement("li");
li.className = "checklist-item";
const cb = document.createElement("input");
cb.type = "checkbox";
cb.tabIndex = -1;
cb.addEventListener("change", () => {
li.classList.toggle("checked", cb.checked);
if (cb.checked)
cb.setAttribute("checked", "");
else
cb.removeAttribute("checked");
scheduleAutoSave();
});
const span = document.createElement("span");
li.append(cb, span);
if (range.startContainer.nodeType === Node.ELEMENT_NODE) {
const el = range.startContainer;
if (el.tagName === "LI") {
// Already in a list
const ul = el.parentElement;
ul.insertBefore(li, el.nextSibling);
const r2 = document.createRange();
r2.setStart(span, 0);
r2.collapse(true);
sel.removeAllRanges();
sel.addRange(r2);
return;
}
}
// At block level or text
if (range.startContainer.nodeType === Node.TEXT_NODE) {
const tn = range.startContainer;
const splitLen = range.startOffset;
if (splitLen > 0) {
tn.splitText(splitLen);
tn.parentElement?.before(document.createElement("br"));
tn.parentElement?.before(li);
}
else {
tn.parentElement?.before(li);
}
}
else {
const ul = document.createElement("ul");
ul.appendChild(li);
range.insertNode(ul);
}
const r2 = document.createRange();
r2.setStart(span, 0);
r2.collapse(true);
sel.removeAllRanges();
sel.addRange(r2);
}
// ---- Load/Render a Note ----
function loadNote(note) {
_activeNoteId = note.id;
_savedBody = note.bodyHTML;
_savedTitle = note.title;
if (_editor) {
_editor.innerHTML = note.bodyHTML;
restoreChecklistStates();
}
if (_titleInput)
_titleInput.value = note.title;
renderTags(note.tags);
const es = document.getElementById("empty-state");
const ea = document.getElementById("editor-active");
const tb = document.getElementById("editor-toolbar");
if (es)
es.style.display = "none";
if (ea)
ea.style.display = "flex";
if (tb)
tb.style.display = "flex";
const te = document.getElementById("editor-time");
if (te)
te.textContent = (0, model_1.formatDate)(note.updated);
_editor?.focus();
refreshToolbarState();
}
function restoreChecklistStates() {
if (!_editor)
return;
const items = _editor.querySelectorAll('li.checklist-item input[type="checkbox"]');
items.forEach(cb => {
const li = cb.closest('li.checklist-item');
if (li) {
// Set DOM checked state from class
cb.checked = li.classList.contains('checked');
}
});
}
function unloadEditor() {
commitSave();
_activeNoteId = null;
const es = document.getElementById("empty-state");
const ea = document.getElementById("editor-active");
const tb = document.getElementById("editor-toolbar");
if (es)
es.style.display = "";
if (ea)
ea.style.display = "none";
if (tb)
tb.style.display = "none";
if (_editor)
_editor.innerHTML = "";
if (_titleInput)
_titleInput.value = "";
if (_tagChipContainer)
_tagChipContainer.innerHTML = "";
refreshToolbarState();
}
// ---- Tags ----
function setupTagInput() {
if (!_tagInput)
return;
_tagInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
const tag = _tagInput.value.trim().toLowerCase();
if (tag && _activeNoteId) {
addTagFromUI(tag);
_tagInput.value = "";
}
}
});
}
function escapeDisplay(str) {
const d = document.createElement("div");
d.textContent = str;
return d.innerHTML;
}
function addTagFromUI(tag) {
if (!_activeNoteId || !_tagChipContainer)
return;
if (_tagChipContainer.querySelector(`span[data-tag="${CSS.escape(tag)}"]`))
return;
const chip = document.createElement("span");
chip.className = "tag-chip";
chip.dataset.tag = tag;
chip.innerHTML = `<span>${escapeDisplay(tag)}</span><span class="tag-chip-remove">×</span>`;
chip.querySelector(".tag-chip-remove")?.addEventListener("click", () => removeTagFromUI(tag));
_tagChipContainer.appendChild(chip);
(0, model_1.addTag)(_activeNoteId, tag);
(0, store_1.saveAppData)((0, model_1.getState)());
}
function removeTagFromUI(tag) {
if (!_activeNoteId)
return;
const chip = _tagChipContainer?.querySelector(`span[data-tag="${CSS.escape(tag)}"]`);
chip?.parentElement?.remove();
(0, model_1.removeTag)(_activeNoteId, tag);
(0, store_1.saveAppData)((0, model_1.getState)());
}
function renderTags(tags) {
if (!_tagChipContainer)
return;
_tagChipContainer.innerHTML = "";
for (const tag of tags) {
const chip = document.createElement("span");
chip.className = "tag-chip";
chip.dataset.tag = tag;
chip.innerHTML = `<span>${escapeDisplay(tag)}</span><span class="tag-chip-remove">×</span>`;
chip.querySelector(".tag-chip-remove")?.addEventListener("click", () => removeTagFromUI(tag));
_tagChipContainer.appendChild(chip);
}
}
};
// ── module: src/sidebar.ts ──
__mods["src/sidebar.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Sidebar — Notebook Tree & Navigation
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSidebarCallbacks = setSidebarCallbacks;
exports.initSidebar = initSidebar;
exports.renderSidebar = renderSidebar;
exports.setActiveNotebook = setActiveNotebook;
const model_1 = require("./model");
const store_1 = require("./store");
let _onNoteListChange = null;
let _onEditorUnload = null;
function setSidebarCallbacks(onNoteListChange, onEditorUnload) {
_onNoteListChange = onNoteListChange;
_onEditorUnload = onEditorUnload;
}
const SIDEBAR_LIST = document.getElementById("sidebar-list");
const SIDEBAR_SEARCH = document.getElementById("sidebar-search");
const ADD_NB_BTN = document.getElementById("add-notebook-btn");
function initSidebar() {
ADD_NB_BTN.addEventListener("click", () => {
const id = doAddNotebook();
setActiveNotebook(id);
renderSidebar();
if (_onNoteListChange)
_onNoteListChange();
});
SIDEBAR_SEARCH.addEventListener("input", () => {
if (_onNoteListChange)
_onNoteListChange();
});
}
function doAddNotebook() {
let name = "New Notebook";
let i = 1;
const state = (0, model_1.getState)();
while (state.notebooks.some(n => n.name === name + (i > 1 ? ` ${i}` : ""))) {
i++;
}
if (i > 1)
name += ` ${i}`;
return (0, model_1.addNotebook)(name);
}
// Render sidebar notebook tree
function renderSidebar() {
const state = (0, model_1.getState)();
const query = SIDEBAR_SEARCH.value.trim().toLowerCase();
SIDEBAR_LIST.innerHTML = "";
// Virtual "All Notes" view
const allBtn = createVirtualItem("📋", "All Notes", !state.activeNotebookId && state.currentView !== "trash");
allBtn.addEventListener("click", () => {
if (state.currentView === "trash")
(0, model_1.setCurrentView)("all");
setActiveNotebook(null);
renderSidebar();
if (_onNoteListChange)
_onNoteListChange();
if (_onEditorUnload)
_onEditorUnload();
});
SIDEBAR_LIST.appendChild(allBtn);
// Virtual "Trash"
const trashCount = state.notes.filter(n => n.notebookId === "__trash__").length;
const trashBtn = createVirtualItem("🗑️", `Trash${trashCount ? ` (${trashCount})` : ""}`, state.currentView === "trash");
trashBtn.addEventListener("click", () => {
(0, model_1.setCurrentView)(state.currentView === "trash" ? "all" : "trash");
renderSidebar();
if (_onNoteListChange)
_onNoteListChange();
if (_onEditorUnload)
_onEditorUnload();
});
SIDEBAR_LIST.appendChild(trashBtn);
if (query || state.currentView === "trash")
return;
for (const nb of state.notebooks) {
const isActive = state.activeNotebookId === nb.id;
const item = document.createElement("div");
item.className = "notebook-item";
const header = document.createElement("div");
header.className = "notebook-header" + (isActive ? " active" : "");
const arrow = document.createElement("span");
arrow.className = "notebook-arrow" + (nb.collapsed ? "" : " open");
arrow.textContent = "▶";
const nameEl = document.createElement("span");
nameEl.className = "notebook-name-display";
nameEl.textContent = nb.name;
const actionsDiv = document.createElement("span");
actionsDiv.className = "notebook-actions";
// Rename button
const renameBtn = document.createElement("button");
renameBtn.className = "btn btn-sm";
renameBtn.textContent = "✎";
renameBtn.title = "Rename";
renameBtn.addEventListener("click", (e) => {
e.stopPropagation();
startRename(nb, header, actionsDiv);
});
// Delete button
const delBtn = document.createElement("button");
delBtn.className = "btn btn-sm";
delBtn.textContent = "×";
delBtn.title = "Delete";
delBtn.style.color = "#e53e3e";
delBtn.addEventListener("click", (e) => {
e.stopPropagation();
if (confirm(`Delete "${nb.name}"? Notes will move.`)) {
(0, model_1.deleteNotebook)(nb.id);
(0, store_1.saveAppData)((0, model_1.getState)());
renderSidebar();
if (_onNoteListChange)
_onNoteListChange();
}
});
actionsDiv.append(renameBtn, delBtn);
header.append(arrow, nameEl, actionsDiv);
header.addEventListener("click", () => {
if (header.querySelector(".notebook-name-input"))
return;
(0, model_1.toggleNotebookCollapse)(nb.id);
if (!isActive)
setActiveNotebook(nb.id);
renderSidebar();
if (_onNoteListChange)
_onNoteListChange();
});
item.appendChild(header);
const childrenWrap = document.createElement("div");
childrenWrap.className = "notebook-children";
childrenWrap.style.display = nb.collapsed ? "none" : "";
const noteCount = state.notes.filter(n => n.notebookId === nb.id && n.notebookId !== "__trash__").length;
const badge = document.createElement("span");
badge.style.cssText = "font-size:11px;color:var(--text-muted);margin-left:auto;padding-right:8px;";
badge.textContent = String(noteCount);
actionsDiv.prepend(badge);
childrenWrap.appendChild(item);
SIDEBAR_LIST.appendChild(childrenWrap);
}
}
function createVirtualItem(icon, label, active) {
const div = document.createElement("div");
div.className = "virtual-notebook" + (active ? " active" : "");
div.innerHTML = `<span class="virtual-notebook-icon">${icon}</span><span>${label}</span>`;
return div;
}
function startRename(nb, header, actions) {
const existingInput = header.querySelector(".notebook-name-input");
if (existingInput)
return;
const nameDisplay = header.querySelector(".notebook-name-display");
const input = document.createElement("input");
input.type = "text";
input.className = "notebook-name-input";
input.value = nb.name;
nameDisplay.replaceWith(input);
input.focus();
input.select();
const finish = () => {
const newName = input.value.trim() || nb.name;
(0, model_1.renameNotebook)(nb.id, newName);
(0, store_1.saveAppData)((0, model_1.getState)());
renderSidebar();
};
input.addEventListener("blur", finish);
input.addEventListener("keydown", (e) => {
if (e.key === "Enter")
input.blur();
if (e.key === "Escape") {
input.value = nb.name;
input.blur();
}
});
}
function setActiveNotebook(id) {
const state = (0, model_1.getState)();
state.activeNotebookId = id;
(0, store_1.saveAppData)(state);
}
};
// ── module: src/notelist.ts ──
__mods["src/notelist.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Note List — Filtering, Sorting, Pinning
// ============================================
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.setNoteListCallbacks = setNoteListCallbacks;
exports.renderNoteList = renderNoteList;
const model_1 = require("./model");
const sidebar_1 = require("./sidebar");
const store_1 = require("./store");
const NOTELIST_BODY = document.getElementById("notelist-body");
const NOTELIST_TITLE = document.getElementById("notelist-title");
const NOTELIST_COUNT = document.getElementById("notelist-count");
const SORT_BTN = document.getElementById("sort-btn");
const FILTER_INPUT = document.getElementById("notelist-search");
let _onNoteSelected = null;
function setNoteListCallbacks(onNoteSelected) {
_onNoteSelected = onNoteSelected;
}
SORT_BTN.addEventListener("click", () => {
const state = (0, model_1.getState)();
const idx = ["modified", "created", "alphabetical"].indexOf(state.sortOption);
const next = (idx + 1) % 3;
(0, model_1.setSortOption)(["modified", "created", "alphabetical"][next]);
renderNoteList();
});
FILTER_INPUT.addEventListener("input", () => renderNoteList());
function renderNoteList() {
const state = (0, model_1.getState)();
const query = FILTER_INPUT.value.trim().toLowerCase();
// Update title based on active notebook
if (state.currentView === "trash") {
NOTELIST_TITLE.textContent = "Trash";
}
else if (state.activeNotebookId) {
const nb = state.notebooks.find(n => n.id === state.activeNotebookId);
NOTELIST_TITLE.textContent = nb?.name ?? "All Notes";
}
else {
NOTELIST_TITLE.textContent = "All Notes";
}
let notes;
let allNotes;
if (state.currentView === "trash") {
allNotes = (0, model_1.getTrashNotes)();
}
else {
allNotes = (0, model_1.filterActiveNotes)({ query });
}
// Group by pinned
const pinned = allNotes.filter(n => n.pinned);
const unpinned = allNotes.filter(n => !n.pinned);
const sortedPinned = (0, model_1.sortNotes)(pinned, state.sortOption);
const sortedUnpinned = (0, model_1.sortNotes)(unpinned, state.sortOption);
notes = [...sortedPinned, ...sortedUnpinned];
NOTELIST_COUNT.textContent = String(allNotes.length);
NOTELIST_BODY.innerHTML = "";
if (notes.length === 0) {
const empty = document.createElement("div");
empty.style.cssText = "padding:40px 20px;text-align:center;color:var(--text-muted);font-size:13px;";
empty.textContent = state.currentView === "trash" ? "Trash is empty" : (query ? "No matching notes" : "No notes yet\nCreate one with Ctrl+N");
NOTELIST_BODY.appendChild(empty);
return;
}
for (const note of notes) {
const card = createNoteCard(note, state.selectedNoteId);
card.addEventListener("click", () => {
(0, model_1.selectNote)(note.id);
(0, store_1.saveAppData)((0, model_1.getState)());
if (_onNoteSelected)
_onNoteSelected(note.id);
});
NOTELIST_BODY.appendChild(card);
}
}
function createNoteCard(note, selectedId) {
const card = document.createElement("div");
card.className = "note-card" + (note.id === selectedId ? " selected" : "");
card.draggable = true;
// Title
const titleDiv = document.createElement("div");
titleDiv.className = "note-card-title";
if (note.pinned) {
const pin = document.createElement("span");
pin.className = "note-pin";
pin.textContent = "📌";
card.insertBefore(pin, card.firstChild);
}
// Handle search highlighting in title
const query = FILTER_INPUT.value.trim();
if (query && note.title.toLowerCase().includes(query)) {
titleDiv.innerHTML = highlightText((0, model_1.escapeHTML)(note.title || "(Untitled)"), query);
}
else {
titleDiv.textContent = note.title || "(Untitled)";
}
card.appendChild(titleDiv);
// Snippet preview
const snippetDiv = document.createElement("div");
snippetDiv.className = "snippet";
const textContent = (0, model_1.extractText)(note.bodyHTML);
const snippet = textContent.substring(0, 100).trim() || "Empty note";
if (query && textContent.toLowerCase().includes(query)) {
snippetDiv.innerHTML = highlightText((0, model_1.escapeHTML)(snippet), query);
}
else {
snippetDiv.textContent = snippet;
}
card.appendChild(snippetDiv);
// Tags
if (note.tags.length > 0) {
const tagWrap = document.createElement("div");
tagWrap.className = "note-card-tags";
for (const tag of note.tags.slice(0, 3)) {
const chip = document.createElement("span");
chip.className = "note-card-tag";
chip.textContent = "#" + tag;
chip.addEventListener("click", (e) => {
e.stopPropagation();
FILTER_INPUT.value = tag;
renderNoteList();
});
tagWrap.appendChild(chip);
}
if (note.tags.length > 3) {
const more = document.createElement("span");
more.className = "note-card-tag";
more.textContent = "+" + (note.tags.length - 3);
tagWrap.appendChild(more);
}
card.appendChild(tagWrap);
}
// Time
const timeDiv = document.createElement("div");
timeDiv.className = "note-card-time";
timeDiv.textContent = (0, model_1.formatDate)(note.updated);
card.appendChild(timeDiv);
// Context menu on right click
card.addEventListener("contextmenu", (e) => {
e.preventDefault();
showNoteContextMenu(e.clientX, e.clientY, note);
});
// Drag start
card.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", note.id);
e.dataTransfer.effectAllowed = "move";
});
return card;
}
function highlightText(text, query) {
const idx = text.toLowerCase().indexOf(query.toLowerCase());
if (idx < 0)
return text;
return text.substring(0, idx) + '<mark>' + text.substring(idx, idx + query.length) + '</mark>' + text.substring(idx + query.length);
}
// ---- Context Menu ----
function showNoteContextMenu(x, y, note) {
closeContextMenu();
const menu = document.getElementById("context-menu");
const isTrash = note.notebookId === "__trash__";
let html = "";
if (!isTrash) {
html += `<div class="context-menu-item" data-action="pin">${note.pinned ? "📌 Unpin" : "📌 Pin"}${note.pinned ? "" : '<span class="context-menu-shortcut">⌘+Shift+P</span>'}</div>`;
html += `<div class="context-menu-item" data-action="duplicate">📄 Duplicate<span class="context-menu-shortcut">⌘+D</span></div>`;
html += `<div class="context-menu-sep"></div>`;
html += `<div class="context-menu-item" data-action="move"><span style="margin-right:8px;">↗ Move to...</span></div>`;
}
html += `<div class="context-menu-item danger" data-action="delete">${isTrash ? '🗑 Perma Delete' : '🗑 Delete'}</div>`;
menu.innerHTML = html;
menu.style.left = Math.min(x, window.innerWidth - 200) + "px";
menu.style.top = Math.min(y, window.innerHeight - 200) + "px";
menu.style.display = "block";
// Apply actions
menu.querySelectorAll(".context-menu-item").forEach(item => {
item.addEventListener("click", () => {
const action = item.dataset.action;
switch (action) {
case "pin":
togglePin(note.id);
break;
case "duplicate":
doDuplicate(note);
break;
case "move":
showMoveMenu(note, x, y);
break;
case "delete":
doDelete(note);
break;
}
closeContextMenu();
});
});
// Close menu items hover styling
menu.classList.add("context-menu");
}
function showMoveMenu(note, x, y) {
const menu = document.getElementById("context-menu");
let html = '<div class="context-menu-item" data-action="back">← Back</div>';
for (const nb of (0, model_1.getState)().notebooks) {
const label = nb.name + (nb.id === note.notebookId ? " (current)" : "");
html += `<div class="context-menu-item" data-move="${nb.id}">📁 ${label}</div>`;
}
menu.innerHTML = html;
menu.style.left = (x - 150) + "px";
menu.style.top = y + "px";
menu.querySelectorAll("[data-move]").forEach(mi => {
mi.addEventListener("click", () => {
(0, model_1.moveNote)(note.id, mi.dataset.move);
(0, store_1.saveAppData)((0, model_1.getState)());
(0, sidebar_1.renderSidebar)();
renderNoteList();
closeContextMenu();
});
});
}
function closeContextMenu() {
const menu = document.getElementById("context-menu");
if (menu)
menu.style.display = "none";
}
document.addEventListener("click", () => closeContextMenu());
function togglePin(noteId) {
const note = (0, model_1.getState)().notes.find(n => n.id === noteId);
if (note) {
(0, model_1.updateNote)(noteId, { pinned: !note.pinned });
(0, store_1.saveAppData)((0, model_1.getState)());
renderNoteList();
}
}
function doDuplicate(note) {
Promise.resolve().then(() => __importStar(require("./model"))).then(m => {
m.duplicateNote(note.id);
(0, store_1.saveAppData)(m.getState());
renderNoteList();
});
}
function doDelete(note) {
if (note.notebookId === "__trash__") {
if (confirm("Permanently delete?")) {
(0, model_1.permanentlyDeleteNote)(note.id);
(0, store_1.saveAppData)((0, model_1.getState)());
if ((0, model_1.getState)().selectedNoteId === note.id) {
const el1 = document.getElementById("empty-state");
const el2 = document.getElementById("editor-active");
const el3 = document.getElementById("editor-toolbar");
if (el1)
el1.style.display = "";
if (el2)
el2.style.display = "none";
if (el3)
el3.style.display = "none";
(0, model_1.getState)().selectedNoteId = null;
(0, store_1.saveAppData)((0, model_1.getState)());
}
renderNoteList();
(0, sidebar_1.renderSidebar)();
}
}
else {
(0, model_1.deleteNote)(note.id);
(0, store_1.saveAppData)((0, model_1.getState)());
renderNoteList();
(0, sidebar_1.renderSidebar)();
}
}
};
// ── module: src/layout.ts ──
__mods["src/layout.ts"] = function (exports, require, module) {
"use strict";
// ============================================
// Layout — Resize Handles & Sidebar Collapse
// ============================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.initLayout = initLayout;
const store_1 = require("./store");
const RESIZE_SIDEBAR = document.getElementById("resize-sidebar");
const RESIZE_NOTELIST = document.getElementById("resize-notelist");
const SIDEBAR = document.getElementById("sidebar");
const NOTELIST = document.getElementById("notelist");
const TOGGLE_BTN = document.getElementById("toggle-sidebar-btn");
let _savedLayout = null;
function initLayout() {
_savedLayout = (0, store_1.loadLayout)();
// Apply saved layout
if (_savedLayout) {
SIDEBAR.style.width = _savedLayout.sidebarWidth + "px";
NOTELIST.style.width = _savedLayout.notelistWidth + "px";
if (_savedLayout.sidebarCollapsed) {
SIDEBAR.classList.add("collapsed");
TOGGLE_BTN.style.display = "flex";
}
}
// Sidebar collapse button (appears in top-left when collapsed)
TOGGLE_BTN.addEventListener("click", () => {
SIDEBAR.classList.remove("collapsed");
TOGGLE_BTN.style.display = "none";
saveCurrentLayout();
});
setupResize(RESIZE_SIDEBAR, SIDEBAR, "sidebar");
setupResize(RESIZE_NOTELIST, NOTELIST, "notelist");
}
function setupResize(handle, target, name) {
let startX = 0;
let startW = 0;
handle.addEventListener("mousedown", (e) => {
e.preventDefault();
startX = e.clientX;
startW = target.offsetWidth;
handle.classList.add("active");
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
const onMove = (ev) => {
const dx = ev.clientX - startX;
const min = name === "sidebar" ? 180 : 200;
const max = name === "sidebar" ? 400 : 450;
const newW = Math.max(min, Math.min(max, startW + dx));
target.style.width = newW + "px";
};
const onUp = () => {
handle.classList.remove("active");
document.body.style.cursor = "";
document.body.style.userSelect = "";
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
saveCurrentLayout();
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
});
}
function saveCurrentLayout() {
(0, store_1.saveLayout)({
sidebarWidth: SIDEBAR.offsetWidth,
notelistWidth: NOTELIST.offsetWidth,
sidebarCollapsed: SIDEBAR.classList.contains("collapsed"),
});
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.7-flash","provider":"openrouter","persona":"typescript","sessionId":"ec6ae2a2-a06d-4fec-a0ee-9a4370038ff0","tokensIn":13720851,"tokensOut":73824,"tokensTotal":13794675,"cost":0.49469005000000016,"turns":170,"toolCalls":169,"failedToolCalls":12,"timestamp":"2026-08-02T03:54:48.066Z"} -->