Notes
thinkingcap qwen3.6 27b · basic

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 40,077 bytes · SHA-256 7183833287d5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NoteFlow — Notes App</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg-primary:#ffffff;--bg-secondary:#f8f9fa;--bg-tertiary:#eef1f5;--bg-hover:#e8eaed;
--text-primary:#1a1a2e;--text-secondary:#555770;--text-tertiary:#8b8da3;
--accent:#6c5ce7;--accent-light:#a29bfe;--accent-bg:rgba(108,92,231,0.08);
--border:#e2e4e9;--shadow:0 2px 12px rgba(0,0,0,0.06);
--sidebar-width:280px;--toolbar-height:52px;
--radius:10px;--transition:0.2s ease;
--tag-colors:#6c5ce7,#00b894,#e17055,#0984e3,#fdcb6e,#d63031,#e84393,#00cec9;
}
[data-theme="dark"]{
--bg-primary:#1a1a2e;--bg-secondary:#16213e;--bg-tertiary:#1f2b47;--bg-hover:#253352;
--text-primary:#e8e8f0;--text-secondary:#9a9ab0;--text-tertiary:#6b6d80;
--accent:#a29bfe;--accent-light:#6c5ce7;--accent-bg:rgba(162,155,254,0.12);
--border:#2a3555;--shadow:0 2px 12px rgba(0,0,0,0.3);
}
html{font-size:15px}
body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:var(--bg-primary);color:var(--text-primary);overflow:hidden;height:100vh;transition:background var(--transition),color var(--transition)}
/* Scrollbar */
::-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)}
/* Layout */
.app{display:flex;height:100vh;position:relative}
/* Sidebar */
.sidebar{width:var(--sidebar-width);min-width:var(--sidebar-width);background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;transition:transform 0.3s ease,opacity 0.3s ease;z-index:100}
.sidebar.collapsed{transform:translateX(calc(-1 * var(--sidebar-width)));position:absolute;height:100%;opacity:0;pointer-events:none}
.sidebar-header{padding:20px 20px 12px;display:flex;align-items:center;justify-content:space-between}
.logo{display:flex;align-items:center;gap:10px;font-weight:700;font-size:1.2rem;color:var(--accent)}
.logo svg{width:28px;height:28px}
.sidebar-actions{display:flex;gap:4px}
.icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all var(--transition)}
.icon-btn:hover{background:var(--bg-hover);color:var(--text-primary)}
/* Search */
.search-box{margin:0 16px 12px;position:relative}
.search-box input{width:100%;padding:9px 14px 9px 38px;border:1.5px solid var(--border);border-radius:var(--radius);background:var(--bg-primary);color:var(--text-primary);font-size:0.88rem;font-family:inherit;outline:none;transition:border-color var(--transition)}
.search-box input:focus{border-color:var(--accent)}
.search-box svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--text-tertiary);width:16px;height:16px}
/* Folders */
.folders-section{padding:4px 12px;flex-shrink:0}
.section-label{font-size:0.7rem;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:var(--text-tertiary);padding:8px 8px 6px;display:flex;align-items:center;justify-content:space-between}
.folder-item{display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;cursor:pointer;font-size:0.88rem;color:var(--text-secondary);transition:all var(--transition);position:relative}
.folder-item:hover{background:var(--bg-hover);color:var(--text-primary)}
.folder-item.active{background:var(--accent-bg);color:var(--accent);font-weight:500}
.folder-item .count{margin-left:auto;font-size:0.72rem;background:var(--bg-tertiary);padding:1px 7px;border-radius:10px;color:var(--text-tertiary)}
.folder-item svg{width:16px;height:16px;flex-shrink:0}
/* Notes List */
.notes-list-section{flex:1;display:flex;flex-direction:column;overflow:hidden;margin-top:4px}
.notes-list{flex:1;overflow-y:auto;padding:0 8px}
.note-item{padding:12px 14px;border-radius:var(--radius);cursor:pointer;margin-bottom:2px;transition:all var(--transition);position:relative}
.note-item:hover{background:var(--bg-hover)}
.note-item.active{background:var(--accent-bg);border-left:3px solid var(--accent);margin-left:-3px;padding-left:17px}
.note-item-title{font-size:0.9rem;font-weight:500;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text-primary)}
.note-item-preview{font-size:0.78rem;color:var(--text-tertiary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.note-item-date{font-size:0.68rem;color:var(--text-tertiary);margin-top:4px}
.note-item-tags{display:flex;gap:4px;margin-top:5px;flex-wrap:wrap}
.mini-tag{font-size:0.62rem;padding:1px 6px;border-radius:4px;font-weight:500}
/* Main Content */
.main{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bg-primary)}
/* Toolbar */
.toolbar{height:var(--toolbar-height);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 20px;gap:4px;flex-shrink:0}
.toolbar-group{display:flex;align-items:center;gap:2px;padding:0 6px;border-right:1px solid var(--border)}
.toolbar-group:last-child{border-right:none}
.tool-btn{width:34px;height:34px;border:none;background:transparent;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all var(--transition);font-size:0.85rem;font-weight:600}
.tool-btn:hover{background:var(--bg-hover);color:var(--text-primary)}
.tool-btn.active{background:var(--accent-bg);color:var(--accent)}
.tool-btn svg{width:18px;height:18px}
.toolbar-spacer{flex:1}
.note-title-input{font-size:1.6rem;font-weight:700;border:none;background:transparent;color:var(--text-primary);outline:none;width:100%;padding:24px 32px 8px;font-family:inherit;letter-spacing:-0.02em}
.note-meta{display:flex;align-items:center;gap:12px;padding:0 32px 8px;font-size:0.75rem;color:var(--text-tertiary)}
/* Editor */
.editor-container{flex:1;overflow-y:auto;padding:0 32px 100px}
.editor{min-height:100%;outline:none;font-size:0.95rem;line-height:1.75;color:var(--text-primary);font-family:'Inter',sans-serif}
.editor h1{font-size:1.8rem;font-weight:700;margin:24px 0 12px;letter-spacing:-0.02em}
.editor h2{font-size:1.4rem;font-weight:600;margin:20px 0 10px}
.editor h3{font-size:1.15rem;font-weight:600;margin:16px 0 8px}
.editor p{margin-bottom:8px}
.editor ul,.editor ol{padding-left:24px;margin-bottom:8px}
.editor blockquote{border-left:3px solid var(--accent);padding-left:16px;color:var(--text-secondary);margin:12px 0;font-style:italic}
.editor code{background:var(--bg-tertiary);padding:2px 6px;border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:0.85em}
.editor pre{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin:12px 0;overflow-x:auto}
.editor pre code{background:none;padding:0}
.editor hr{border:none;border-top:2px solid var(--border);margin:20px 0}
.editor a{color:var(--accent)}
/* Empty State */
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-tertiary);gap:16px;padding:40px;text-align:center}
.empty-state svg{width:80px;height:80px;opacity:0.3}
.empty-state h2{font-size:1.3rem;color:var(--text-secondary)}
.empty-state p{font-size:0.9rem;max-width:360px;line-height:1.6}
/* Tags Panel */
.tags-panel{padding:8 12px;border-top:1px solid var(--border);flex-shrink:0}
.tag-item{display:flex;align-items:center;gap:8px;padding:5px 10px;border-radius:6px;cursor:pointer;font-size:0.82rem;color:var(--text-secondary);transition:all var(--transition)}
.tag-item:hover{background:var(--bg-hover)}
.tag-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
/* Modal */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:1000;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity 0.2s ease}
.modal-overlay.active{opacity:1;pointer-events:auto}
.modal{background:var(--bg-primary);border-radius:16px;padding:28px;width:90%;max-width:420px;box-shadow:0 20px 60px rgba(0,0,0,0.2);transform:scale(0.95);transition:transform 0.2s ease}
.modal-overlay.active .modal{transform:scale(1)}
.modal h3{font-size:1.1rem;margin-bottom:16px}
.modal input,.modal select{width:100%;padding:10px 14px;border:1.5px solid var(--border);border-radius:8px;background:var(--bg-secondary);color:var(--text-primary);font-size:0.9rem;font-family:inherit;outline:none;margin-bottom:12px}
.modal input:focus{border-color:var(--accent)}
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}
.btn{padding:9px 20px;border-radius:8px;border:none;font-size:0.85rem;font-weight:500;cursor:pointer;font-family:inherit;transition:all var(--transition)}
.btn-primary{background:var(--accent);color:#fff}
.btn-primary:hover{opacity:0.9}
.btn-secondary{background:var(--bg-tertiary);color:var(--text-secondary)}
.btn-secondary:hover{background:var(--bg-hover)}
/* Context Menu */
.context-menu{position:fixed;background:var(--bg-primary);border:1px solid var(--border);border-radius:10px;padding:6px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:2000;min-width:180px;display:none}
.context-menu.show{display:block}
.ctx-item{padding:8px 12px;border-radius:6px;font-size:0.84rem;color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;gap:10px;transition:all var(--transition)}
.ctx-item:hover{background:var(--bg-hover);color:var(--text-primary)}
.ctx-item.danger{color:#e74c3c}
.ctx-divider{height:1px;background:var(--border);margin:4px 8px}
/* Toast */
.toast-container{position:fixed;bottom:24px;right:24px;z-index:3000;display:flex;flex-direction:column;gap:8px}
.toast{padding:12px 20px;border-radius:10px;background:var(--bg-primary);border:1px solid var(--border);box-shadow:0 8px 30px rgba(0,0,0,0.15);font-size:0.85rem;color:var(--text-primary);display:flex;align-items:center;gap:10px;animation:toastIn 0.3s ease}
.toast.out{animation:toastOut 0.3s ease forwards}
@keyframes toastIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
@keyframes toastOut{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(20px)}}
/* Responsive */
@media(max-width:768px){
.sidebar{position:absolute;height:100%;box-shadow:var(--shadow)}
.sidebar.collapsed{transform:translateX(calc(-1 * var(--sidebar-width)))}
.note-title-input,.editor-container{padding-left:20px;padding-right:20px}
.note-meta{padding-left:20px;padding-right:20px}
.toolbar{padding:0 12px}
}
/* Word count bar */
.status-bar{height:28px;border-top:1px solid var(--border);display:flex;align-items:center;padding:0 20px;font-size:0.7rem;color:var(--text-tertiary);gap:16px;flex-shrink:0}
/* Checkbox in editor */
.editor input[type="checkbox"]{margin-right:6px;accent-color:var(--accent)}
/* Image placeholder */
.editor img{max-width:100%;border-radius:8px;margin:8px 0}
/* Print styles */
@media print{.sidebar,.toolbar,.status-bar{display:none}.main{width:100%}}
</style>
</head>
<body>
<div class="app" id="app">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">
<svg viewBox="0 0 28 28" fill="none"><rect x="3" y="3" width="22" height="22" rx="6" fill="currentColor"/><path d="M9 14l3 3 7-7" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
NoteFlow
</div>
<div class="sidebar-actions">
<button class="icon-btn" onclick="toggleTheme()" title="Toggle theme" id="themeBtn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
</button>
<button class="icon-btn" onclick="toggleSidebar()" title="Toggle sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
</button>
</div>
</div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" placeholder="Search notes..." id="searchInput" oninput="filterNotes()">
</div>
<div class="folders-section">
<div class="section-label">
<span>Folders</span>
<button class="icon-btn" onclick="showAddFolderModal()" style="width:24px;height:24px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
</div>
<div id="foldersList"></div>
</div>
<div class="notes-list-section">
<div class="section-label"><span>Notes</span><span id="noteCount"></span></div>
<div class="notes-list" id="notesList"></div>
</div>
<div class="tags-panel" style="padding:8px 12px;border-top:1px solid var(--border)">
<div class="section-label"><span>Tags</span></div>
<div id="tagsList"></div>
</div>
</aside>
<!-- Main -->
<main class="main" id="mainContent">
<div class="toolbar" id="toolbar">
<button class="icon-btn" onclick="toggleSidebar()" title="Toggle sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
</button>
<div class="toolbar-group">
<button class="tool-btn" onclick="execCmd('bold')" title="Bold"><b>B</b></button>
<button class="tool-btn" onclick="execCmd('italic')" title="Italic"><i>I</i></button>
<button class="tool-btn" onclick="execCmd('underline')" title="Underline"><u>U</u></button>
<button class="tool-btn" onclick="execCmd('strikeThrough')" title="Strikethrough"><s>S</s></button>
</div>
<div class="toolbar-group">
<select onchange="execCmd('formatBlock',this.value);this.selectedIndex=0" style="padding:6px 8px;border:1.5px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);font-size:0.8rem;font-family:inherit;cursor:pointer">
<option value="">Heading</option>
<option value="h1">Heading 1</option>
<option value="h2">Heading 2</option>
<option value="h3">Heading 3</option>
<option value="p">Paragraph</option>
</select>
</div>
<div class="toolbar-group">
<button class="tool-btn" onclick="execCmd('insertUnorderedList')" title="Bullet list">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="5" cy="6" r="1.5" fill="currentColor"/><circle cx="5" cy="12" r="1.5" fill="currentColor"/><circle cx="5" cy="18" r="1.5" fill="currentColor"/></svg>
</button>
<button class="tool-btn" onclick="execCmd('insertOrderedList')" title="Numbered list">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="10" y1="6" x2="20" y2="6"/><line x1="10" y1="12" x2="20" y2="12"/><line x1="10" y1="18" x2="20" y2="18"/><text x="3" y="8" font-size="7" fill="currentColor" stroke="none">1</text><text x="3" y="14" font-size="7" fill="currentColor" stroke="none">2</text><text x="3" y="20" font-size="7" fill="currentColor" stroke="none">3</text></svg>
</button>
<button class="tool-btn" onclick="execCmd('formatBlock','blockquote')" title="Quote">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/></svg>
</button>
</div>
<div class="toolbar-group">
<button class="tool-btn" onclick="insertCodeBlock()" title="Code block">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
</button>
<button class="tool-btn" onclick="insertHR()" title="Horizontal rule">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/></svg>
</button>
<button class="tool-btn" onclick="insertCheckbox()" title="Checkbox">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="3"/><polyline points="9 12 11 14 15 10"/></svg>
</button>
</div>
<div class="toolbar-spacer"></div>
<button class="tool-btn" onclick="showTagModal()" title="Add tags">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
</button>
<button class="tool-btn" onclick="showNoteContextMenu(event)" title="More options">
<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg>
</button>
</div>
<div id="editorArea">
<input type="text" class="note-title-input" id="noteTitleInput" placeholder="Untitled note..." oninput="onTitleChange()">
<div class="note-meta" id="noteMeta"></div>
<div class="editor-container">
<div class="editor" contenteditable="true" id="editor" oninput="onEditorChange()" onkeydown="handleEditorKeys(event)"></div>
</div>
</div>
<div class="status-bar" id="statusBar">
<span id="wordCount">0 words</span>
<span id="charCount">0 characters</span>
<span id="lastSaved">Not saved yet</span>
</div>
</main>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="ctx-item" onclick="duplicateNote()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
Duplicate
</div>
<div class="ctx-item" onclick="changeNoteFolder()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
Move to folder...
</div>
<div class="ctx-item" onclick="togglePinNote()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
<span id="pinLabel">Pin note</span>
</div>
<div class="ctx-divider"></div>
<div class="ctx-item danger" onclick="deleteCurrentNote()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
Delete note
</div>
</div>
<!-- Add Folder Modal -->
<div class="modal-overlay" id="folderModal">
<div class="modal">
<h3>New Folder</h3>
<input type="text" id="newFolderName" placeholder="Folder name..." onkeydown="if(event.key==='Enter')addFolder()">
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal('folderModal')">Cancel</button>
<button class="btn btn-primary" onclick="addFolder()">Create</button>
</div>
</div>
</div>
<!-- Tag Modal -->
<div class="modal-overlay" id="tagModal">
<div class="modal">
<h3>Edit Tags</h3>
<input type="text" id="newTagInput" placeholder="Add a tag..." onkeydown="if(event.key==='Enter')addTag()">
<div id="currentTagsDisplay" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px"></div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal('tagModal')">Done</button>
</div>
</div>
</div>
<!-- Move Note Modal -->
<div class="modal-overlay" id="moveNoteModal">
<div class="modal">
<h3>Move to Folder</h3>
<select id="moveFolderSelect"></select>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal('moveNoteModal')">Cancel</button>
<button class="btn btn-primary" onclick="doMoveNote()">Move</button>
</div>
</div>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
// ===== DATA =====
const DEFAULT_FOLDERS = [
{ id: 'all', name: 'All Notes', icon: 'notes', system: true },
{ id: 'personal', name: 'Personal', icon: 'user' },
{ id: 'work', name: 'Work', icon: 'briefcase' },
{ id: 'ideas', name: 'Ideas', icon: 'bulb' },
{ id: 'journal', name: 'Journal', icon: 'book' }
];
const DEFAULT_NOTES = [
{
id: '1', title: 'Welcome to NoteFlow ✨', folderId: 'personal', pinned: true,
content: '<h1>Welcome to NoteFlow!</h1><p>This is your new notes application. Here are some things you can do:</p><ul><li>Create and organize notes into folders</li><li>Format text with the toolbar above</li><li>Add tags to categorize your notes</li><li>Search through all your notes instantly</li><li>Toggle dark mode for comfortable reading</li></ul><blockquote>NoteFlow saves everything automatically to your browser\'s local storage.</blockquote><h2>Getting Started</h2><p>Select a folder from the sidebar, then click on any note or create a new one. Your changes are saved as you type!</p><hr><p><strong>Tip:</strong> Try using keyboard shortcuts like <code>Ctrl+B</code> for bold, <code>Ctrl+I</code> for italic.</p>',
tags: ['welcome', 'getting-started'], created: Date.now() - 86400000 * 2, updated: Date.now() - 3600000
},
{
id: '2', title: 'Project Ideas Q1 2025', folderId: 'ideas', pinned: false,
content: '<h2>App Ideas</h2><ol><li>Habit tracker with streak visualization</li><li>Recipe organizer with meal planning</li><li>Pomodoro timer with ambient sounds</li></ol><h2>Features to explore</h2><ul><li>AI-powered note summarization</li><li>Cross-device sync via WebDAV</li><li>Markdown export support</li></ul>',
tags: ['ideas', 'planning'], created: Date.now() - 86400000, updated: Date.now() - 7200000
},
{
id: '3', title: 'Meeting Notes — Design Review', folderId: 'work', pinned: false,
content: '<h2>Attendees</h2><p>Sarah, Mike, Alex, Jordan</p><h2>Key Decisions</h2><ul><li>Switch to a card-based layout for the dashboard</li><li>Implement dark mode by end of sprint</li><li>Reduce color palette to 5 primary colors</li></ul><h2>Action Items</h2><p>☐ Sarah: Create wireframes by Friday<br>☑ Mike: Set up design tokens<br>☐ Alex: Review accessibility compliance</p>',
tags: ['work', 'meetings'], created: Date.now() - 86400000 * 3, updated: Date.now() - 86400000
},
{
id: '4', title: 'Weekly Journal — Week 12', folderId: 'journal', pinned: false,
content: '<h2>Monday</h2><p>Started the week with a productive morning routine. Felt energized and focused.</p><h2>Wednesday</h2><p>Had an interesting conversation about design systems at lunch. Need to read more about atomic design principles.</p><h2>Friday</h2><p>Wrapped up the sprint ahead of schedule. Time for some well-deserved rest!</p>',
tags: ['journal', 'weekly'], created: Date.now() - 86400000 * 5, updated: Date.now() - 86400000 * 2
}
];
let state = {
folders: [],
notes: [],
activeFolderId: 'all',
activeNoteId: null,
searchQuery: '',
theme: 'light'
};
// ===== INIT =====
function init() {
loadState();
if (!state.folders.length) state.folders = JSON.parse(JSON.stringify(DEFAULT_FOLDERS));
if (!state.notes.length) state.notes = JSON.parse(JSON.stringify(DEFAULT_NOTES));
document.documentElement.setAttribute('data-theme', state.theme);
renderFolders();
renderNotesList();
renderTags();
updateNoteCount();
if (state.activeNoteId) {
selectNote(state.activeNoteId);
} else {
showEmptyState();
}
}
function loadState() {
try {
const saved = localStorage.getItem('noteflow_state');
if (saved) {
const parsed = JSON.parse(saved);
state.folders = parsed.folders || [];
state.notes = parsed.notes || [];
state.activeFolderId = parsed.activeFolderId || 'all';
state.activeNoteId = parsed.activeNoteId || null;
state.theme = parsed.theme || 'light';
}
} catch(e) { console.warn('Failed to load state', e); }
}
function saveState() {
try {
localStorage.setItem('noteflow_state', JSON.stringify({
folders: state.folders,
notes: state.notes,
activeFolderId: state.activeFolderId,
activeNoteId: state.activeNoteId,
theme: state.theme
}));
} catch(e) { console.warn('Failed to save state', e); }
}
// ===== RENDERING =====
function renderFolders() {
const container = document.getElementById('foldersList');
container.innerHTML = state.folders.map(f => {
if (f.system) return '';
const count = state.notes.filter(n => n.folderId === f.id).length;
const icons = { user: '👤', briefcase: '💼', bulb: '💡', book: '📖', default: '📁' };
const icon = icons[f.icon] || icons.default;
return `<div class="folder-item ${state.activeFolderId === f.id ? 'active' : ''}" onclick="selectFolder('${f.id}')">
<span>${icon}</span>
<span>${escHtml(f.name)}</span>
<span class="count">${count}</span>
</div>`;
}).join('');
}
function renderNotesList() {
const container = document.getElementById('notesList');
let notes = getFilteredNotes();
// Sort: pinned first, then by updated date
notes.sort((a, b) => {
if (a.pinned && !b.pinned) return -1;
if (!a.pinned && b.pinned) return 1;
return b.updated - a.updated;
});
container.innerHTML = notes.map(n => {
const preview = stripHtml(n.content).substring(0, 80);
const dateStr = formatDate(n.updated);
const tagsHtml = n.tags.slice(0, 3).map((t, i) =>
`<span class="mini-tag" style="background:var(--tag-colors,${getTagColor(t)});color:#fff">${escHtml(t)}</span>`
).join('');
return `<div class="note-item ${state.activeNoteId === n.id ? 'active' : ''}" onclick="selectNote('${n.id}')" oncontextmenu="showNoteItemContextMenu(event,'${n.id}')">
<div class="note-item-title">${n.pinned ? '📌 ' : ''}${escHtml(n.title || 'Untitled')}</div>
<div class="note-item-preview">${escHtml(preview)}</div>
${tagsHtml ? `<div class="note-item-tags">${tagsHtml}</div>` : ''}
<div class="note-item-date">${dateStr}</div>
</div>`;
}).join('');
if (!notes.length) {
container.innerHTML = '<div style="text-align:center;padding:40px 20px;color:var(--text-tertiary);font-size:0.85rem">No notes found</div>';
}
}
function renderTags() {
const container = document.getElementById('tagsList');
const allTags = {};
state.notes.forEach(n => n.tags.forEach(t => { allTags[t] = (allTags[t] || 0) + 1; }));
container.innerHTML = Object.entries(allTags).map(([tag, count]) =>
`<div class="tag-item" onclick="filterByTag('${escHtml(tag)}')">
<span class="tag-dot" style="background:${getTagColor(tag)}"></span>
<span>${escHtml(tag)}</span>
<span style="margin-left:auto;font-size:0.7rem;color:var(--text-tertiary)">${count}</span>
</div>`
).join('');
}
function updateNoteCount() {
const count = getFilteredNotes().length;
document.getElementById('noteCount').textContent = `${count} note${count !== 1 ? 's' : ''}`;
}
// ===== NOTE OPERATIONS =====
function getFilteredNotes() {
let notes = state.notes;
if (state.activeFolderId !== 'all') {
notes = notes.filter(n => n.folderId === state.activeFolderId);
}
if (state.searchQuery) {
const q = state.searchQuery.toLowerCase();
notes = notes.filter(n =>
(n.title || '').toLowerCase().includes(q) ||
stripHtml(n.content).toLowerCase().includes(q) ||
n.tags.some(t => t.toLowerCase().includes(q))
);
}
return notes;
}
function selectNote(id) {
state.activeNoteId = id;
const note = state.notes.find(n => n.id === id);
if (!note) { showEmptyState(); return; }
document.getElementById('noteTitleInput').value = note.title || '';
document.getElementById('editor').innerHTML = note.content || '';
updateNoteMeta(note);
updateWordCount();
renderNotesList();
saveState();
}
function selectFolder(id) {
state.activeFolderId = id;
state.searchQuery = '';
document.getElementById('searchInput').value = '';
renderFolders();
renderNotesList();
updateNoteCount();
saveState();
}
function createNewNote() {
const folderId = state.activeFolderId === 'all' ? (state.folders[1]?.id || 'personal') : state.activeFolderId;
const note = {
id: generateId(),
title: '',
content: '',
folderId,
tags: [],
pinned: false,
created: Date.now(),
updated: Date.now()
};
state.notes.unshift(note);
selectNote(note.id);
document.getElementById('noteTitleInput').focus();
saveState();
renderFolders();
}
function deleteCurrentNote() {
if (!state.activeNoteId) return;
hideContextMenu();
state.notes = state.notes.filter(n => n.id !== state.activeNoteId);
state.activeNoteId = null;
showEmptyState();
renderNotesList();
renderFolders();
updateNoteCount();
saveState();
showToast('Note deleted');
}
function duplicateNote() {
if (!state.activeNoteId) return;
hideContextMenu();
const orig = state.notes.find(n => n.id === state.activeNoteId);
if (!orig) return;
const dup = { ...orig, id: generateId(), title: orig.title + ' (copy)', created: Date.now(), updated: Date.now() };
state.notes.unshift(dup);
selectNote(dup.id);
renderFolders();
saveState();
showToast('Note duplicated');
}
function togglePinNote() {
hideContextMenu();
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
note.pinned = !note.pinned;
document.getElementById('pinLabel').textContent = note.pinned ? 'Unpin note' : 'Pin note';
renderNotesList();
saveState();
showToast(note.pinned ? 'Note pinned' : 'Note unpinned');
}
function changeNoteFolder() {
hideContextMenu();
const select = document.getElementById('moveFolderSelect');
select.innerHTML = state.folders.filter(f => !f.system).map(f =>
`<option value="${f.id}" ${f.id === (state.notes.find(n=>n.id===state.activeNoteId)?.folderId||'') ? 'selected' : ''}>${escHtml(f.name)}</option>`
).join('');
openModal('moveNoteModal');
}
function doMoveNote() {
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
note.folderId = document.getElementById('moveFolderSelect').value;
closeModal('moveNoteModal');
renderNotesList();
renderFolders();
saveState();
showToast('Note moved');
}
// ===== EDITOR =====
function onTitleChange() {
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
note.title = document.getElementById('noteTitleInput').value;
note.updated = Date.now();
renderNotesList();
debouncedSave();
}
function onEditorChange() {
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
note.content = document.getElementById('editor').innerHTML;
note.updated = Date.now();
updateWordCount();
renderNotesList();
debouncedSave();
}
function handleEditorKeys(e) {
if (e.key === 'Tab') {
e.preventDefault();
execCmd('insertText', ' ');
}
}
function execCmd(cmd, val = null) {
document.execCommand(cmd, false, val);
document.getElementById('editor').focus();
}
function insertCodeBlock() {
const sel = window.getSelection();
if (sel.rangeCount) {
const text = sel.toString() || 'code here';
document.execCommand('insertHTML', false, `<pre><code>${escHtml(text)}</code></pre><p></p>`);
}
}
function insertHR() {
document.execCommand('insertHTML', false, '<hr>');
}
function insertCheckbox() {
const checked = Math.random() > 0.5 ? 'checked' : '';
document.execCommand('insertHTML', false, `<div><input type="checkbox" ${checked}> Task item</div>`);
}
// ===== FOLDER OPERATIONS =====
function showAddFolderModal() { openModal('folderModal'); setTimeout(() => document.getElementById('newFolderName').focus(), 100); }
function addFolder() {
const name = document.getElementById('newFolderName').value.trim();
if (!name) return;
state.folders.push({ id: generateId(), name, icon: 'default' });
document.getElementById('newFolderName').value = '';
closeModal('folderModal');
renderFolders();
saveState();
showToast(`Folder "${name}" created`);
}
// ===== TAG OPERATIONS =====
function showTagModal() {
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
renderCurrentTags(note);
openModal('tagModal');
setTimeout(() => document.getElementById('newTagInput').focus(), 100);
}
function renderCurrentTags(note) {
const container = document.getElementById('currentTagsDisplay');
container.innerHTML = note.tags.map(t =>
`<span class="mini-tag" style="background:${getTagColor(t)};color:#fff;padding:4px 10px;border-radius:6px;display:flex;align-items:center;gap:6px">
${escHtml(t)}
<span onclick="removeTag('${escHtml(t)}')" style="cursor:pointer;font-weight:bold">×</span>
</span>`
).join('');
}
function addTag() {
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
const tag = document.getElementById('newTagInput').value.trim().toLowerCase();
if (!tag || note.tags.includes(tag)) { document.getElementById('newTagInput').value = ''; return; }
note.tags.push(tag);
document.getElementById('newTagInput').value = '';
renderCurrentTags(note);
renderTags();
saveState();
}
function removeTag(tag) {
const note = state.notes.find(n => n.id === state.activeNoteId);
if (!note) return;
note.tags = note.tags.filter(t => t !== tag);
renderCurrentTags(note);
renderTags();
saveState();
}
function filterByTag(tag) {
state.searchQuery = tag;
document.getElementById('searchInput').value = tag;
renderNotesList();
updateNoteCount();
}
// ===== SEARCH =====
function filterNotes() {
state.searchQuery = document.getElementById('searchInput').value.trim();
renderNotesList();
updateNoteCount();
}
// ===== UI HELPERS =====
function showEmptyState() {
const editorArea = document.getElementById('editorArea');
editorArea.innerHTML = `
<div class="empty-state">
<svg viewBox="0 0 80 80" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="12" y="8" width="56" height="64" rx="8"/>
<line x1="22" y1="24" x2="58" y2="24"/><line x1="22" y1="34" x2="50" y2="34"/>
<line x1="22" y1="44" x2="54" y2="44"/><line x1="22" y1="54" x2="42" y2="54"/>
</svg>
<h2>No note selected</h2>
<p>Select a note from the sidebar or create a new one to get started.</p>
<button class="btn btn-primary" onclick="createNewNote()" style="margin-top:8px">+ New Note</button>
</div>`;
document.getElementById('statusBar').style.display = 'none';
}
function updateNoteMeta(note) {
const meta = document.getElementById('noteMeta');
const folder = state.folders.find(f => f.id === note.folderId);
meta.innerHTML = `
<span>📁 ${escHtml(folder?.name || 'Unknown')}</span>
<span>Created: ${formatDate(note.created)}</span>
<span>Modified: ${formatDate(note.updated)}</span>
`;
}
function updateWordCount() {
const text = stripHtml(document.getElementById('editor').innerHTML);
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
const chars = text.length;
document.getElementById('wordCount').textContent = `${words} word${words !== 1 ? 's' : ''}`;
document.getElementById('charCount').textContent = `${chars} character${chars !== 1 ? 's' : ''}`;
}
function updateLastSaved() {
document.getElementById('lastSaved').textContent = `Saved ${formatTime(Date.now())}`;
}
// ===== CONTEXT MENU =====
function showNoteContextMenu(e) {
e.preventDefault();
const note = state.notes.find(n => n.id === state.activeNoteId);
if (note) document.getElementById('pinLabel').textContent = note.pinned ? 'Unpin note' : 'Pin note';
showContextMenuAt(e.clientX, e.clientY);
}
function showNoteItemContextMenu(e, id) {
e.preventDefault();
e.stopPropagation();
selectNote(id);
const note = state.notes.find(n => n.id === id);
if (note) document.getElementById('pinLabel').textContent = note.pinned ? 'Unpin note' : 'Pin label';
showContextMenuAt(e.clientX, e.clientY);
}
function showContextMenuAt(x, y) {
const menu = document.getElementById('contextMenu');
menu.style.left = Math.min(x, window.innerWidth - 200) + 'px';
menu.style.top = Math.min(y, window.innerHeight - 250) + 'px';
menu.classList.add('show');
}
function hideContextMenu() {
document.getElementById('contextMenu').classList.remove('show');
}
document.addEventListener('click', () => hideContextMenu());
// ===== MODALS =====
function openModal(id) { document.getElementById(id).classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.classList.remove('active'); });
});
// ===== THEME =====
function toggleTheme() {
state.theme = state.theme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', state.theme);
saveState();
}
// ===== SIDEBAR =====
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('collapsed');
}
// ===== TOAST =====
function showToast(msg) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = 'toast';
toast.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> ${escHtml(msg)}`;
container.appendChild(toast);
setTimeout(() => { toast.classList.add('out'); setTimeout(() => toast.remove(), 300); }, 2500);
}
// ===== UTILITIES =====
function generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); }
function escHtml(str) { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; }
function stripHtml(html) { const d = document.createElement('div'); d.innerHTML = html; return d.textContent || ''; }
function formatDate(ts) {
const d = new Date(ts);
const now = new Date();
const diff = now - d;
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`;
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: d.getFullYear() !== now.getFullYear() ? 'numeric' : undefined });
}
function formatTime(ts) {
return new Date(ts).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
}
function getTagColor(tag) {
let hash = 0;
for (let i = 0; i < tag.length; i++) hash = tag.charCodeAt(i) + ((hash << 5) - hash);
const colors = ['#6c5ce7','#00b894','#e17055','#0984e3','#fdcb6e','#d63031','#e84393','#00cec9'];
return colors[Math.abs(hash) % colors.length];
}
let saveTimeout;
function debouncedSave() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => { saveState(); updateLastSaved(); }, 500);
}
// ===== KEYBOARD SHORTCUTS =====
document.addEventListener('keydown', e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'n') { e.preventDefault(); createNewNote(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'f') { e.preventDefault(); document.getElementById('searchInput').focus(); }
if (e.key === 'Escape') {
document.querySelectorAll('.modal-overlay.active').forEach(m => m.classList.remove('active'));
hideContextMenu();
}
});
// ===== INIT =====
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"basic","sessionId":"a9f699e4-f3d8-4838-8d8d-ec95f038a53f","tokensIn":217,"tokensOut":13070,"tokensTotal":13287,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-19T01:42:32.694Z"} -->