Notes
ThinkingCap Qwen3.6 27B GGUF · 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 68,181 bytes · SHA-256 c40fe2e85d60
<!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; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg);
transition: background 0.2s, color 0.2s;
}
/* CSS Variables from custom properties */
:root {
--bg: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #f0f1f3;
--bg-hover: #e8eaed;
--bg-active: #e3e6ea;
--text: #1a1a1a;
--text-secondary: #5f6368;
--text-tertiary: #9aa0a6;
--border: #e0e0e0;
--border-light: #eee;
--accent: #4285f4;
--accent-light: #e8f0fe;
--accent-dark: #2b6cb0;
--danger: #ea4335;
--danger-light: #fce8e6;
--success: #34a853;
--warning: #fbbc04;
--editor-bg: #ffffff;
--editor-text: #202124;
--code-bg: #f5f5f5;
--code-color: #c7254e;
--quote-border: #4285f4;
--hr-color: #e0e0e0;
--checkbox-color: #4285f4;
--tag-bg: #e8f0fe;
--tag-text: #1967d2;
--pin-color: #fbbc04;
--shadow: rgba(0,0,0,0.08);
--toolbar-bg: #fafafa;
--divider-color: #e0e0e0;
--font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-editor: 'Georgia', 'Times New Roman', serif;
--font-mono: 'Consolas', 'Monaco', monospace;
}
body.dark-mode {
--bg: #1e1e2e;
--bg-secondary: #181825;
--bg-tertiary: #11111b;
--bg-hover: #313244;
--bg-active: #45475a;
--text: #cdd6f4;
--text-secondary: #a6adc8;
--text-tertiary: #6c7086;
--border: #313244;
--border-light: #2a2a3c;
--accent: #89b4fa;
--accent-light: #333353;
--accent-dark: #74c7ec;
--danger: #f38ba8;
--danger-light: #3d2330;
--success: #a6e3a1;
--warning: #f9e2af;
--editor-bg: #1e1e2e;
--editor-text: #cdd6f4;
--code-bg: #313244;
--code-color: #f38ba8;
--quote-border: #89b4fa;
--hr-color: #313244;
--checkbox-color: #89b4fa;
--tag-bg: #333353;
--tag-text: #89b4fa;
--pin-color: #f9e2af;
--shadow: rgba(0,0,0,0.3);
--toolbar-bg: #181825;
--divider-color: #313244;
}
/* Sidebar */
#sidebar-pane {
width: 240px;
min-width: 180px;
max-width: 400px;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
transition: width 0.2s ease, opacity 0.2s ease;
overflow: hidden;
}
#sidebar-pane.collapsed {
width: 0 !important;
min-width: 0;
border-right: none;
opacity: 0;
pointer-events: none;
}
#sidebar-header {
padding: 16px;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
#app-logo {
font-size: 18px;
font-weight: 700;
color: var(--accent);
letter-spacing: -0.5px;
}
#new-notebook-btn {
margin-left: auto;
width: 28px;
height: 28px;
border: none;
background: var(--accent);
color: white;
border-radius: 6px;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s, transform 0.1s;
}
#new-notebook-btn:hover { background: var(--accent-dark); transform: scale(1.05); }
#sidebar-content {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.sidebar-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 6px;
cursor: pointer;
transition: background 0.1s;
user-select: none;
margin-bottom: 2px;
}
.sidebar-item:hover { background: var(--bg-hover); }
.sidebar-item.active { background: var(--accent-light); color: var(--accent); }
.sidebar-icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }
.sidebar-label {
flex: 1;
font-size: 13px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar-item .notebook-actions {
display: flex;
gap: 2px;
opacity: 0;
transition: opacity 0.1s;
}
.sidebar-item:hover .notebook-actions { opacity: 1; }
.notebook-actions button {
width: 22px;
height: 22px;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.notebook-actions button:hover { background: var(--bg-active); color: var(--text); }
.notebook-toggle {
width: 20px;
height: 20px;
border: none;
background: transparent;
cursor: pointer;
color: var(--text-tertiary);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
flex-shrink: 0;
transition: transform 0.15s;
}
.notebook-toggle.collapsed { transform: rotate(-90deg); }
/* Notebook notes list in sidebar */
.notebook-notes {
padding-left: 16px;
overflow: hidden;
transition: max-height 0.2s ease;
}
.notebook-notes.collapsed { max-height: 0 !important; }
.notebook-note-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border-radius: 5px;
cursor: pointer;
font-size: 12.5px;
color: var(--text-secondary);
transition: background 0.1s;
margin-bottom: 1px;
}
.notebook-note-item:hover { background: var(--bg-hover); color: var(--text); }
.notebook-note-item.active { background: var(--accent-light); color: var(--accent); font-weight: 500; }
.notebook-note-item .pin-dot {
color: var(--pin-color);
font-size: 10px;
flex-shrink: 0;
}
/* Resize handles */
.resize-handle {
width: 4px;
cursor: col-resize;
background: transparent;
transition: background 0.15s;
flex-shrink: 0;
}
.resize-handle:hover, .resize-handle.active {
background: var(--accent);
}
.resize-handle.hidden { display: none; }
/* Note List Pane */
#notelist-pane {
width: 300px;
min-width: 200px;
max-width: 500px;
background: var(--bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
#notelist-header {
padding: 12px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
#search-input {
flex: 1;
padding: 7px 10px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 13px;
background: var(--bg-secondary);
color: var(--text);
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
font-family: var(--font-ui);
}
#search-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-light);
}
#notelist-actions {
display: flex;
gap: 4px;
}
#notelist-actions button {
width: 30px;
height: 30px;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: 6px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.1s, color 0.1s;
}
#notelist-actions button:hover { background: var(--bg-hover); color: var(--text); }
#new-note-btn { background: var(--accent) !important; color: white !important; }
#new-note-btn:hover { background: var(--accent-dark) !important; }
/* Note filter bar */
#note-filter-bar {
padding: 8px 12px;
display: flex;
gap: 6px;
flex-shrink: 0;
border-bottom: 1px solid var(--border-light);
}
.filter-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
background: var(--tag-bg);
color: var(--tag-text);
border-radius: 12px;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: background 0.1s;
}
.filter-tag:hover { opacity: 0.8; }
.filter-tag .remove { font-size: 14px; line-height: 1; }
/* Note list */
#note-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.note-card {
padding: 12px;
border-radius: 8px;
cursor: pointer;
margin-bottom: 4px;
transition: background 0.1s, box-shadow 0.1s;
border: 1px solid transparent;
}
.note-card:hover { background: var(--bg-secondary); }
.note-card.active {
background: var(--accent-light);
border-color: var(--accent);
box-shadow: 0 1px 3px var(--shadow);
}
.note-card-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.note-card-title .pin-icon {
color: var(--pin-color);
font-size: 12px;
flex-shrink: 0;
}
.note-card-snippet {
font-size: 12.5px;
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 4px;
}
.note-card-meta {
font-size: 11px;
color: var(--text-tertiary);
display: flex;
align-items: center;
gap: 8px;
}
.note-card-tags {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-top: 6px;
}
.note-tag {
padding: 1px 7px;
background: var(--tag-bg);
color: var(--tag-text);
border-radius: 10px;
font-size: 10.5px;
font-weight: 500;
}
/* Editor Pane */
#editor-pane {
flex: 1;
display: flex;
flex-direction: column;
min-width: 300px;
overflow: hidden;
background: var(--editor-bg);
}
.editor-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-tertiary);
gap: 12px;
user-select: none;
}
.editor-empty-icon { font-size: 48px; opacity: 0.5; }
.editor-empty-text {
font-size: 16px;
font-weight: 400;
letter-spacing: 0.2px;
}
#editor-container {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
/* Toolbar */
#editor-toolbar {
display: flex;
align-items: center;
gap: 2px;
padding: 6px 12px;
background: var(--toolbar-bg);
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
flex-shrink: 0;
}
#editor-toolbar button {
width: 32px;
height: 30px;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: 5px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.1s, color 0.1s;
font-family: var(--font-ui);
}
#editor-toolbar button:hover { background: var(--bg-hover); color: var(--text); }
#editor-toolbar button.active { background: var(--accent-light); color: var(--accent); }
.toolbar-divider {
width: 1px;
height: 20px;
background: var(--divider-color);
margin: 0 4px;
}
/* Editor meta */
#editor-meta {
padding: 16px 24px 8px;
flex-shrink: 0;
}
#editor-title {
width: 100%;
border: none;
font-size: 26px;
font-weight: 700;
background: transparent;
color: var(--editor-text);
outline: none;
font-family: var(--font-ui);
line-height: 1.3;
letter-spacing: -0.5px;
}
#editor-title::placeholder { color: var(--text-tertiary); }
#editor-tags {
display: flex;
align-items: center;
gap: 6px;
margin-top: 8px;
flex-wrap: wrap;
}
.tag-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
background: var(--tag-bg);
color: var(--tag-text);
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.tag-chip .tag-remove {
cursor: pointer;
font-size: 14px;
line-height: 1;
opacity: 0.6;
transition: opacity 0.1s;
}
.tag-chip .tag-remove:hover { opacity: 1; }
#tag-input {
border: 1px solid var(--border);
background: transparent;
color: var(--text);
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
outline: none;
width: 100px;
font-family: var(--font-ui);
}
#tag-input:focus { border-color: var(--accent); }
/* Editor content */
#editor-content {
flex: 1;
overflow-y: auto;
padding: 12px 24px 100px;
font-family: var(--font-editor);
font-size: 16px;
line-height: 1.75;
color: var(--editor-text);
outline: none;
min-height: 200px;
}
#editor-content h1 { font-size: 2em; font-weight: 700; margin: 0.6em 0 0.3em; line-height: 1.3; letter-spacing: -0.5px; }
#editor-content h2 { font-size: 1.5em; font-weight: 600; margin: 0.5em 0 0.25em; line-height: 1.3; }
#editor-content h3 { font-size: 1.25em; font-weight: 600; margin: 0.4em 0 0.2em; }
#editor-content p { margin: 0.3em 0; }
#editor-content blockquote {
border-left: 3px solid var(--quote-border);
padding-left: 16px;
margin: 0.5em 0;
color: var(--text-secondary);
font-style: italic;
}
#editor-content hr {
border: none;
border-top: 2px solid var(--hr-color);
margin: 1em 0;
}
#editor-content code {
background: var(--code-bg);
color: var(--code-color);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
font-family: var(--font-mono);
}
#editor-content ul, #editor-content ol {
padding-left: 24px;
margin: 0.3em 0;
}
#editor-content li { margin: 0.15em 0; }
/* Checklist items */
.checklist-item {
list-style: none;
display: flex;
align-items: flex-start;
gap: 8px;
margin: 0.15em 0;
padding-left: 0;
}
.checklist-checkbox {
width: 18px;
height: 18px;
border: 2px solid var(--accent);
border-radius: 4px;
cursor: pointer;
flex-shrink: 0;
margin-top: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.1s, border-color 0.1s;
}
.checklist-checkbox.checked {
background: var(--accent);
border-color: var(--accent);
}
.checklist-checkbox.checked::after {
content: '✓';
color: white;
font-size: 12px;
font-weight: 700;
}
.checklist-checkbox:hover { background: var(--accent-light); }
.checklist-text { flex: 1; }
.checklist-item.checked .checklist-text {
text-decoration: line-through;
color: var(--text-tertiary);
}
/* Editor status bar */
#editor-status {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 24px;
border-top: 1px solid var(--border);
font-size: 11px;
color: var(--text-tertiary);
flex-shrink: 0;
}
#save-indicator {
transition: opacity 0.3s;
font-weight: 500;
}
#save-indicator.saving { color: var(--warning); }
#save-indicator.saved { color: var(--success); }
/* Note actions bar */
#note-actions-bar {
display: flex;
align-items: center;
padding: 6px 12px;
border-top: 1px solid var(--border);
gap: 4px;
flex-shrink: 0;
background: var(--bg-secondary);
}
#note-actions-bar button {
width: 32px;
height: 28px;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: 5px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.1s;
}
#note-actions-bar button:hover { background: var(--bg-hover); }
#note-actions-bar .spacer { flex: 1; }
#note-actions-bar .danger-btn:hover { background: var(--danger-light); color: var(--danger); }
/* Move note dropdown */
.move-dropdown {
position: absolute;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 12px var(--shadow);
padding: 4px;
z-index: 100;
min-width: 180px;
}
.move-dropdown-item {
padding: 8px 12px;
border-radius: 5px;
cursor: pointer;
font-size: 13px;
transition: background 0.1s;
}
.move-dropdown-item:hover { background: var(--bg-hover); }
/* Dark mode toggle */
#dark-mode-toggle {
position: fixed;
bottom: 16px;
right: 16px;
width: 36px;
height: 36px;
border: none;
background: var(--bg-secondary);
color: var(--text);
border-radius: 50%;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px var(--shadow);
transition: background 0.1s, transform 0.1s;
z-index: 200;
}
#dark-mode-toggle:hover { transform: scale(1.1); }
/* Rename input */
.rename-input {
border: 1px solid var(--accent);
background: var(--bg);
color: var(--text);
padding: 2px 6px;
border-radius: 4px;
font-size: 13px;
outline: none;
font-family: var(--font-ui);
flex: 1;
min-width: 0;
}
/* Search highlight */
.search-highlight {
background: var(--warning);
color: var(--text);
border-radius: 2px;
padding: 0 1px;
}
/* Scrollbar styling */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-hover); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--bg-active); }
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.note-card { animation: fadeIn 0.15s ease; }
</style>
</head>
<body>
<div id="app" style="display:flex;height:100vh;overflow:hidden;">
<!-- Sidebar Pane -->
<div id="sidebar-pane">
<div id="sidebar-header">
<span id="app-logo">Notes</span>
<button id="new-notebook-btn" title="New Notebook">+</button>
</div>
<div id="sidebar-content">
<div id="all-notes-item" class="sidebar-item" data-type="virtual-notebook" data-id="all">
<span class="sidebar-icon">📋</span>
<span class="sidebar-label">All Notes</span>
</div>
<div id="trash-item" class="sidebar-item" data-type="virtual-notebook" data-id="trash">
<span class="sidebar-icon">🗑️</span>
<span class="sidebar-label">Trash</span>
</div>
<div id="notebook-list"></div>
</div>
</div>
<div id="sidebar-handle" class="resize-handle"></div>
<!-- Note List Pane -->
<div id="notelist-pane">
<div id="notelist-header">
<input id="search-input" type="text" placeholder="Search notes...">
<div id="notelist-actions">
<button id="sort-toggle" title="Sort order">⇅</button>
<button id="new-note-btn" title="New Note">+</button>
</div>
</div>
<div id="note-filter-bar"></div>
<div id="note-list"></div>
</div>
<div id="notelist-handle" class="resize-handle"></div>
<!-- Editor Pane -->
<div id="editor-pane">
<div id="editor-empty" class="editor-empty">
<div class="editor-empty-icon">📝</div>
<div class="editor-empty-text">Select or create a note</div>
</div>
<div id="editor-container" style="display:none;">
<div id="editor-toolbar">
<button data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
<button data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
<button data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
<button data-cmd="strikeThrough" title="Strikethrough"><s>S</s></button>
<span class="toolbar-divider"></span>
<button data-cmd="code" title="Inline Code"><></button>
<span class="toolbar-divider"></span>
<button data-cmd="h1" title="Heading 1">H1</button>
<button data-cmd="h2" title="Heading 2">H2</button>
<button data-cmd="h3" title="Heading 3">H3</button>
<span class="toolbar-divider"></span>
<button data-cmd="ul" title="Bullet List">•</button>
<button data-cmd="ol" title="Numbered List">1.</button>
<button data-cmd="checklist" title="Checklist">☑</button>
<button data-cmd="blockquote" title="Blockquote">❝</button>
<button data-cmd="hr" title="Horizontal Rule">—</button>
</div>
<div id="editor-meta">
<input id="editor-title" type="text" placeholder="Note title...">
<div id="editor-tags"></div>
</div>
<div id="editor-content" contenteditable="true" spellcheck="true"></div>
<div id="editor-status">
<span id="save-indicator"></span>
<span id="note-info"></span>
</div>
</div>
<!-- Note action menu -->
<div id="note-actions-bar">
<button id="pin-note-btn" title="Pin/Unpin">📌</button>
<button id="duplicate-note-btn" title="Duplicate">📋</button>
<button id="move-note-btn" title="Move to notebook">↗</button>
<button id="delete-note-btn" title="Delete">🗑</button>
<span class="spacer"></span>
<button id="restore-note-btn" title="Restore" style="display:none;">♻</button>
<button id="permanent-delete-btn" title="Permanently delete" style="display:none;">✕</button>
</div>
</div>
</div>
<button id="dark-mode-toggle" title="Toggle dark mode">🌙</button>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./sidebar":"src/sidebar.ts","./notelist":"src/notelist.ts","./editor":"src/editor.ts"},"src/sidebar.ts":{"./state":"src/state.ts","./constants":"src/constants.ts"},"src/notelist.ts":{"./state":"src/state.ts","./constants":"src/constants.ts"},"src/editor.ts":{"./state":"src/state.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";
Object.defineProperty(exports, "__esModule", { value: true });
const state_1 = require("./state");
const sidebar_1 = require("./sidebar");
const notelist_1 = require("./notelist");
const editor_1 = require("./editor");
function start() {
(0, sidebar_1.initSidebar)();
(0, notelist_1.initNoteList)();
(0, editor_1.initEditor)();
(0, state_1.onStateChange)(() => {
(0, sidebar_1.render)();
(0, notelist_1.render)();
(0, editor_1.render)();
applyDarkMode();
applyPaneSizes();
});
initResizeHandles();
applyDarkMode();
applyPaneSizes();
const darkToggle = document.getElementById('dark-mode-toggle');
darkToggle?.addEventListener('click', () => {
(0, state_1.setState)(s => { s.darkMode = !s.darkMode; });
});
}
function applyDarkMode() {
const state = (0, state_1.getState)();
document.body.classList.toggle('dark-mode', state.darkMode);
const toggle = document.getElementById('dark-mode-toggle');
if (toggle)
toggle.textContent = state.darkMode ? '☀️' : '🌙';
}
function applyPaneSizes() {
const state = (0, state_1.getState)();
const sidebarPane = document.getElementById('sidebar-pane');
if (sidebarPane)
sidebarPane.style.width = state.layout.sidebarWidth + 'px';
const notelistPane = document.getElementById('notelist-pane');
if (notelistPane)
notelistPane.style.width = state.layout.noteListWidth + 'px';
}
function initResizeHandles() {
const sidebarHandle = document.getElementById('sidebar-handle');
const sidebarPane = document.getElementById('sidebar-pane');
const notelistHandle = document.getElementById('notelist-handle');
const notelistPane = document.getElementById('notelist-pane');
if (sidebarHandle && sidebarPane) {
makeResizable(sidebarHandle, sidebarPane, (delta) => {
(0, state_1.setState)(s => {
s.layout.sidebarWidth = Math.max(180, Math.min(400, s.layout.sidebarWidth + delta));
});
});
sidebarHandle.addEventListener('dblclick', () => {
(0, state_1.setState)(s => {
s.layout.sidebarCollapsed = !s.layout.sidebarCollapsed;
});
});
}
if (notelistHandle && notelistPane) {
makeResizable(notelistHandle, notelistPane, (delta) => {
(0, state_1.setState)(s => {
s.layout.noteListWidth = Math.max(200, Math.min(500, s.layout.noteListWidth + delta));
});
});
}
}
function makeResizable(handle, target, onResize) {
function onMouseMove(e) {
onResize(e.clientX - handle._startX);
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
handle.classList.remove('active');
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
handle.addEventListener('mousedown', (e) => {
e.preventDefault();
handle._startX = e.clientX;
handle.classList.add('active');
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
start();
};
// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONSTANTS = void 0;
// Tunable constants
exports.CONSTANTS = {
autosaveDebounce: 500,
snippetLength: 80,
minPaneWidth: 180,
defaultSidebarWidth: 240,
defaultNoteListWidth: 300,
colors: {
light: {
bg: '#ffffff',
bgSecondary: '#f8f9fa',
bgTertiary: '#f0f1f3',
bgHover: '#e8eaed',
bgActive: '#e3e6ea',
text: '#1a1a1a',
textSecondary: '#5f6368',
textTertiary: '#9aa0a6',
border: '#e0e0e0',
borderLight: '#eee',
accent: '#4285f4',
accentLight: '#e8f0fe',
accentDark: '#2b6cb0',
danger: '#ea4335',
dangerLight: '#fce8e6',
success: '#34a853',
warning: '#fbbc04',
editorBg: '#ffffff',
editorText: '#202124',
codeBg: '#f5f5f5',
codeColor: '#c7254e',
quoteBorder: '#4285f4',
hrColor: '#e0e0e0',
checkbox: '#4285f4',
tagBg: '#e8f0fe',
tagText: '#1967d2',
pinColor: '#fbbc04',
shadow: 'rgba(0,0,0,0.08)',
toolbarBg: '#fafafa',
dividerColor: '#e0e0e0',
},
dark: {
bg: '#1e1e2e',
bgSecondary: '#181825',
bgTertiary: '#11111b',
bgHover: '#313244',
bgActive: '#45475a',
text: '#cdd6f4',
textSecondary: '#a6adc8',
textTertiary: '#6c7086',
border: '#313244',
borderLight: '#2a2a3c',
accent: '#89b4fa',
accentLight: '#333353',
accentDark: '#74c7ec',
danger: '#f38ba8',
dangerLight: '#3d2330',
success: '#a6e3a1',
warning: '#f9e2af',
editorBg: '#1e1e2e',
editorText: '#cdd6f4',
codeBg: '#313244',
codeColor: '#f38ba8',
quoteBorder: '#89b4fa',
hrColor: '#313244',
checkbox: '#89b4fa',
tagBg: '#333353',
tagText: '#89b4fa',
pinColor: '#f9e2af',
shadow: 'rgba(0,0,0,0.3)',
toolbarBg: '#181825',
dividerColor: '#313244',
},
},
fonts: {
ui: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
editor: "'Source Serif 4', 'Lora', 'Georgia', serif",
mono: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
},
};
};
// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// Data model types and state
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultState = void 0;
exports.onStateChange = onStateChange;
exports.notifyChange = notifyChange;
exports.getState = getState;
exports.setState = setState;
exports.persist = persist;
exports.markUnsaved = markUnsaved;
exports.setSaving = setSaving;
exports.getNote = getNote;
exports.getNotebook = getNotebook;
exports.stripHtml = stripHtml;
let _listeners = [];
function onStateChange(fn) {
_listeners.push(fn);
}
function notifyChange() {
for (const fn of _listeners)
fn();
}
function loadFromStorage() {
try {
const raw = localStorage.getItem('notes-app-state');
if (!raw)
return null;
return JSON.parse(raw);
}
catch {
return null;
}
}
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
}
exports.defaultState = {
data: {
notebooks: [
{ id: generateId(), name: 'Personal', collapsed: false, createdAt: Date.now() },
{ id: generateId(), name: 'Work', collapsed: false, createdAt: Date.now() },
],
notes: [
{
id: generateId(),
notebookId: '',
title: 'Welcome to Notes',
content: '<h1>Welcome to Notes!</h1><p>This is a <b>rich text editor</b>. Try the toolbar or keyboard shortcuts like <i>Ctrl+B</i> for bold.</p><p>Markdown shortcuts work too:</p><ul><li>Type <code># </code> for headings</li><li>Type <code>- </code> for lists</li><li>Type <code>[] </code> for checklists</li></ul><p>Enjoy!</p>',
tags: ['welcome', 'tips'],
pinned: true,
createdAt: Date.now() - 100000,
updatedAt: Date.now() - 100000,
},
],
trash: [],
},
layout: {
sidebarWidth: 240,
noteListWidth: 300,
sidebarCollapsed: false,
},
activeNotebookId: 'all',
activeNoteId: null,
searchQuery: '',
tagFilter: null,
sortMode: 'modified',
darkMode: false,
saveStatus: 'saved',
};
const firstNoteId = exports.defaultState.data.notes[0].id;
const firstNotebookId = exports.defaultState.data.notebooks[0]?.id || '';
exports.defaultState.data.notes[0].notebookId = firstNotebookId;
let state = loadFromStorage() || { ...exports.defaultState, saveStatus: 'saved' };
function getState() {
return state;
}
function setState(updater) {
updater(state);
persist();
notifyChange();
}
function persist() {
state.saveStatus = 'saved';
try {
localStorage.setItem('notes-app-state', JSON.stringify(state));
}
catch { /* storage full */ }
}
function markUnsaved() {
state.saveStatus = 'unsaved';
notifyChange();
}
function setSaving() {
state.saveStatus = 'saving';
notifyChange();
}
function getNote(id) {
return state.data.notes.find(n => n.id === id)
|| state.data.trash.find(n => n.id === id);
}
function getNotebook(id) {
return state.data.notebooks.find(n => n.id === id);
}
function stripHtml(html) {
const tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
}
};
// ── module: src/sidebar.ts ──
__mods["src/sidebar.ts"] = function (exports, require, module) {
"use strict";
// Sidebar rendering and interaction
Object.defineProperty(exports, "__esModule", { value: true });
exports.initSidebar = initSidebar;
exports.render = render;
const state_1 = require("./state");
let sidebarElement = null;
let notebookListElement = null;
function initSidebar() {
sidebarElement = document.getElementById('sidebar-pane');
notebookListElement = document.getElementById('notebook-list');
render();
bindEvents();
}
function render() {
const state = (0, state_1.getState)();
if (!notebookListElement)
return;
// Update active states for virtual items
const allNotesItem = document.getElementById('all-notes-item');
const trashItem = document.getElementById('trash-item');
if (allNotesItem)
allNotesItem.classList.toggle('active', state.activeNotebookId === 'all');
if (trashItem)
trashItem.classList.toggle('active', state.activeNotebookId === 'trash');
// Render notebook list
notebookListElement.innerHTML = '';
for (const nb of state.data.notebooks) {
const nbEl = document.createElement('div');
nbEl.className = 'sidebar-item';
nbEl.dataset.notebookId = nb.id;
const notebookNotes = state.data.notes.filter(n => n.notebookId === nb.id);
nbEl.innerHTML = `
<button class="notebook-toggle ${nb.collapsed ? 'collapsed' : ''}" data-toggle="${nb.id}">▼</button>
<span class="sidebar-icon">📁</span>
<span class="sidebar-label">${escapeHtml(nb.name)}</span>
<div class="notebook-actions">
<button data-rename="${nb.id}" title="Rename">✎</button>
<button data-delete-nb="${nb.id}" title="Delete">✕</button>
</div>
`;
if (state.activeNotebookId === nb.id)
nbEl.classList.add('active');
notebookListElement.appendChild(nbEl);
// Notes under this notebook
if (!nb.collapsed) {
const notesContainer = document.createElement('div');
notesContainer.className = 'notebook-notes';
for (const note of notebookNotes) {
const noteItem = document.createElement('div');
noteItem.className = 'notebook-note-item';
if (state.activeNoteId === note.id)
noteItem.classList.add('active');
noteItem.dataset.noteId = note.id;
noteItem.innerHTML = `
${note.pinned ? '<span class="pin-dot">📌</span>' : ''}
<span>${escapeHtml(note.title || 'Untitled')}</span>
`;
notesContainer.appendChild(noteItem);
}
notebookListElement.appendChild(notesContainer);
}
}
// Update sidebar width
if (sidebarElement) {
sidebarElement.style.width = state.layout.sidebarWidth + 'px';
sidebarElement.classList.toggle('collapsed', state.layout.sidebarCollapsed);
}
}
function bindEvents() {
const app = document.getElementById('app');
if (!app)
return;
// Sidebar item clicks
app.addEventListener('click', (e) => {
const target = e.target;
// Toggle notebook collapse
const toggle = target.closest('[data-toggle]');
if (toggle) {
const nbId = toggle.dataset.toggle;
(0, state_1.setState)(s => {
const nb = s.data.notebooks.find(n => n.id === nbId);
if (nb)
nb.collapsed = !nb.collapsed;
});
return;
}
// Rename notebook
const renameBtn = target.closest('[data-rename]');
if (renameBtn) {
renameNotebook(renameBtn.dataset.rename);
return;
}
// Delete notebook
const deleteNbBtn = target.closest('[data-delete-nb]');
if (deleteNbBtn) {
deleteNotebook(deleteNbBtn.dataset.deleteNb);
return;
}
// Virtual notebook (All Notes / Trash)
const virtualItem = target.closest('[data-type="virtual-notebook"]');
if (virtualItem) {
(0, state_1.setState)(s => {
s.activeNotebookId = virtualItem.dataset.id;
s.activeNoteId = null;
s.tagFilter = null;
});
return;
}
// Notebook selection
const notebookItem = target.closest('.sidebar-item');
if (notebookItem && notebookItem.dataset.notebookId) {
(0, state_1.setState)(s => {
s.activeNotebookId = notebookItem.dataset.notebookId;
s.activeNoteId = null;
s.tagFilter = null;
});
return;
}
// Notebook note selection
const noteItem = target.closest('.notebook-note-item');
if (noteItem && noteItem.dataset.noteId) {
(0, state_1.setState)(s => {
s.activeNoteId = noteItem.dataset.noteId;
const note = (0, state_1.getNote)(noteItem.dataset.noteId);
s.activeNotebookId = note?.notebookId || 'all';
s.tagFilter = null;
});
return;
}
});
// New notebook button
document.getElementById('new-notebook-btn')?.addEventListener('click', () => {
const name = prompt('Notebook name:');
if (name?.trim()) {
(0, state_1.setState)(s => {
s.data.notebooks.push({
id: Date.now().toString(36) + Math.random().toString(36).substring(2, 9),
name: name.trim(),
collapsed: false,
createdAt: Date.now(),
});
});
}
});
}
function renameNotebook(nbId) {
const state = (0, state_1.getState)();
const nb = state.data.notebooks.find(n => n.id === nbId);
if (!nb)
return;
const newName = prompt('Rename notebook:', nb.name);
if (newName?.trim()) {
(0, state_1.setState)(s => {
const notebook = s.data.notebooks.find(n => n.id === nbId);
if (notebook)
notebook.name = newName.trim();
});
}
}
function deleteNotebook(nbId) {
const state = (0, state_1.getState)();
const nb = state.data.notebooks.find(n => n.id === nbId);
if (!nb)
return;
if (!confirm(`Delete "${nb.name}" and all its notes?`))
return;
(0, state_1.setState)(s => {
// Move notes to trash
const notesInNb = s.data.notes.filter(n => n.notebookId === nbId);
s.data.trash.push(...notesInNb);
s.data.notes = s.data.notes.filter(n => n.notebookId !== nbId);
s.data.notebooks = s.data.notebooks.filter(n => n.id !== nbId);
if (s.activeNotebookId === nbId) {
s.activeNotebookId = 'all';
s.activeNoteId = null;
}
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
};
// ── module: src/notelist.ts ──
__mods["src/notelist.ts"] = function (exports, require, module) {
"use strict";
// Note list rendering and interaction
Object.defineProperty(exports, "__esModule", { value: true });
exports.initNoteList = initNoteList;
exports.render = render;
const state_1 = require("./state");
const constants_1 = require("./constants");
let noteListElement = null;
let searchInputElement = null;
let filterBarElement = null;
let sortToggleElement = null;
function initNoteList() {
noteListElement = document.getElementById('note-list');
searchInputElement = document.getElementById('search-input');
filterBarElement = document.getElementById('note-filter-bar');
sortToggleElement = document.getElementById('sort-toggle');
render();
bindEvents();
}
const SORT_MODES = ['modified', 'created', 'alphabetical'];
function render() {
const state = (0, state_1.getState)();
if (!noteListElement)
return;
// Filter notes based on active notebook
let notes;
if (state.activeNotebookId === 'all') {
notes = state.data.notes;
}
else if (state.activeNotebookId === 'trash') {
notes = state.data.trash;
}
else {
notes = state.data.notes.filter(n => n.notebookId === state.activeNotebookId);
}
// Apply tag filter
if (state.tagFilter) {
notes = notes.filter(n => n.tags.includes(state.tagFilter));
}
// Apply search
if (state.searchQuery) {
const q = state.searchQuery.toLowerCase();
notes = notes.filter(n => {
const titleMatch = n.title.toLowerCase().includes(q);
const bodyMatch = (0, state_1.stripHtml)(n.content || '').toLowerCase().includes(q);
return titleMatch || bodyMatch;
});
}
// Sort: pinned first, then by sort mode
notes.sort((a, b) => {
if (a.pinned && !b.pinned)
return -1;
if (!a.pinned && b.pinned)
return 1;
switch (state.sortMode) {
case 'created': return b.createdAt - a.createdAt;
case 'alphabetical': return a.title.localeCompare(b.title);
case 'modified':
default: return b.updatedAt - a.updatedAt;
}
});
// Render
noteListElement.innerHTML = '';
if (notes.length === 0) {
const empty = document.createElement('div');
empty.style.cssText = 'padding:24px;text-align:center;color:var(--text-tertiary);font-size:13px;';
empty.textContent = state.searchQuery ? 'No matching notes' : 'No notes here';
noteListElement.appendChild(empty);
}
for (const note of notes) {
const card = document.createElement('div');
card.className = 'note-card';
if (state.activeNoteId === note.id)
card.classList.add('active');
card.dataset.noteId = note.id;
const snippet = (0, state_1.stripHtml)(note.content).substring(0, constants_1.CONSTANTS.snippetLength);
const date = new Date(note.updatedAt);
const dateStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +
' ' + date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
let html = '';
html += `<div class="note-card-title">`;
html += note.pinned ? '<span class="pin-icon">📌</span>' : '';
html += escapeHtml(note.title || 'Untitled');
html += `</div>`;
// Search highlight in snippet
if (state.searchQuery && snippet.toLowerCase().includes(state.searchQuery.toLowerCase())) {
const highlighted = highlightText(snippet, state.searchQuery);
html += `<div class="note-card-snippet">${highlighted}</div>`;
}
else {
html += `<div class="note-card-snippet">${escapeHtml(snippet)}</div>`;
}
html += `<div class="note-card-meta"><span>${dateStr}</span></div>`;
if (note.tags.length > 0) {
html += `<div class="note-card-tags">`;
for (const tag of note.tags) {
html += `<span class="note-tag" data-tag="${escapeHtml(tag)}">${escapeHtml(tag)}</span>`;
}
html += `</div>`;
}
card.innerHTML = html;
noteListElement.appendChild(card);
}
// Update filter bar
if (filterBarElement) {
filterBarElement.innerHTML = '';
if (state.tagFilter) {
const tag = document.createElement('span');
tag.className = 'filter-tag';
tag.innerHTML = `🏷 ${escapeHtml(state.tagFilter)} <span class="remove">×</span>`;
filterBarElement.appendChild(tag);
}
}
// Update sort toggle label
if (sortToggleElement) {
const labels = { modified: '↕ Mod', created: '↕ Cre', alphabetical: '↕ A-Z' };
sortToggleElement.title = labels[state.sortMode] || '';
}
}
function highlightText(text, query) {
if (!query)
return escapeHtml(text);
const escaped = escapeHtml(text);
const escapedQuery = escapeHtml(query);
const regex = new RegExp(`(${escapeRegex(escapedQuery)})`, 'gi');
return escaped.replace(regex, '<span class="search-highlight">$1</span>');
}
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function bindEvents() {
const app = document.getElementById('app');
if (!app)
return;
// Note card click
app.addEventListener('click', (e) => {
const card = e.target.closest('.note-card');
if (card) {
(0, state_1.setState)(s => {
s.activeNoteId = card.dataset.noteId;
s.tagFilter = null;
});
return;
}
// Tag click
const tag = e.target.closest('.note-tag');
if (tag) {
const tagFilter = tag.dataset.tag;
(0, state_1.setState)(s => {
s.tagFilter = s.tagFilter === tagFilter ? null : tagFilter;
});
return;
}
// Filter bar remove
const removeBtn = e.target.closest('.filter-tag .remove');
if (removeBtn) {
(0, state_1.setState)(s => { s.tagFilter = null; });
return;
}
// Filter tag click (click on the tag itself, not the remove)
const filterTag = e.target.closest('.filter-tag');
if (filterTag && !filterTag.querySelector('.remove')?.contains(e.target)) {
(0, state_1.setState)(s => { s.tagFilter = null; });
return;
}
});
// Search input
if (searchInputElement) {
searchInputElement.addEventListener('input', () => {
(0, state_1.setState)(s => { s.searchQuery = searchInputElement.value; });
});
}
// Sort toggle
if (sortToggleElement) {
sortToggleElement.addEventListener('click', () => {
(0, state_1.setState)(s => {
const idx = SORT_MODES.indexOf(s.sortMode);
s.sortMode = SORT_MODES[(idx + 1) % SORT_MODES.length];
});
});
}
// New note button
document.getElementById('new-note-btn')?.addEventListener('click', () => {
const state = (0, state_1.getState)();
let notebookId = state.activeNotebookId;
if (notebookId === 'all' || notebookId === 'trash') {
notebookId = state.data.notebooks[0]?.id || 'all';
}
if (!notebookId || notebookId === 'all' || notebookId === 'trash') {
alert('Create a notebook first');
return;
}
const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
(0, state_1.setState)(s => {
s.data.notes.push({
id,
notebookId: notebookId,
title: '',
content: '',
tags: [],
pinned: false,
createdAt: Date.now(),
updatedAt: Date.now(),
});
s.activeNoteId = id;
});
});
}
};
// ── module: src/editor.ts ──
__mods["src/editor.ts"] = function (exports, require, module) {
"use strict";
// Rich text editor with contenteditable, toolbar, markdown shortcuts, paste sanitization
Object.defineProperty(exports, "__esModule", { value: true });
exports.initEditor = initEditor;
exports.render = render;
const state_1 = require("./state");
const constants_1 = require("./constants");
let editorEl = null;
let titleEl = null;
let tagsContainer = null;
let saveIndicator = null;
let noteInfoEl = null;
let emptyEl = null;
let containerEl = null;
let toolbarEl = null;
let pinBtn = null;
let dupBtn = null;
let moveBtn = null;
let delBtn = null;
let restoreBtn = null;
let permDelBtn = null;
let _saveTimer = null;
function initEditor() {
editorEl = document.getElementById('editor-content');
titleEl = document.getElementById('editor-title');
tagsContainer = document.getElementById('editor-tags');
saveIndicator = document.getElementById('save-indicator');
noteInfoEl = document.getElementById('note-info');
emptyEl = document.getElementById('editor-empty');
containerEl = document.getElementById('editor-container');
toolbarEl = document.getElementById('editor-toolbar');
pinBtn = document.getElementById('pin-note-btn');
dupBtn = document.getElementById('duplicate-note-btn');
moveBtn = document.getElementById('move-note-btn');
delBtn = document.getElementById('delete-note-btn');
restoreBtn = document.getElementById('restore-note-btn');
permDelBtn = document.getElementById('permanent-delete-btn');
render();
bindEvents();
}
function render() {
const state = (0, state_1.getState)();
const note = (0, state_1.getNote)(state.activeNoteId || '');
const inTrash = state.activeNotebookId === 'trash';
if (!containerEl || !emptyEl)
return;
if (!note) {
containerEl.style.display = 'none';
emptyEl.style.display = '';
return;
}
containerEl.style.display = '';
emptyEl.style.display = 'none';
if (titleEl)
titleEl.value = note.title;
// Only set content if different — prevents cursor jumping during editing
if (editorEl && editorEl.innerHTML !== note.content) {
editorEl.innerHTML = note.content;
}
renderTags(note);
updateSaveIndicator(state.saveStatus);
if (noteInfoEl) {
const created = new Date(note.createdAt).toLocaleDateString();
const modified = new Date(note.updatedAt).toLocaleDateString();
noteInfoEl.textContent = `Created: ${created} · Modified: ${modified}`;
}
if (pinBtn)
pinBtn.style.display = inTrash ? 'none' : '';
if (dupBtn)
dupBtn.style.display = inTrash ? 'none' : '';
if (moveBtn)
moveBtn.style.display = inTrash ? 'none' : '';
if (delBtn)
delBtn.style.display = inTrash ? 'none' : '';
if (restoreBtn)
restoreBtn.style.display = inTrash ? '' : 'none';
if (permDelBtn)
permDelBtn.style.display = inTrash ? '' : 'none';
}
function renderTags(note) {
if (!tagsContainer)
return;
tagsContainer.innerHTML = '';
if (!note)
return;
for (const tag of note.tags) {
const chip = document.createElement('span');
chip.className = 'tag-chip';
chip.innerHTML = `${escapeHtml(tag)} <span class="tag-remove">×</span>`;
tagsContainer.appendChild(chip);
}
const input = document.createElement('input');
input.id = 'tag-input';
input.placeholder = 'Add tag...';
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && input.value.trim()) {
e.preventDefault();
const t = input.value.trim().toLowerCase();
(0, state_1.setState)(s => {
const n = (0, state_1.getNote)(s.activeNoteId || '');
if (n && !n.tags.includes(t)) {
n.tags.push(t);
n.updatedAt = Date.now();
}
});
input.value = '';
}
});
tagsContainer.appendChild(input);
}
function updateSaveIndicator(status) {
if (!saveIndicator)
return;
saveIndicator.className = status;
if (status === 'saved')
saveIndicator.textContent = '✓ Saved';
else if (status === 'saving')
saveIndicator.textContent = '⏳ Saving...';
else
saveIndicator.textContent = '● Unsaved';
}
function escapeHtml(text) {
const d = document.createElement('div');
d.textContent = text;
return d.innerHTML;
}
function bindEvents() {
if (!editorEl || !titleEl)
return;
toolbarEl.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn)
return;
const cmd = btn.dataset?.cmd;
if (!cmd)
return;
editorEl.focus();
handleToolbarCommand(cmd);
});
editorEl.addEventListener('input', () => {
scheduleSave();
updateToolbarState();
});
titleEl.addEventListener('input', () => { scheduleSave(); });
tagsContainer.addEventListener('click', (e) => {
const remove = e.target.closest('.tag-remove');
if (!remove)
return;
const chip = remove.closest('.tag-chip');
if (!chip)
return;
const tagText = chip.childNodes[0].textContent?.trim();
if (!tagText)
return;
(0, state_1.setState)(s => {
const n = (0, state_1.getNote)(s.activeNoteId || '');
if (n) {
n.tags = n.tags.filter(t => t !== tagText);
n.updatedAt = Date.now();
}
});
});
editorEl.addEventListener('keydown', handleEditorKeyDown);
editorEl.addEventListener('paste', handlePaste);
document.addEventListener('selectionchange', () => { setTimeout(updateToolbarState, 0); });
pinBtn.addEventListener('click', () => {
(0, state_1.setState)(s => {
const n = (0, state_1.getNote)(s.activeNoteId || '');
if (n) {
n.pinned = !n.pinned;
n.updatedAt = Date.now();
}
});
});
dupBtn.addEventListener('click', () => {
const state = (0, state_1.getState)();
const note = (0, state_1.getNote)(state.activeNoteId || '');
if (!note)
return;
const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
(0, state_1.setState)(s => {
s.data.notes.push({
...note, id, title: note.title + ' (copy)',
createdAt: Date.now(), updatedAt: Date.now(),
});
s.activeNoteId = id;
});
});
moveBtn.addEventListener('click', showMoveMenu);
delBtn.addEventListener('click', () => {
(0, state_1.setState)(s => {
const idx = s.data.notes.findIndex(n => n.id === s.activeNoteId);
if (idx >= 0) {
s.data.trash.push(s.data.notes[idx]);
s.data.notes.splice(idx, 1);
s.activeNoteId = null;
}
});
});
restoreBtn.addEventListener('click', () => {
(0, state_1.setState)(s => {
const idx = s.data.trash.findIndex(n => n.id === s.activeNoteId);
if (idx >= 0) {
const note = s.data.trash[idx];
s.data.notes.push(note);
s.data.trash.splice(idx, 1);
s.activeNotebookId = note.notebookId || 'all';
}
});
});
permDelBtn.addEventListener('click', () => {
if (!confirm('Permanently delete this note?'))
return;
(0, state_1.setState)(s => {
const idx = s.data.trash.findIndex(n => n.id === s.activeNoteId);
if (idx >= 0) {
s.data.trash.splice(idx, 1);
s.activeNoteId = null;
}
});
});
// Drag and drop
const sidebar = document.getElementById('sidebar-pane');
sidebar.addEventListener('dragover', (e) => { e.preventDefault(); });
sidebar.addEventListener('drop', (e) => {
e.preventDefault();
const item = e.target.closest('.sidebar-item');
const nbId = item?.dataset?.notebookId;
if (!nbId)
return;
const note = (0, state_1.getNote)((0, state_1.getState)().activeNoteId || '');
if (!note)
return;
(0, state_1.setState)(s => {
const n = s.data.notes.find(n => n.id === note.id);
if (n)
n.notebookId = nbId;
});
});
}
// ── Toolbar commands ──
function handleToolbarCommand(cmd) {
if (!editorEl)
return;
switch (cmd) {
case 'bold':
case 'italic':
case 'underline':
case 'strikeThrough':
document.execCommand(cmd, false);
break;
case 'code':
wrapInlineTag('code');
break;
case 'h1':
case 'h2':
case 'h3':
formatBlock(cmd.toUpperCase());
break;
case 'ul':
formatBlock('ul');
break;
case 'ol':
formatBlock('ol');
break;
case 'checklist':
insertChecklistItem();
break;
case 'blockquote':
formatBlock('blockquote');
break;
case 'hr':
document.execCommand('insertHTML', false, '<hr>');
break;
}
saveEditorContent();
updateToolbarState();
}
function formatBlock(tag) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
let block = sel.getRangeAt(0).startContainer;
while (block && block !== editorEl && block.nodeType !== 1)
block = block.parentNode;
if (block && block !== editorEl && block.nodeName === 'BODY')
block = editorEl;
const tagName = block?.tagName;
if (tagName === tag.toUpperCase() && block !== editorEl) {
unwrapElement(block);
}
else if (['H1', 'H2', 'H3', 'BLOCKQUOTE', 'UL', 'OL'].includes(tag.toUpperCase())) {
document.execCommand('formatBlock', false, tag);
}
}
function unwrapElement(el) {
const parent = el.parentNode;
if (!parent)
return;
while (el.firstChild)
parent.insertBefore(el.firstChild, el);
parent.removeChild(el);
}
function wrapInlineTag(tag) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount || sel.isCollapsed)
return;
const range = sel.getRangeAt(0);
const text = range.toString();
if (!text)
return;
let parent = range.commonAncestorContainer;
while (parent && parent.nodeType === 3)
parent = parent.parentNode;
if (!parent)
return;
if (parent.tagName === tag.toUpperCase()) {
unwrapElement(parent);
return;
}
document.execCommand('insertHTML', false, `<${tag}>${text}</${tag}>`);
}
function insertChecklistItem() {
const sel = window.getSelection();
if (!sel)
return;
const block = getCurrentBlock();
if (block && block.classList?.contains('checklist-parent')) {
unwrapElement(block);
return;
}
document.execCommand('formatBlock', false, 'p');
const checkbox = document.createElement('span');
checkbox.className = 'checklist-checkbox';
checkbox.contentEditable = 'false';
checkbox.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
checkbox.classList.toggle('checked');
const li = checkbox.closest('li');
if (li)
li.classList.toggle('checked');
scheduleSave();
});
const textSpan = document.createElement('span');
textSpan.className = 'checklist-text';
textSpan.contentEditable = 'true';
const li = document.createElement('li');
li.className = 'checklist-item';
li.contentEditable = 'true';
li.appendChild(checkbox);
li.appendChild(textSpan);
const ul = document.createElement('ul');
ul.className = 'checklist-parent';
ul.appendChild(li);
if (!editorEl)
return;
editorEl.appendChild(ul);
const range = document.createRange();
range.selectNodeContents(textSpan);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
textSpan.focus();
}
function getCurrentBlock() {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return null;
let node = sel.anchorNode;
while (node && node !== editorEl && node.nodeName !== 'BODY') {
if (node.nodeType === 1) {
const el = node;
if (['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'DIV'].includes(el.tagName))
return el;
}
node = node.parentNode;
}
return null;
}
// ── Keyboard handling ──
function handleEditorKeyDown(e) {
if (!editorEl)
return;
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
const block = getCurrentBlock();
if (!block)
return;
if (block.tagName === 'LI' && block.classList.contains('checklist-item')) {
const textSpan = block.querySelector('.checklist-text');
if (textSpan && textSpan.textContent.trim() === '') {
e.preventDefault();
unwrapElement(block);
}
else {
e.preventDefault();
insertChecklistItem();
scheduleSave();
}
return;
}
if (block.tagName === 'LI' && block.textContent?.trim() === '') {
e.preventDefault();
const ul = block.parentElement;
if (ul)
unwrapElement(ul);
return;
}
return;
}
if (e.key === 'Tab') {
e.preventDefault();
return;
}
if (e.key === ' ' && handleMarkdownShortcut()) {
e.preventDefault();
scheduleSave();
return;
}
scheduleSave();
}
function handleMarkdownShortcut() {
if (!editorEl)
return false;
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const block = getCurrentBlock();
if (!block || block.tagName !== 'P')
return false;
const text = block.textContent || '';
const selOffset = getCaretOffsetInBlock(block);
const prefix = text.substring(0, selOffset);
if (prefix === '# ') {
block.innerHTML = text.substring(2);
block.outerHTML = `<h1>${block.innerHTML || ' '}</h1>`;
scheduleSave();
return true;
}
if (prefix === '## ') {
block.innerHTML = text.substring(3);
block.outerHTML = `<h2>${block.innerHTML || ' '}</h2>`;
scheduleSave();
return true;
}
if (prefix === '### ') {
block.innerHTML = text.substring(4);
block.outerHTML = `<h3>${block.innerHTML || ' '}</h3>`;
scheduleSave();
return true;
}
if (prefix === '- ') {
const ul = document.createElement('ul');
const li = document.createElement('li');
li.textContent = text.substring(2);
ul.appendChild(li);
block.replaceWith(ul);
scheduleSave();
return true;
}
if (prefix === '[] ') {
const ul = document.createElement('ul');
ul.className = 'checklist-parent';
const li = document.createElement('li');
li.className = 'checklist-item';
const cb = document.createElement('span');
cb.className = 'checklist-checkbox';
cb.contentEditable = 'false';
cb.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
cb.classList.toggle('checked');
li.classList.toggle('checked');
scheduleSave();
});
const ts = document.createElement('span');
ts.className = 'checklist-text';
ts.textContent = text.substring(3);
li.appendChild(cb);
li.appendChild(ts);
ul.appendChild(li);
block.replaceWith(ul);
setTimeout(() => ts.focus(), 10);
scheduleSave();
return true;
}
if (prefix === '> ') {
block.innerHTML = text.substring(2);
block.outerHTML = `<blockquote>${block.innerHTML}</blockquote>`;
scheduleSave();
return true;
}
return false;
}
function getCaretOffsetInBlock(block) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return 0;
const range = sel.getRangeAt(0);
const preRange = document.createRange();
preRange.selectNodeContents(block);
preRange.setEnd(range.startContainer, range.startOffset);
return preRange.toString().length;
}
// ── Paste sanitization ──
function handlePaste(e) {
e.preventDefault();
const html = e.clipboardData?.getData('text/html');
const plain = e.clipboardData?.getData('text/plain');
let content;
if (html) {
content = sanitizeHtml(html);
}
else if (plain) {
content = escapeHtml(plain).replace(/\n/g, '<br>');
}
else
return;
document.execCommand('insertHTML', false, content);
scheduleSave();
}
function sanitizeHtml(html) {
const allowed = new Set(['b', 'i', 'u', 's', 'strike', 'del', 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'div', 'ul', 'ol', 'li', 'blockquote', 'hr', 'span', 'a', 'strong', 'em']);
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const body = doc.body;
const toRemove = [];
body.querySelectorAll('*').forEach(el => {
if (!allowed.has(el.tagName.toLowerCase()))
toRemove.push(el);
Array.from(el.attributes).forEach(attr => {
if (!['href', 'target', 'class', 'style'].includes(attr.name))
el.removeAttribute(attr.name);
});
if (el.tagName.toLowerCase() === 'a') {
const href = el.getAttribute('href');
if (href && !href.startsWith('http'))
el.removeAttribute('href');
}
});
for (const node of toRemove) {
const parent = node.parentNode;
while (node.firstChild)
parent?.insertBefore(node.firstChild, node);
parent?.removeChild(node);
}
return body.innerHTML;
}
// ── Auto-save ──
function scheduleSave() {
(0, state_1.markUnsaved)();
if (_saveTimer)
clearTimeout(_saveTimer);
_saveTimer = setTimeout(() => { (0, state_1.setSaving)(); saveEditorContent(); }, constants_1.CONSTANTS.autosaveDebounce);
}
function saveEditorContent() {
const state = (0, state_1.getState)();
const note = (0, state_1.getNote)(state.activeNoteId || '');
if (!note || !editorEl || !titleEl)
return;
note.content = editorEl.innerHTML;
note.title = titleEl.value;
note.updatedAt = Date.now();
}
// ── Toolbar state ──
function updateToolbarState() {
if (!toolbarEl)
return;
toolbarEl.querySelectorAll('button[data-cmd]').forEach(btn => {
const cmd = btn.dataset?.cmd;
let active = false;
if (!cmd) {
btn.classList.remove('active');
return;
}
if (cmd === 'bold')
active = document.queryCommandState('bold');
else if (cmd === 'italic')
active = document.queryCommandState('italic');
else if (cmd === 'underline')
active = document.queryCommandState('underline');
else if (cmd === 'strikeThrough')
active = document.queryCommandState('strikeThrough');
else if (['h1', 'h2', 'h3'].includes(cmd))
active = document.queryCommandValue('formatBlock') === cmd.toUpperCase();
else if (cmd === 'ul')
active = document.queryCommandState('insertUnorderedList');
else if (cmd === 'ol')
active = document.queryCommandState('insertOrderedList');
else if (cmd === 'blockquote')
active = document.queryCommandValue('formatBlock') === 'BLOCKQUOTE';
else if (cmd === 'code') {
let n = window.getSelection()?.anchorNode;
while (n && n !== editorEl) {
if (n.nodeType === 1 && n.tagName === 'CODE') {
active = true;
break;
}
n = n.parentNode;
}
}
else if (cmd === 'checklist')
active = getCurrentBlock()?.classList?.contains('checklist-item') || false;
btn.classList.toggle('active', active);
});
}
// ── Move menu ──
function showMoveMenu() {
const existing = document.querySelector('.move-dropdown');
if (existing)
existing.remove();
const state = (0, state_1.getState)();
if (state.data.notebooks.length === 0)
return;
const menu = document.createElement('div');
menu.className = 'move-dropdown';
for (const nb of state.data.notebooks) {
const item = document.createElement('div');
item.className = 'move-dropdown-item';
item.textContent = nb.name;
item.addEventListener('click', () => {
(0, state_1.setState)(s => {
const n = s.data.notes.find(n => n.id === s.activeNoteId);
if (n)
n.notebookId = nb.id;
s.activeNotebookId = nb.id;
});
menu.remove();
});
menu.appendChild(item);
}
const rect = moveBtn?.getBoundingClientRect();
if (rect) {
menu.style.left = rect.left + 'px';
menu.style.top = (rect.bottom + 4) + 'px';
}
document.body.appendChild(menu);
setTimeout(() => {
document.addEventListener('click', (e) => { if (!menu.contains(e.target))
menu.remove(); }, { once: true });
}, 10);
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"ThinkingCap-Qwen3.6-27B-GGUF","provider":"brigid","persona":"typescript","sessionId":"088c3395-359d-4aed-81d5-4424146af4de","tokensIn":7161640,"tokensOut":56054,"tokensTotal":7217694,"cost":0,"turns":116,"toolCalls":116,"failedToolCalls":12,"timestamp":"2026-08-03T20:48:51.484Z"} -->