Notes
thinkingcap qwen3.6 27b · typescript

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 81,369 bytes · SHA-256 8b7bb220afa2
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes — Your Personal Knowledge Base</title>
<style>
/* ── css: layout ── */
/* ─── Reset & Base ───────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
overflow: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* ─── CSS Custom Properties (Light Theme) ────────────────────── */
.notes-app {
--bg: #faf9f7;
--surface: #ffffff;
--surface-hover: #f5f4f1;
--surface-active: #ebe9e4;
--sidebar-bg: #f3f2ef;
--border: #e8e6e1;
--text: #2c2c2c;
--text-secondary: #7a7a7a;
--text-tertiary: #a0a0a0;
--accent: #5b6abf;
--accent-hover: #4d5aa8;
--accent-light: rgba(91, 106, 191, 0.08);
--danger: #c73e3e;
--danger-hover: #b33535;
--tag-bg: #edebe6;
--tag-text: #5a5a5a;
--editor-bg: #ffffff;
--toolbar-bg: #faf9f7;
--shadow: rgba(0, 0, 0, 0.06);
--checkbox-checked: #5b6abf;
--search-highlight: #fff3cd;
--pin-color: #e8a42d;
}
.notes-app.dark {
--bg: #1a1a1e;
--surface: #242428;
--surface-hover: #2c2c30;
--surface-active: #353539;
--sidebar-bg: #1e1e22;
--border: #36363a;
--text: #d4d4d8;
--text-secondary: #8b8b90;
--text-tertiary: #5c5c60;
--accent: #7c8bd4;
--accent-hover: #6a7ac2;
--accent-light: rgba(124, 139, 212, 0.1);
--danger: #d45555;
--danger-hover: #c24444;
--tag-bg: #333338;
--tag-text: #a0a0a5;
--editor-bg: #242428;
--toolbar-bg: #1e1e22;
--shadow: rgba(0, 0, 0, 0.2);
--checkbox-checked: #7c8bd4;
--search-highlight: #5a4f20;
--pin-color: #d4a030;
}
/* ─── Three-Pane Layout ────────────────────────────────────── */
.notes-app {
display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
}
#sidebar {
width: var(--sidebar-width, 240px);
min-width: 180px;
max-width: 400px;
background: var(--sidebar-bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
transition: width 0.2s ease, min-width 0.2s ease, opacity 0.2s ease;
overflow: hidden;
}
#sidebar.collapsed {
width: 0 !important;
min-width: 0 !important;
border-right: none;
opacity: 0;
pointer-events: none;
}
#note-list-pane {
width: var(--note-list-width, 300px);
min-width: 220px;
max-width: 500px;
background: var(--surface);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
#editor-pane {
flex: 1;
min-width: 300px;
background: var(--editor-bg);
display: flex;
flex-direction: column;
overflow: hidden;
}
/* ─── Resize Handles ───────────────────────────────────────── */
.resize-handle {
width: 4px;
cursor: col-resize;
background: transparent;
transition: background 0.15s ease;
flex-shrink: 0;
z-index: 10;
}
.resize-handle:hover,
.resize-handle.active {
background: var(--accent);
}
/* ─── Sidebar Header ───────────────────────────────────────── */
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 16px 8px;
flex-shrink: 0;
}
.app-title {
font-size: 15px;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text);
}
/* ─── Sidebar Footer ───────────────────────────────────────── */
.sidebar-footer {
padding: 8px 12px 16px;
flex-shrink: 0;
}
.btn-ghost {
width: 100%;
padding: 8px 12px;
background: transparent;
border: 1px dashed var(--border);
border-radius: 6px;
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-ghost:hover {
background: var(--surface-hover);
color: var(--text);
border-color: var(--accent);
}
/* ─── Notebook List ────────────────────────────────────────── */
#notebook-list {
flex: 1;
overflow-y: auto;
padding: 4px 8px;
}
.notebook-item {
display: flex;
align-items: center;
padding: 6px 10px;
margin: 1px 0;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
color: var(--text-secondary);
transition: all 0.12s ease;
user-select: none;
}
.notebook-item:hover {
background: var(--surface-hover);
color: var(--text);
}
.notebook-item.active {
background: var(--accent-light);
color: var(--accent);
font-weight: 500;
}
.notebook-icon {
margin-right: 8px;
font-size: 14px;
flex-shrink: 0;
}
.notebook-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notebook-count {
font-size: 11px;
color: var(--text-tertiary);
margin-left: 6px;
flex-shrink: 0;
}
.notebook-actions {
display: none;
gap: 2px;
margin-left: 4px;
}
.notebook-item:hover .notebook-actions {
display: flex;
}
.notebook-action-btn {
background: none;
border: none;
cursor: pointer;
padding: 2px 4px;
font-size: 12px;
color: var(--text-tertiary);
border-radius: 3px;
}
.notebook-action-btn:hover {
background: var(--surface-active);
color: var(--text);
}
/* ─── Note List Header ────────────────────────────────────── */
.note-list-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 16px 8px;
flex-shrink: 0;
}
.note-list-header h2 {
font-size: 15px;
font-weight: 600;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.note-list-actions {
display: flex;
align-items: center;
gap: 4px;
}
#search-input {
width: 120px;
padding: 5px 8px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface-hover);
color: var(--text);
font-size: 12px;
outline: none;
transition: all 0.15s ease;
}
#search-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-light);
}
/* ─── Note List ────────────────────────────────────────────── */
#note-list {
flex: 1;
overflow-y: auto;
padding: 4px 8px 16px;
}
.note-item {
display: flex;
flex-direction: column;
padding: 10px 12px;
margin: 2px 0;
border-radius: 8px;
cursor: pointer;
transition: all 0.12s ease;
position: relative;
}
.note-item:hover {
background: var(--surface-hover);
}
.note-item.active {
background: var(--accent-light);
}
.note-item-header {
display: flex;
align-items: center;
gap: 6px;
}
.note-pin-indicator {
font-size: 10px;
color: var(--pin-color);
flex-shrink: 0;
}
.note-title {
font-size: 13.5px;
font-weight: 500;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.note-item.active .note-title {
color: var(--accent);
}
.note-snippet {
font-size: 12px;
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 3px;
padding-left: 16px;
}
.note-meta {
display: flex;
align-items: center;
gap: 6px;
margin-top: 4px;
padding-left: 16px;
}
.note-date {
font-size: 11px;
color: var(--text-tertiary);
}
.note-tags {
display: flex;
gap: 3px;
flex-wrap: wrap;
}
.note-tag {
font-size: 10px;
padding: 1px 6px;
background: var(--tag-bg);
color: var(--tag-text);
border-radius: 3px;
cursor: pointer;
transition: all 0.1s ease;
}
.note-tag:hover {
background: var(--accent-light);
color: var(--accent);
}
/* ─── Icon Buttons ────────────────────────────────────────── */
.icon-btn {
background: none;
border: none;
cursor: pointer;
padding: 4px 6px;
font-size: 14px;
color: var(--text-secondary);
border-radius: 5px;
transition: all 0.12s ease;
line-height: 1;
}
.icon-btn:hover {
background: var(--surface-hover);
color: var(--text);
}
.icon-btn.danger:hover {
background: rgba(199, 62, 62, 0.1);
color: var(--danger);
}
/* ─── Editor Header ────────────────────────────────────────── */
.editor-header {
display: flex;
align-items: center;
padding: 16px 32px 8px;
gap: 12px;
flex-shrink: 0;
}
#note-title-input {
flex: 1;
font-size: 24px;
font-weight: 700;
border: none;
background: transparent;
color: var(--text);
outline: none;
font-family: inherit;
letter-spacing: -0.02em;
}
#note-title-input::placeholder {
color: var(--text-tertiary);
}
.editor-actions {
display: flex;
align-items: center;
gap: 2px;
}
/* ─── Toolbar ──────────────────────────────────────────────── */
.toolbar {
display: flex;
align-items: center;
padding: 4px 32px;
gap: 1px;
border-bottom: 1px solid var(--border);
background: var(--toolbar-bg);
flex-shrink: 0;
flex-wrap: wrap;
}
.toolbar-btn {
background: none;
border: none;
cursor: pointer;
padding: 5px 8px;
font-size: 13px;
color: var(--text-secondary);
border-radius: 4px;
transition: all 0.1s ease;
font-weight: 500;
min-width: 28px;
text-align: center;
}
.toolbar-btn:hover {
background: var(--surface-hover);
color: var(--text);
}
.toolbar-btn.active {
background: var(--accent-light);
color: var(--accent);
}
.toolbar-separator {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 4px;
}
/* ─── Editor ──────────────────────────────────────────────── */
#editor {
flex: 1;
padding: 24px 32px 64px;
overflow-y: auto;
outline: none;
font-family: Georgia, 'Liberation Serif', 'Times New Roman', serif;
font-size: 15.5px;
line-height: 1.75;
color: var(--text);
}
#editor:empty::before {
content: "Start writing…";
color: var(--text-tertiary);
pointer-events: none;
}
#editor h1 { font-size: 2em; font-weight: 700; margin: 0.6em 0 0.3em; letter-spacing: -0.02em; }
#editor h2 { font-size: 1.5em; font-weight: 600; margin: 0.5em 0 0.25em; }
#editor h3 { font-size: 1.25em; font-weight: 600; margin: 0.4em 0 0.2em; }
#editor blockquote {
border-left: 3px solid var(--accent);
padding-left: 16px;
margin: 8px 0;
color: var(--text-secondary);
font-style: italic;
}
#editor code {
background: var(--surface-hover);
padding: 2px 5px;
border-radius: 3px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9em;
}
#editor hr {
border: none;
border-top: 1px solid var(--border);
margin: 16px 0;
}
/* Checklist items */
.checklist-item {
list-style: none;
display: flex;
align-items: flex-start;
gap: 8px;
padding: 2px 0;
}
.checklist-checkbox {
width: 16px;
height: 16px;
border: 2px solid var(--border);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
margin-top: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.1s ease;
}
.checklist-checkbox.checked {
background: var(--checkbox-checked);
border-color: var(--checkbox-checked);
}
.checklist-checkbox.checked::after {
content: "✓";
color: white;
font-size: 10px;
font-weight: bold;
}
/* ─── Empty State ──────────────────────────────────────────── */
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text-tertiary);
gap: 12px;
}
.empty-state svg {
opacity: 0.3;
}
.empty-state p {
font-size: 15px;
}
/* ─── Saved Indicator ──────────────────────────────────────── */
.saved-indicator {
position: fixed;
bottom: 20px;
right: 24px;
padding: 6px 14px;
background: var(--accent);
color: white;
font-size: 12px;
border-radius: 20px;
opacity: 0;
transform: translateY(8px);
transition: all 0.3s ease;
pointer-events: none;
z-index: 100;
}
.saved-indicator.show {
opacity: 1;
transform: translateY(0);
}
/* ─── Dropdown ────────────────────────────────────────────── */
.dropdown {
position: fixed;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 20px var(--shadow);
padding: 6px;
z-index: 50;
min-width: 160px;
}
.dropdown-title {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-tertiary);
padding: 4px 8px 6px;
font-weight: 600;
}
.dropdown-item {
padding: 7px 10px;
font-size: 13px;
cursor: pointer;
border-radius: 5px;
color: var(--text);
transition: all 0.1s ease;
}
.dropdown-item:hover {
background: var(--surface-hover);
}
/* ─── Utility ──────────────────────────────────────────────── */
.hidden { display: none !important; }
/* ─── Scrollbar Styling ────────────────────────────────────── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
/* ─── Search Highlight ────────────────────────────────────── */
mark.search-match {
background: var(--search-highlight);
color: inherit;
padding: 0 1px;
border-radius: 2px;
}
/* ─── Tag Input Area ──────────────────────────────────────── */
#tag-display {
display: flex;
gap: 4px;
align-items: center;
margin-right: 8px;
}
.tag-pill {
font-size: 11px;
padding: 2px 8px;
background: var(--tag-bg);
color: var(--tag-text);
border-radius: 10px;
display: flex;
align-items: center;
gap: 4px;
}
.tag-pill .remove-tag {
cursor: pointer;
font-size: 12px;
opacity: 0.6;
}
.tag-pill .remove-tag:hover {
opacity: 1;
}
/* ─── Trash-specific styles ──────────────────────────────── */
.trash-actions {
display: flex;
gap: 4px;
margin-top: 4px;
padding-left: 16px;
}
.trash-btn {
font-size: 11px;
padding: 2px 8px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.1s ease;
}
.restore-btn {
background: var(--accent-light);
color: var(--accent);
}
.permanent-delete-btn {
background: rgba(199, 62, 62, 0.1);
color: var(--danger);
}
/* ─── Sidebar collapse toggle ────────────────────────────── */
#collapse-sidebar-btn {
display: none;
position: fixed;
top: 12px;
left: 8px;
z-index: 20;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 4px 8px;
cursor: pointer;
font-size: 14px;
color: var(--text-secondary);
}
#collapse-sidebar-btn:hover {
background: var(--surface-hover);
}
.sidebar-collapsed-show #collapse-sidebar-btn {
display: block;
}
/* ─── Rename input inline ────────────────────────────────── */
.notebook-rename-input {
font-size: 13px;
border: 1px solid var(--accent);
border-radius: 4px;
padding: 2px 6px;
background: var(--surface);
color: var(--text);
outline: none;
flex: 1;
}
/* ─── Sort indicator ────────────────────────────────────── */
.sort-label {
font-size: 10px;
color: var(--text-tertiary);
padding: 2px 6px;
border-radius: 3px;
background: var(--surface-hover);
}
/* ─── Empty note list state ──────────────────────────────── */
.note-list-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: var(--text-tertiary);
font-size: 13px;
text-align: center;
gap: 8px;
}
.note-list-empty .empty-icon {
font-size: 28px;
opacity: 0.4;
}
</style>
</head>
<body>
<div id="app" class="notes-app">
<!-- Sidebar -->
<aside id="sidebar">
<div class="sidebar-header">
<span class="app-title">Notes</span>
<button id="theme-toggle" class="icon-btn" title="Toggle theme" aria-label="Toggle theme">🌙</button>
</div>
<nav id="notebook-list"></nav>
<div class="sidebar-footer">
<button id="new-notebook-btn" class="btn-ghost">+ New Notebook</button>
</div>
</aside>
<!-- Note List -->
<section id="note-list-pane">
<div class="note-list-header">
<h2 id="note-list-title">All Notes</h2>
<div class="note-list-actions">
<button id="sort-toggle" class="icon-btn" title="Sort order" aria-label="Toggle sort order">↕</button>
<input type="text" id="search-input" placeholder="Search notes…" aria-label="Search notes">
</div>
</div>
<div id="note-list"></div>
</section>
<!-- Editor -->
<main id="editor-pane">
<div id="empty-state" class="empty-state">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg>
<p>Select or create a note</p>
</div>
<div id="editor-container" class="hidden">
<div class="editor-header">
<input type="text" id="note-title-input" placeholder="Note title…" aria-label="Note title">
<div class="editor-actions">
<span id="tag-display"></span>
<button id="add-tag-btn" class="icon-btn" title="Add tag" aria-label="Add tag">🏷</button>
<button id="pin-note-btn" class="icon-btn" title="Pin note" aria-label="Toggle pin">📌</button>
<button id="move-note-btn" class="icon-btn" title="Move to notebook" aria-label="Move note">↗</button>
<button id="duplicate-note-btn" class="icon-btn" title="Duplicate" aria-label="Duplicate note">⧉</button>
<button id="delete-note-btn" class="icon-btn danger" title="Delete" aria-label="Delete note">🗑</button>
</div>
</div>
<div id="toolbar" class="toolbar"></div>
<div id="editor" contenteditable="true" spellcheck="true" role="textbox" aria-multiline="true"></div>
</div>
</main>
<!-- Resize handles -->
<div id="resize-handle-1" class="resize-handle"></div>
<div id="resize-handle-2" class="resize-handle"></div>
</div>
<!-- Move note dropdown -->
<div id="move-dropdown" class="dropdown hidden">
<div class="dropdown-title">Move to…</div>
<div id="move-notebook-list"></div>
</div>
<!-- Saved indicator -->
<div id="saved-indicator" class="saved-indicator">Saved</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./ui":"src/ui.ts"},"src/state.ts":{"./constants":"src/constants.ts","./types":"src/types.ts"},"src/editor.ts":{"./constants":"src/constants.ts","./types":"src/types.ts"},"src/ui.ts":{"./constants":"src/constants.ts","./state":"src/state.ts","./editor":"src/editor.ts","./types":"src/types.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";
// ─── Entry Point ──────────────────────────────────────────────
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 });
const UI = __importStar(require("./ui"));
function start() {
// Create collapse sidebar button dynamically
const app = document.getElementById("app");
if (app) {
const btn = document.createElement("button");
btn.id = "collapse-sidebar-btn";
btn.className = "icon-btn";
btn.textContent = "☰";
btn.title = "Show sidebar";
app.appendChild(btn);
}
// Initialize the UI
UI.initUI();
}
start();
};
// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ─── Tunable Constants ───────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateId = exports.DEFAULT_NOTEBOOKS = exports.ALLOWED_ATTRS = exports.ALLOWED_TAGS = exports.THEME_KEY = exports.LAYOUT_KEY = exports.STORAGE_KEY = exports.SNIPPET_LENGTH = exports.AUTOSAVE_DEBOUNCE = exports.FONTS = exports.COLORS = exports.APP_NAME = void 0;
exports.APP_NAME = "Notes";
exports.COLORS = {
light: {
bg: "#faf9f7",
surface: "#ffffff",
surfaceHover: "#f5f4f1",
surfaceActive: "#ebe9e4",
sidebarBg: "#f3f2ef",
border: "#e8e6e1",
text: "#2c2c2c",
textSecondary: "#7a7a7a",
textTertiary: "#a0a0a0",
accent: "#5b6abf",
accentHover: "#4d5aa8",
accentLight: "rgba(91, 106, 191, 0.08)",
danger: "#c73e3e",
dangerHover: "#b33535",
tagBg: "#edebe6",
tagText: "#5a5a5a",
editorBg: "#ffffff",
toolbarBg: "#faf9f7",
shadow: "rgba(0, 0, 0, 0.06)",
checkboxChecked: "#5b6abf",
searchHighlight: "#fff3cd",
pinColor: "#e8a42d",
},
dark: {
bg: "#1a1a1e",
surface: "#242428",
surfaceHover: "#2c2c30",
surfaceActive: "#353539",
sidebarBg: "#1e1e22",
border: "#36363a",
text: "#d4d4d8",
textSecondary: "#8b8b90",
textTertiary: "#5c5c60",
accent: "#7c8bd4",
accentHover: "#6a7ac2",
accentLight: "rgba(124, 139, 212, 0.1)",
danger: "#d45555",
dangerHover: "#c24444",
tagBg: "#333338",
tagText: "#a0a0a5",
editorBg: "#242428",
toolbarBg: "#1e1e22",
shadow: "rgba(0, 0, 0, 0.2)",
checkboxChecked: "#7c8bd4",
searchHighlight: "#5a4f20",
pinColor: "#d4a030",
},
};
exports.FONTS = {
ui: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
editor: "'Georgia', 'Liberation Serif', 'Times New Roman', serif",
mono: "'JetBrains Mono', 'Fira Code', monospace",
};
exports.AUTOSAVE_DEBOUNCE = 500;
exports.SNIPPET_LENGTH = 80;
exports.STORAGE_KEY = "notes-app-data";
exports.LAYOUT_KEY = "notes-app-layout";
exports.THEME_KEY = "notes-app-theme";
// Allowed HTML tags for sanitization
exports.ALLOWED_TAGS = [
"p", "br", "strong", "b", "em", "i", "u", "s", "del", "strike",
"code", "pre", "blockquote", "hr",
"h1", "h2", "h3", "h4", "h5", "h6",
"ul", "ol", "li",
"div", "span",
];
exports.ALLOWED_ATTRS = [
"style", "class", "data-checked", "checked",
];
// Default notebooks on first load
exports.DEFAULT_NOTEBOOKS = [
{ id: genId(), name: "Personal" },
{ id: genId(), name: "Work" },
{ id: genId(), name: "Ideas" },
];
function genId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
// Export for use in other modules
exports.generateId = genId;
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ─── Data Model Types ──────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
};
// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// ─── State Management & Persistence ────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.getState = getState;
exports.getLayout = getLayout;
exports.getTheme = getTheme;
exports.setSortMode = setSortMode;
exports.setSearchQuery = setSearchQuery;
exports.setTagFilter = setTagFilter;
exports.addNotebook = addNotebook;
exports.renameNotebook = renameNotebook;
exports.deleteNotebook = deleteNotebook;
exports.toggleNotebookCollapsed = toggleNotebookCollapsed;
exports.createNote = createNote;
exports.updateNote = updateNote;
exports.deleteNote = deleteNote;
exports.restoreNote = restoreNote;
exports.permanentlyDeleteNote = permanentlyDeleteNote;
exports.duplicateNote = duplicateNote;
exports.togglePin = togglePin;
exports.moveNote = moveNote;
exports.addTag = addTag;
exports.removeTag = removeTag;
exports.setActiveNotebook = setActiveNotebook;
exports.setActiveNote = setActiveNote;
exports.setSidebarWidth = setSidebarWidth;
exports.setNoteListWidth = setNoteListWidth;
exports.toggleSidebar = toggleSidebar;
exports.toggleTheme = toggleTheme;
exports.getFilteredNotes = getFilteredNotes;
exports.getNoteCountForNotebook = getNoteCountForNotebook;
exports.extractTextFromHtml = extractTextFromHtml;
exports.getSnippet = getSnippet;
exports.getActiveNote = getActiveNote;
const constants_1 = require("./constants");
const defaultNotebooks = JSON.parse(JSON.stringify(constants_1.DEFAULT_NOTEBOOKS));
function createDefaultState() {
return {
notebooks: defaultNotebooks,
notes: [],
activeNotebookId: defaultNotebooks[0]?.id ?? null,
activeNoteId: null,
sortMode: "modified",
searchQuery: "",
tagFilter: null,
};
}
function createDefaultLayout() {
return {
sidebarWidth: 240,
noteListWidth: 300,
sidebarCollapsed: false,
};
}
// ─── State ──────────────────────────────────────────────────────
let state = loadState();
let layout = loadLayout();
let theme = loadTheme();
function loadState() {
try {
const raw = localStorage.getItem(constants_1.STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
// Validate structure
if (parsed && Array.isArray(parsed.notebooks) && Array.isArray(parsed.notes)) {
return { ...createDefaultState(), ...parsed };
}
}
}
catch { /* ignore */ }
return createDefaultState();
}
function loadLayout() {
try {
const raw = localStorage.getItem(constants_1.LAYOUT_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed.sidebarWidth === "number") {
return { ...createDefaultLayout(), ...parsed };
}
}
}
catch { /* ignore */ }
return createDefaultLayout();
}
function loadTheme() {
try {
const raw = localStorage.getItem(constants_1.THEME_KEY);
if (raw === "dark")
return "dark";
if (raw === "light")
return "light";
}
catch { /* ignore */ }
// Check system preference
if (window.matchMedia?.("(prefers-color-scheme: dark)").matches)
return "dark";
return "light";
}
function saveState() {
try {
localStorage.setItem(constants_1.STORAGE_KEY, JSON.stringify(state));
}
catch { /* storage full */ }
}
function saveLayout() {
try {
localStorage.setItem(constants_1.LAYOUT_KEY, JSON.stringify(layout));
}
catch { /* ignore */ }
}
function saveTheme() {
try {
localStorage.setItem(constants_1.THEME_KEY, theme);
}
catch { /* ignore */ }
}
// ─── Actions ──────────────────────────────────────────────────────
function getState() { return state; }
function getLayout() { return layout; }
function getTheme() { return theme; }
function setSortMode(mode) {
state.sortMode = mode;
}
function setSearchQuery(q) {
state.searchQuery = q;
}
function setTagFilter(tag) {
state.tagFilter = tag;
}
// Notebooks
function addNotebook(name) {
const nb = { id: (0, constants_1.generateId)(), name };
state.notebooks.push(nb);
saveState();
return nb;
}
function renameNotebook(id, name) {
const nb = state.notebooks.find(n => n.id === id);
if (nb) {
nb.name = name;
saveState();
}
}
function deleteNotebook(id) {
// Move notes to no notebook (or trash)
state.notes.forEach(n => {
if (n.notebookId === id)
n.notebookId = null;
});
state.notebooks = state.notebooks.filter(n => n.id !== id);
if (state.activeNotebookId === id)
state.activeNotebookId = state.notebooks[0]?.id ?? null;
saveState();
}
function toggleNotebookCollapsed(id) {
const nb = state.notebooks.find(n => n.id === id);
if (nb)
nb.collapsed = !nb.collapsed;
saveState();
}
// Notes
function createNote(notebookId, title) {
const note = {
id: (0, constants_1.generateId)(),
notebookId,
title: title || "",
content: "",
tags: [],
pinned: false,
createdAt: Date.now(),
updatedAt: Date.now(),
};
state.notes.push(note);
saveState();
return note;
}
function updateNote(id, updates) {
const note = state.notes.find(n => n.id === id);
if (note) {
Object.assign(note, updates, { updatedAt: Date.now() });
saveState();
}
}
function deleteNote(id) {
const note = state.notes.find(n => n.id === id);
if (note && note.notebookId !== null) {
note.notebookId = null; // Move to trash
note.updatedAt = Date.now();
saveState();
}
}
function restoreNote(id) {
const note = state.notes.find(n => n.id === id);
if (note) {
// Restore to first available notebook
note.notebookId = state.notebooks[0]?.id ?? null;
note.updatedAt = Date.now();
saveState();
}
}
function permanentlyDeleteNote(id) {
state.notes = state.notes.filter(n => n.id !== id);
if (state.activeNoteId === id)
state.activeNoteId = null;
saveState();
}
function duplicateNote(id) {
const note = state.notes.find(n => n.id === id);
if (!note)
return;
const dup = {
...JSON.parse(JSON.stringify(note)),
id: (0, constants_1.generateId)(),
title: note.title + " (copy)",
createdAt: Date.now(),
updatedAt: Date.now(),
};
state.notes.push(dup);
saveState();
return dup;
}
function togglePin(id) {
const note = state.notes.find(n => n.id === id);
if (note) {
note.pinned = !note.pinned;
note.updatedAt = Date.now();
saveState();
}
}
function moveNote(noteId, targetNotebookId) {
const note = state.notes.find(n => n.id === noteId);
if (note) {
note.notebookId = targetNotebookId;
note.updatedAt = Date.now();
saveState();
}
}
function addTag(noteId, tag) {
const note = state.notes.find(n => n.id === noteId);
if (note && !note.tags.includes(tag)) {
note.tags.push(tag);
note.updatedAt = Date.now();
saveState();
}
}
function removeTag(noteId, tag) {
const note = state.notes.find(n => n.id === noteId);
if (note) {
note.tags = note.tags.filter(t => t !== tag);
note.updatedAt = Date.now();
saveState();
}
}
// Selection
function setActiveNotebook(id) {
state.activeNotebookId = id;
if (id === "trash") {
// Don't auto-select a note in trash
}
else {
state.activeNoteId = null;
}
}
function setActiveNote(id) {
state.activeNoteId = id;
}
// Layout
function setSidebarWidth(w) { layout.sidebarWidth = w; saveLayout(); }
function setNoteListWidth(w) { layout.noteListWidth = w; saveLayout(); }
function toggleSidebar() {
layout.sidebarCollapsed = !layout.sidebarCollapsed;
saveLayout();
}
// Theme
function toggleTheme() {
theme = theme === "light" ? "dark" : "light";
saveTheme();
return theme;
}
// Get filtered/sorted notes for display
function getFilteredNotes() {
let notes = state.notes;
const activeNb = state.activeNotebookId;
// Filter by notebook
if (activeNb === "trash") {
notes = notes.filter(n => n.notebookId === null);
}
else if (activeNb !== null) {
notes = notes.filter(n => n.notebookId === activeNb);
}
// Filter by tag
const tagF = state.tagFilter;
if (tagF) {
notes = notes.filter(n => n.tags.includes(tagF));
}
// Search filter
if (state.searchQuery.trim()) {
const q = state.searchQuery.toLowerCase();
notes = notes.filter(n => {
if (n.title.toLowerCase().includes(q))
return true;
// Extract text from HTML content for search
const textContent = extractTextFromHtml(n.content);
return textContent.toLowerCase().includes(q);
});
}
// Sort: pinned first, then by sort mode
notes.sort((a, b) => {
if (a.pinned !== b.pinned)
return a.pinned ? -1 : 1;
switch (state.sortMode) {
case "modified": return b.updatedAt - a.updatedAt;
case "created": return b.createdAt - a.createdAt;
case "alphabetical": return a.title.localeCompare(b.title);
default: return 0;
}
});
return notes;
}
function getNoteCountForNotebook(notebookId) {
if (notebookId === "trash")
return state.notes.filter(n => n.notebookId === null).length;
const nbId = notebookId;
return state.notes.filter(n => n.notebookId === nbId).length;
}
// Pure text extraction from HTML — used for search and snippets
function extractTextFromHtml(html) {
if (!html)
return "";
const div = document.createElement("div");
div.innerHTML = html;
return (div.textContent || div.innerText || "").trim();
}
// Get snippet preview text
function getSnippet(note, len = 80) {
const text = extractTextFromHtml(note.content);
if (!text)
return "Empty note";
return text.length > len ? text.slice(0, len) + "…" : text;
}
// Get active note
function getActiveNote() {
if (!state.activeNoteId)
return undefined;
return state.notes.find(n => n.id === state.activeNoteId);
}
};
// ── module: src/editor.ts ──
__mods["src/editor.ts"] = function (exports, require, module) {
"use strict";
// ─── Rich Text Editor Module ──────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.initEditor = initEditor;
exports.setOnSave = setOnSave;
exports.setOnTitleChange = setOnTitleChange;
exports.loadNote = loadNote;
exports.sanitizeHtml = sanitizeHtml;
exports.updateToolbarState = updateToolbarState;
exports.createToolbar = createToolbar;
exports.setupTitleInput = setupTitleInput;
exports.getEditorContent = getEditorContent;
const constants_1 = require("./constants");
let editorEl = null;
let onSaveCallback = null;
let onTitleChangeCallback = null;
// ─── Initialization ──────────────────────────────────────────
function initEditor(editorElement, titleInput) {
editorEl = editorElement;
// Setup paste sanitization
editorEl.addEventListener("paste", handlePaste);
// Setup keyboard shortcuts
editorEl.addEventListener("keydown", handleKeyDown);
// Setup input event for toolbar state updates
editorEl.addEventListener("input", onEditorInput);
editorEl.addEventListener("mouseup", updateToolbarState);
editorEl.addEventListener("keyup", updateToolbarState);
// Focus management
editorEl.addEventListener("focus", () => {
document.execCommand("styleWithCSS", false, "true");
});
}
function setOnSave(cb) { onSaveCallback = cb; }
function setOnTitleChange(cb) { onTitleChangeCallback = cb; }
// ─── Content Loading ──────────────────────────────────────────
function loadNote(note) {
if (!editorEl || !note)
return;
// Save current content before switching
saveCurrentContent();
editorEl.innerHTML = note.content ?? "";
// Restore checklist checkbox states from data attributes
restoreCheckboxes(editorEl);
}
function saveCurrentContent() {
if (!editorEl || !onSaveCallback)
return;
onSaveCallback(editorEl.innerHTML);
}
// ─── Paste Sanitization ──────────────────────────────────────
function handlePaste(e) {
e.preventDefault();
const html = e.clipboardData?.getData("text/html");
const text = e.clipboardData?.getData("text/plain");
if (html) {
const sanitized = sanitizeHtml(html);
document.execCommand("insertHTML", false, sanitized);
}
else if (text) {
// Insert plain text with current formatting context
document.execCommand("insertText", false, text);
}
}
function sanitizeHtml(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
for (const node of Array.from(doc.body.querySelectorAll("*"))) {
const tag = node.tagName.toLowerCase();
// Remove script tags and their content entirely
if (tag === "script" || tag === "style" || tag === "link" || tag === "meta") {
node.remove();
continue;
}
// Remove disallowed tags but keep their text content
if (!constants_1.ALLOWED_TAGS.includes(tag)) {
const fragment = document.createDocumentFragment();
while (node.firstChild) {
fragment.appendChild(node.firstChild);
}
node.parentNode?.replaceChild(fragment, node);
continue;
}
// Remove disallowed attributes
for (const attr of Array.from(node.attributes)) {
if (!constants_1.ALLOWED_ATTRS.includes(attr.name.toLowerCase())) {
node.removeAttribute(attr.name);
}
}
}
return doc.body.innerHTML;
}
// ─── Keyboard Handling ──────────────────────────────────────
function handleKeyDown(e) {
if (!editorEl)
return;
// Ctrl/Cmd + B/I/U for formatting
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case "b":
e.preventDefault();
document.execCommand("bold");
updateToolbarState();
break;
case "i":
e.preventDefault();
document.execCommand("italic");
updateToolbarState();
break;
case "u":
e.preventDefault();
document.execCommand("underline");
updateToolbarState();
break;
}
}
// Enter key handling for lists and checklists
if (e.key === "Enter" && !e.shiftKey) {
handleListEnter(e);
}
// Markdown shortcuts on space
if (e.key === " ") {
handleMarkdownShortcut();
}
}
function handleListEnter(e) {
const sel = window.getSelection();
if (!sel || !editorEl)
return;
const anchorNode = sel.anchorNode;
let element = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement;
// Find the list item we're in
while (element && element !== editorEl) {
if (element.tagName === "LI")
break;
element = element.parentElement;
}
if (!element || element.tagName !== "LI")
return;
const li = element;
const textContent = li.textContent?.trim();
// If list item is empty, exit the list
if (!textContent) {
e.preventDefault();
// For checklist items, remove the whole LI
if (li.classList.contains("checklist-item")) {
const parent = li.parentElement;
if (parent && parent.childElementCount <= 1) {
// Last item in list - exit list
const br = document.createElement("div");
br.innerHTML = "<br>";
parent.parentNode?.insertBefore(br, parent.nextSibling);
parent.remove();
// Place cursor in the new div
const range = document.createRange();
range.setStart(br, 0);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
else {
li.remove();
}
}
else {
// Regular list item - remove it and add a paragraph after
const parent = li.parentElement;
if (parent && parent.childElementCount <= 1) {
const p = document.createElement("p");
p.innerHTML = "<br>";
parent.parentNode?.insertBefore(p, parent.nextSibling);
parent.remove();
const range = document.createRange();
range.setStart(p, 0);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
else {
li.remove();
}
}
updateToolbarState();
return;
}
// Non-empty list item: continue the list (browser default behavior handles this)
}
// ─── Markdown Shortcuts ──────────────────────────────────────
function handleMarkdownShortcut() {
if (!editorEl)
return;
const sel = window.getSelection();
if (!sel)
return;
// Get the current line text before cursor
const lineText = getCurrentLinePrefix(sel);
if (!lineText)
return;
let replacement = null;
let command = null;
if (lineText === "# ") {
replacement = "";
command = "h1";
}
else if (lineText === "## ") {
replacement = "";
command = "h2";
}
else if (lineText === "### ") {
replacement = "";
command = "h3";
}
else if (lineText === "- ") {
replacement = "";
command = "insertUnorderedList";
}
else if (lineText === "1. ") {
replacement = "";
command = "insertOrderedList";
}
else if (lineText === "> ") {
replacement = "";
command = "blockquote";
}
else if (lineText === "[] ") {
replacement = "";
command = "checklist";
}
if (!command)
return;
// Only trigger at the start of a line/block
const sel2 = window.getSelection();
if (!sel2)
return;
const anchorNode = sel2.anchorNode;
let element = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement;
while (element && element !== editorEl) {
if (["P", "DIV", "H1", "H2", "H3", "LI"].includes(element.tagName))
break;
element = element.parentElement;
}
if (!element || element === editorEl)
return;
// Check that the cursor is right after the prefix (at position matching prefix length + 1 for space)
const range = sel2.getRangeAt(0);
const textBeforeCursor = getTextBeforeRange(range, element);
if (textBeforeCursor !== lineText)
return;
// Remove the prefix text
const newRange = document.createRange();
newRange.setStart(element, 0);
newRange.setEnd(range.startContainer, range.startOffset);
newRange.deleteContents();
// No event to prevent (space key already consumed)
// Apply formatting
switch (command) {
case "h1":
document.execCommand("formatBlock", false, "H1");
break;
case "h2":
document.execCommand("formatBlock", false, "H2");
break;
case "h3":
document.execCommand("formatBlock", false, "H3");
break;
case "blockquote":
document.execCommand("formatBlock", false, "BLOCKQUOTE");
break;
case "insertUnorderedList":
document.execCommand("insertUnorderedList");
break;
case "insertOrderedList":
document.execCommand("insertOrderedList");
break;
case "checklist":
insertChecklistItem();
break;
}
updateToolbarState();
}
function getCurrentLinePrefix(sel) {
const anchorNode = sel.anchorNode;
let element = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement;
while (element && element !== editorEl) {
if (["P", "DIV"].includes(element.tagName))
break;
element = element.parentElement;
}
if (!element || element === editorEl)
return null;
// Get text content up to cursor position
const range = sel.getRangeAt(0);
return getTextBeforeRange(range, element);
}
function getTextBeforeRange(range, container) {
const boundaryRange = document.createRange();
boundaryRange.selectNodeContents(container);
boundaryRange.setEnd(range.startContainer, range.startOffset);
return boundaryRange.toString();
}
function insertChecklistItem() {
if (!editorEl)
return;
const sel = window.getSelection();
if (!sel)
return;
// Create a checklist LI
const li = document.createElement("li");
li.className = "checklist-item";
li.contentEditable = "true";
const checkbox = document.createElement("span");
checkbox.className = "checklist-checkbox";
checkbox.setAttribute("data-checked", "false");
checkbox.addEventListener("click", handleCheckboxClick);
const textSpan = document.createElement("span");
textSpan.contentEditable = "true";
textSpan.innerHTML = "<br>";
li.appendChild(checkbox);
li.appendChild(textSpan);
// Wrap in UL if not already in one
let parentUl = null;
const anchorNode2 = sel.anchorNode;
let element2 = anchorNode2 instanceof Element ? anchorNode2 : anchorNode2?.parentElement ?? null;
while (element2 && element2 !== editorEl) {
if (element2.tagName === "UL") {
parentUl = element2;
break;
}
element2 = element2.parentElement;
}
const targetParent = parentUl || createNewList();
targetParent.appendChild(li);
// Place cursor in the text span
const range = document.createRange();
range.setStart(textSpan, 0);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
function createNewList() {
if (!editorEl)
return document.createElement("ul");
const ul = document.createElement("ul");
editorEl.appendChild(ul);
return ul;
}
// ─── Checkbox Handling ──────────────────────────────────────
function handleCheckboxClick(e) {
e.stopPropagation();
const checkbox = e.target;
const isChecked = checkbox.getAttribute("data-checked") === "true";
if (isChecked) {
checkbox.setAttribute("data-checked", "false");
checkbox.classList.remove("checked");
}
else {
checkbox.setAttribute("data-checked", "true");
checkbox.classList.add("checked");
}
}
function restoreCheckboxes(container) {
const checkboxes = container.querySelectorAll(".checklist-checkbox");
checkboxes.forEach(cb => {
cb.addEventListener("click", handleCheckboxClick);
if (cb.getAttribute("data-checked") === "true" || cb.hasAttribute("checked")) {
cb.setAttribute("data-checked", "true");
cb.classList.add("checked");
}
else {
cb.setAttribute("data-checked", "false");
cb.classList.remove("checked");
}
});
}
// ─── Toolbar State ──────────────────────────────────────────
function onEditorInput() {
updateToolbarState();
}
function updateToolbarState() {
if (!editorEl)
return;
const buttons = editorEl.closest(".notes-app")?.querySelectorAll(".toolbar-btn[data-cmd]");
if (!buttons)
return;
for (const btn of Array.from(buttons)) {
const cmd = btn.getAttribute("data-cmd");
if (!cmd)
continue;
let isActive = false;
try {
switch (cmd) {
case "bold":
case "italic":
case "underline":
case "strikeThrough":
isActive = document.queryCommandState(cmd);
break;
case "insertUnorderedList":
case "insertOrderedList":
isActive = document.queryCommandState(cmd);
break;
default:
// For heading buttons, check if current block is that heading
const sel = window.getSelection();
if (sel && sel.anchorNode) {
let el = sel.anchorNode instanceof Element ? sel.anchorNode : sel.anchorNode.parentElement;
while (el && el !== editorEl) {
if (el.tagName === cmd.toUpperCase()) {
isActive = true;
break;
}
el = el.parentElement;
}
}
}
}
catch { /* ignore */ }
btn.classList.toggle("active", isActive);
}
}
// ─── Toolbar Creation ──────────────────────────────────────
function createToolbar(toolbarEl) {
const buttons = [
{ cmd: "bold", label: "B", style: "font-weight:bold" },
{ cmd: "italic", label: "I", style: "font-style:italic" },
{ cmd: "underline", label: "U", style: "text-decoration:underline" },
{ cmd: "strikeThrough", label: "S", style: "text-decoration:line-through" },
{ type: "separator" },
{ cmd: "code", label: "<>", style: "font-family:monospace;font-size:12px" },
{ type: "separator" },
{ cmd: "H1", label: "H1", style: "font-weight:bold;font-size:14px" },
{ cmd: "H2", label: "H2", style: "font-weight:600;font-size:13px" },
{ cmd: "H3", label: "H3", style: "font-weight:500;font-size:12px" },
{ type: "separator" },
{ cmd: "insertUnorderedList", label: "• List" },
{ cmd: "insertOrderedList", label: "1. List" },
{ cmd: "checklist", label: "☐ Check" },
{ type: "separator" },
{ cmd: "formatBlock", label: "Quote", blockTag: "BLOCKQUOTE" },
{ cmd: "insertHorizontalRule", label: "— HR" },
];
toolbarEl.innerHTML = "";
for (const btn of buttons) {
if (btn.type === "separator") {
const sep = document.createElement("div");
sep.className = "toolbar-separator";
toolbarEl.appendChild(sep);
continue;
}
const button = document.createElement("button");
button.className = "toolbar-btn";
button.textContent = btn.label ?? "";
if (btn.style)
button.setAttribute("style", btn.style);
if (btn.cmd)
button.setAttribute("data-cmd", btn.cmd);
button.addEventListener("click", () => {
editorEl?.focus();
const cmd = btn.cmd ?? "";
switch (cmd) {
case "bold":
case "italic":
case "underline":
case "strikeThrough":
case "insertUnorderedList":
case "insertOrderedList":
case "insertHorizontalRule":
document.execCommand(cmd);
break;
case "code":
wrapSelection("CODE");
break;
case "H1":
case "H2":
case "H3":
document.execCommand("formatBlock", false, cmd);
break;
case "checklist":
insertChecklistItem();
break;
default:
if (btn.blockTag) {
document.execCommand("formatBlock", false, btn.blockTag);
}
}
updateToolbarState();
});
toolbarEl.appendChild(button);
}
}
function wrapSelection(tag) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const range = sel.getRangeAt(0);
if (range.collapsed)
return;
const el = document.createElement(tag);
try {
range.surroundContents(el);
}
catch { /* partial selection across blocks */ }
}
// ─── Title Input ──────────────────────────────────────────────
function setupTitleInput(titleInput) {
titleInput.addEventListener("input", () => {
onTitleChangeCallback?.(titleInput.value);
});
titleInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
editorEl?.focus();
}
});
}
// ─── Public API ──────────────────────────────────────────────
function getEditorContent() {
if (!editorEl)
return "";
return editorEl.innerHTML;
}
};
// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
// ─── UI Rendering Module ──────────────────────────────────────
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.initUI = initUI;
exports.renderAll = renderAll;
const constants_1 = require("./constants");
const S = __importStar(require("./state"));
const Editor = __importStar(require("./editor"));
// ─── DOM References ──────────────────────────────────────────
let sidebarEl;
let notebookListEl;
let noteListTitleEl;
let noteListEl;
let editorContainerEl;
let emptyStateEl;
let titleInputEl;
let toolbarEl;
let savedIndicatorEl;
let moveDropdownEl;
let tagDisplayEl;
// ─── State for UI ────────────────────────────────────────────
let autosaveTimer = null;
let savedIndicatorTimeout = null;
const SORT_MODES = ["modified", "created", "alphabetical"];
const SORT_LABELS = {
modified: "Modified",
created: "Created",
alphabetical: "A-Z",
};
// ─── Initialization ──────────────────────────────────────────
function initUI() {
sidebarEl = document.getElementById("sidebar");
notebookListEl = document.getElementById("notebook-list");
noteListTitleEl = document.getElementById("note-list-title");
noteListEl = document.getElementById("note-list");
editorContainerEl = document.getElementById("editor-container");
emptyStateEl = document.getElementById("empty-state");
titleInputEl = document.getElementById("note-title-input");
toolbarEl = document.getElementById("toolbar");
savedIndicatorEl = document.getElementById("saved-indicator");
moveDropdownEl = document.getElementById("move-dropdown");
tagDisplayEl = document.getElementById("tag-display");
// Apply theme
applyTheme(S.getTheme());
// Setup editor
const editorEl = document.getElementById("editor");
Editor.initEditor(editorEl, titleInputEl);
Editor.createToolbar(toolbarEl);
Editor.setOnSave(debounceAutosave);
Editor.setOnTitleChange(onTitleChanged);
Editor.setupTitleInput(titleInputEl);
// Setup sidebar events
setupSidebar();
// Create new note button in note list header
const createNoteBtn = document.createElement("button");
createNoteBtn.id = "create-note-btn";
createNoteBtn.className = "icon-btn";
createNoteBtn.textContent = "+";
createNoteBtn.title = "New Note";
createNoteBtn.style.fontSize = "16px";
createNoteBtn.style.fontWeight = "bold";
const noteListActions = document.querySelector(".note-list-actions");
if (noteListActions) {
noteListActions.insertBefore(createNoteBtn, noteListActions.firstChild);
}
createNoteBtn.addEventListener("click", () => {
const state = S.getState();
const nbId = state.activeNotebookId === "trash" ? null : state.activeNotebookId;
if (nbId !== null) {
const note = S.createNote(nbId);
S.setActiveNote(note.id);
renderAll();
}
});
// Setup note list events
setupNoteListActions();
// Setup editor actions
setupEditorActions();
// Setup resize handles
setupResizeHandles();
// Setup search
setupSearch();
// Initial render
renderAll();
}
// ─── Theme ──────────────────────────────────────────────────
function applyTheme(theme) {
const app = document.getElementById("app");
if (theme === "dark") {
app?.classList.add("dark");
}
else {
app?.classList.remove("dark");
}
// Update theme toggle button
const btn = document.getElementById("theme-toggle");
if (btn)
btn.textContent = theme === "dark" ? "☀️" : "🌙";
}
// ─── Sidebar Rendering ──────────────────────────────────────
function setupSidebar() {
// Theme toggle
const themeBtn = document.getElementById("theme-toggle");
themeBtn?.addEventListener("click", () => {
const newTheme = S.toggleTheme();
applyTheme(newTheme);
});
// New notebook button
const newNbBtn = document.getElementById("new-notebook-btn");
newNbBtn?.addEventListener("click", () => {
const name = prompt("Notebook name:");
if (name?.trim()) {
S.addNotebook(name.trim());
renderSidebar();
}
});
// Collapse sidebar toggle (when collapsed)
const collapseBtn = document.getElementById("collapse-sidebar-btn");
collapseBtn?.addEventListener("click", () => {
S.toggleSidebar();
applyLayout();
renderSidebar();
});
}
function renderSidebar() {
const state = S.getState();
const layout = S.getLayout();
// Apply sidebar collapsed state
if (layout.sidebarCollapsed) {
sidebarEl.classList.add("collapsed");
}
else {
sidebarEl.classList.remove("collapsed");
}
notebookListEl.innerHTML = "";
// "All Notes" virtual view
const allNotesItem = createNotebookItem("📋", "All Notes", null, state.activeNotebookId === null, state.notes.length);
notebookListEl.appendChild(allNotesItem);
// Notebooks
for (const nb of state.notebooks) {
const item = createNotebookItem("📓", nb.name, nb.id, state.activeNotebookId === nb.id, S.getNoteCountForNotebook(nb.id), nb.collapsed ?? false);
notebookListEl.appendChild(item);
}
// Trash
const trashItem = createNotebookItem("🗑", "Trash", "trash", state.activeNotebookId === "trash", S.getNoteCountForNotebook("trash"));
notebookListEl.appendChild(trashItem);
}
function createNotebookItem(icon, name, id, active, count, collapsed) {
const item = document.createElement("div");
item.className = "notebook-item" + (active ? " active" : "");
const iconSpan = document.createElement("span");
iconSpan.className = "notebook-icon";
iconSpan.textContent = icon;
item.appendChild(iconSpan);
const nameSpan = document.createElement("span");
nameSpan.className = "notebook-name";
nameSpan.textContent = name;
item.appendChild(nameSpan);
const countSpan = document.createElement("span");
countSpan.className = "notebook-count";
countSpan.textContent = String(count);
item.appendChild(countSpan);
// Actions for real notebooks (not All Notes or Trash)
if (id && id !== "trash") {
const actionsDiv = document.createElement("div");
actionsDiv.className = "notebook-actions";
const renameBtn = document.createElement("button");
renameBtn.className = "notebook-action-btn";
renameBtn.textContent = "✏️";
renameBtn.title = "Rename";
renameBtn.addEventListener("click", (e) => {
e.stopPropagation();
handleRenameNotebook(id, nameSpan);
});
actionsDiv.appendChild(renameBtn);
const deleteBtn = document.createElement("button");
deleteBtn.className = "notebook-action-btn";
deleteBtn.textContent = "🗑";
deleteBtn.title = "Delete";
deleteBtn.addEventListener("click", (e) => {
e.stopPropagation();
if (confirm(`Delete notebook "${name}"? Notes will be moved to Trash.`)) {
S.deleteNotebook(id);
renderAll();
}
});
actionsDiv.appendChild(deleteBtn);
item.appendChild(actionsDiv);
}
// Click handler
item.addEventListener("click", () => {
S.setActiveNotebook(id === "trash" ? "trash" : id);
S.setTagFilter(null);
renderAll();
});
return item;
}
function handleRenameNotebook(id, nameEl) {
const currentName = nameEl.textContent ?? "";
const input = document.createElement("input");
input.type = "text";
input.className = "notebook-rename-input";
input.value = currentName;
nameEl.replaceWith(input);
input.focus();
input.select();
const finish = () => {
const newName = input.value.trim() || currentName;
S.renameNotebook(id, newName);
renderSidebar();
};
input.addEventListener("blur", finish);
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
input.blur();
}
if (e.key === "Escape") {
input.value = currentName;
input.blur();
}
});
}
// ─── Note List Rendering ──────────────────────────────────────
function setupNoteListActions() {
// Sort toggle
const sortBtn = document.getElementById("sort-toggle");
sortBtn?.addEventListener("click", () => {
const state = S.getState();
const idx = SORT_MODES.indexOf(state.sortMode);
const next = SORT_MODES[(idx + 1) % SORT_MODES.length];
S.setSortMode(next);
renderNoteList();
sortBtn.textContent = SORT_LABELS[next][0] ?? "↕";
});
}
function renderNoteList() {
const state = S.getState();
// Update title
if (state.activeNotebookId === null) {
noteListTitleEl.textContent = "All Notes";
}
else if (state.activeNotebookId === "trash") {
noteListTitleEl.textContent = "Trash";
}
else {
const nb = state.notebooks.find(n => n.id === state.activeNotebookId);
noteListTitleEl.textContent = nb?.name ?? "Notes";
}
// Tag filter indicator
if (state.tagFilter) {
noteListTitleEl.textContent += ` — #${state.tagFilter}`;
}
const notes = S.getFilteredNotes();
noteListEl.innerHTML = "";
if (notes.length === 0) {
// Show empty state
const emptyDiv = document.createElement("div");
emptyDiv.className = "note-list-empty";
const icon = document.createElement("span");
icon.className = "empty-icon";
icon.textContent = S.getState().activeNotebookId === "trash" ? "🗑" : "📝";
emptyDiv.appendChild(icon);
const msg = document.createElement("p");
if (S.getState().searchQuery.trim()) {
msg.textContent = "No notes match your search";
}
else if (S.getState().activeNotebookId === "trash") {
msg.textContent = "Trash is empty";
}
else {
msg.textContent = "No notes yet\nClick + to create one";
msg.style.whiteSpace = "pre-line";
}
emptyDiv.appendChild(msg);
noteListEl.appendChild(emptyDiv);
}
else {
for (const note of notes) {
const item = createNoteItem(note);
noteListEl.appendChild(item);
}
}
// Update sort button label
const sortBtn = document.getElementById("sort-toggle");
if (sortBtn)
sortBtn.textContent = SORT_LABELS[state.sortMode][0] ?? "↕";
}
function createNoteItem(note) {
const item = document.createElement("div");
item.className = "note-item" + (S.getState().activeNoteId === note.id ? " active" : "");
// Header with pin indicator and title
const header = document.createElement("div");
header.className = "note-item-header";
if (note.pinned) {
const pin = document.createElement("span");
pin.className = "note-pin-indicator";
pin.textContent = "📌";
header.appendChild(pin);
}
const title = document.createElement("span");
title.className = "note-title";
title.textContent = note.title || "Untitled";
header.appendChild(title);
item.appendChild(header);
// Snippet
const snippet = document.createElement("div");
snippet.className = "note-snippet";
snippet.textContent = S.getSnippet(note, constants_1.SNIPPET_LENGTH);
item.appendChild(snippet);
// Meta: date + tags
const meta = document.createElement("div");
meta.className = "note-meta";
const date = document.createElement("span");
date.className = "note-date";
date.textContent = formatRelativeTime(note.updatedAt);
meta.appendChild(date);
if (note.tags.length > 0) {
const tagsDiv = document.createElement("div");
tagsDiv.className = "note-tags";
for (const tag of note.tags) {
const tagEl = document.createElement("span");
tagEl.className = "note-tag";
tagEl.textContent = "#" + tag;
tagEl.addEventListener("click", (e) => {
e.stopPropagation();
S.setTagFilter(tag);
renderAll();
});
tagsDiv.appendChild(tagEl);
}
meta.appendChild(tagsDiv);
}
item.appendChild(meta);
// Trash actions for trash view
if (S.getState().activeNotebookId === "trash") {
const trashActions = document.createElement("div");
trashActions.className = "trash-actions";
const restoreBtn = document.createElement("button");
restoreBtn.className = "trash-btn restore-btn";
restoreBtn.textContent = "Restore";
restoreBtn.addEventListener("click", (e) => {
e.stopPropagation();
S.restoreNote(note.id);
renderAll();
});
trashActions.appendChild(restoreBtn);
const permDeleteBtn = document.createElement("button");
permDeleteBtn.className = "trash-btn permanent-delete-btn";
permDeleteBtn.textContent = "Delete Forever";
permDeleteBtn.addEventListener("click", (e) => {
e.stopPropagation();
S.permanentlyDeleteNote(note.id);
renderAll();
});
trashActions.appendChild(permDeleteBtn);
item.appendChild(trashActions);
}
// Click to select note
item.addEventListener("click", () => {
S.setActiveNote(note.id);
renderAll();
});
return item;
}
// ─── Editor Rendering ──────────────────────────────────────
function setupEditorActions() {
// Pin button
const pinBtn = document.getElementById("pin-note-btn");
pinBtn?.addEventListener("click", () => {
if (S.getState().activeNoteId) {
S.togglePin(S.getState().activeNoteId);
renderAll();
}
});
// Duplicate button
const dupBtn = document.getElementById("duplicate-note-btn");
dupBtn?.addEventListener("click", () => {
if (S.getState().activeNoteId) {
S.duplicateNote(S.getState().activeNoteId);
renderAll();
}
});
// Delete button
const delBtn = document.getElementById("delete-note-btn");
delBtn?.addEventListener("click", () => {
if (S.getState().activeNoteId) {
S.deleteNote(S.getState().activeNoteId);
renderAll();
}
});
// Move button
const moveBtn = document.getElementById("move-note-btn");
moveBtn?.addEventListener("click", () => {
showMoveDropdown(moveBtn);
});
// Add tag button
const addTagBtn = document.getElementById("add-tag-btn");
addTagBtn?.addEventListener("click", () => {
if (S.getState().activeNoteId) {
const tag = prompt("Enter tag name:");
if (tag?.trim()) {
S.addTag(S.getState().activeNoteId, tag.trim());
renderEditor();
}
}
});
// Close dropdown on outside click
document.addEventListener("click", (e) => {
if (!moveDropdownEl.contains(e.target)) {
moveDropdownEl.classList.add("hidden");
}
});
}
function showMoveDropdown(anchor) {
const noteId = S.getState().activeNoteId;
if (!noteId)
return;
const rect = anchor.getBoundingClientRect();
moveDropdownEl.style.top = (rect.bottom + 4) + "px";
moveDropdownEl.style.left = rect.left + "px";
moveDropdownEl.classList.remove("hidden");
const listEl = document.getElementById("move-notebook-list");
listEl.innerHTML = "";
for (const nb of S.getState().notebooks) {
const item = document.createElement("div");
item.className = "dropdown-item";
item.textContent = nb.name;
item.addEventListener("click", () => {
S.moveNote(noteId, nb.id);
moveDropdownEl.classList.add("hidden");
renderAll();
});
listEl.appendChild(item);
}
}
function renderEditor() {
const note = S.getActiveNote();
if (!note) {
editorContainerEl.classList.add("hidden");
emptyStateEl.classList.remove("hidden");
return;
}
editorContainerEl.classList.remove("hidden");
emptyStateEl.classList.add("hidden");
// Load note content into editor
Editor.loadNote(note);
// Set title
titleInputEl.value = note.title;
// Update pin button state
const pinBtn = document.getElementById("pin-note-btn");
if (pinBtn) {
pinBtn.style.opacity = note.pinned ? "1" : "0.4";
}
// Render tags
tagDisplayEl.innerHTML = "";
for (const tag of note.tags) {
const pill = document.createElement("span");
pill.className = "tag-pill";
pill.textContent = "#" + tag;
const removeBtn = document.createElement("span");
removeBtn.className = "remove-tag";
removeBtn.textContent = "×";
removeBtn.addEventListener("click", () => {
S.removeTag(note.id, tag);
renderEditor();
});
pill.appendChild(removeBtn);
tagDisplayEl.appendChild(pill);
}
// Update toolbar state
Editor.updateToolbarState();
}
// ─── Autosave ──────────────────────────────────────────────
function debounceAutosave(content) {
if (autosaveTimer)
clearTimeout(autosaveTimer);
autosaveTimer = setTimeout(() => {
const noteId = S.getState().activeNoteId;
if (noteId) {
S.updateNote(noteId, { content });
showSavedIndicator();
}
}, 500);
}
function onTitleChanged(title) {
// Debounced title save is handled by the same autosave mechanism
const noteId = S.getState().activeNoteId;
if (noteId) {
if (autosaveTimer)
clearTimeout(autosaveTimer);
autosaveTimer = setTimeout(() => {
S.updateNote(noteId, { title });
showSavedIndicator();
renderNoteList(); // Update note list with new title
}, 500);
}
}
function showSavedIndicator() {
savedIndicatorEl.classList.add("show");
if (savedIndicatorTimeout)
clearTimeout(savedIndicatorTimeout);
savedIndicatorTimeout = setTimeout(() => {
savedIndicatorEl.classList.remove("show");
}, 1500);
}
// ─── Resize Handles ──────────────────────────────────────
function setupResizeHandles() {
const handle1 = document.getElementById("resize-handle-1");
const handle2 = document.getElementById("resize-handle-2");
if (handle1)
makeResizable(handle1, (delta) => {
S.setSidebarWidth(Math.max(180, Math.min(400, S.getLayout().sidebarWidth + delta)));
applyLayout();
});
if (handle2)
makeResizable(handle2, (delta) => {
S.setNoteListWidth(Math.max(220, Math.min(500, S.getLayout().noteListWidth + delta)));
applyLayout();
});
}
function makeResizable(handle, onResize) {
let startX = 0;
const onMouseDown = (e) => {
e.preventDefault();
startX = e.clientX;
handle.classList.add("active");
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
const onMouseMove = (e) => {
onResize(e.clientX - startX);
startX = e.clientX;
};
const onMouseUp = () => {
handle.classList.remove("active");
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
handle.addEventListener("mousedown", onMouseDown);
}
// ─── Search ──────────────────────────────────────────────
function setupSearch() {
const searchInput = document.getElementById("search-input");
let searchTimer;
searchInput?.addEventListener("input", () => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
S.setSearchQuery(searchInput.value);
renderNoteList();
}, 200);
});
}
// ─── Layout ──────────────────────────────────────────────
function applyLayout() {
const layout = S.getLayout();
sidebarEl.style.setProperty("--sidebar-width", layout.sidebarWidth + "px");
document.getElementById("note-list-pane").style.setProperty("--note-list-width", layout.noteListWidth + "px");
// Show/hide collapse button
const app = document.getElementById("app");
if (layout.sidebarCollapsed) {
app?.classList.add("sidebar-collapsed-show");
}
else {
app?.classList.remove("sidebar-collapsed-show");
}
}
// ─── Render All ──────────────────────────────────────────
function renderAll() {
applyLayout();
renderSidebar();
renderNoteList();
renderEditor();
}
// ─── Utility ──────────────────────────────────────────────
function formatRelativeTime(timestamp) {
const now = Date.now();
const diff = now - timestamp;
if (diff < 60000)
return "Just now";
if (diff < 3600000)
return Math.floor(diff / 60000) + "m ago";
if (diff < 86400000)
return Math.floor(diff / 3600000) + "h ago";
if (diff < 604800000)
return Math.floor(diff / 86400000) + "d ago";
// Fall back to date format
const d = new Date(timestamp);
return d.toLocaleDateString(undefined, { month: "short", day: "numeric" });
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"698750dc-8777-474c-98ad-83a018a5137a","tokensIn":2563797,"tokensOut":55320,"tokensTotal":2619117,"cost":0,"turns":65,"toolCalls":64,"failedToolCalls":2,"timestamp":"2026-07-31T20:05:11.607Z"} -->