{#if !$activeNote}
Select a note or create a new one
Ctrl
+
N
New note
Ctrl
+
P
Quick open
{:else}
{ if (e.key === 'Tab') { e.preventDefault(); editor?.commands.focus('start'); } if (e.key === 'Enter') { e.preventDefault(); editor?.commands.focus('start'); } }} onchange={(e) => { if ($activeNote) { const newTitle = (e.target as HTMLInputElement).value; $activeNote.meta.title = newTitle; // Update the note list entry so the 2nd panel reflects the change notes.update(list => list.map(n => n.path === $activeNotePath ? { ...n, meta: { ...n.meta, title: newTitle } } : n )); $editorDirty = true; autoSave(); } }} />
{#if $editorDirty}
Unsaved
{/if} {#if readOnly}
View Mode
{/if}
noteSearchOpen ? closeNoteSearch() : openNoteSearch()} title="Find in note (Ctrl+F)" >
{#if readOnly}
{:else}
{/if}
{ if ($activeNote) { $activeNote.meta.pinned = !$activeNote.meta.pinned; $editorDirty = true; autoSave(); } }} title={$activeNote?.meta.pinned ? 'Unpin note' : 'Pin note'} >
{ if (!noteRelativePath) return; try { if (isQuickAccess) { await removeQuickAccess(noteRelativePath); } else { await addQuickAccess(noteRelativePath); } const qa = await getQuickAccess(); $quickAccessPaths = qa.map(n => n.relative_path); } catch (e) { console.error('Quick access toggle failed:', e); } }} title={isQuickAccess ? 'Remove from Quick Access' : 'Add to Quick Access'} >
{#if $appConfig?.enable_wiki_links}
showGraph = !showGraph} title="Graph View" >
{/if} {#if $appConfig?.ai_provider && ($appConfig?.ai_api_key || $appConfig?.openai_api_key)}
{/if}
($sourceMode = !$sourceMode)} title="Toggle Markdown Editor" >
{#if noteSearchOpen}
updateNoteSearch(noteSearchQuery)} onkeydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); e.shiftKey ? noteSearchPrev() : noteSearchNext(); } if (e.key === 'Escape') { e.preventDefault(); closeNoteSearch(); } }} use:autofocus />
{#if noteSearchQuery && noteSearchResults.length > 0} {noteSearchIndex + 1} / {noteSearchResults.length} {:else if noteSearchQuery} No results {/if}
{/if}
{#if $sourceMode}
{ $editorDirty = true; autoSave(); }} spellcheck="false" >
{:else}
{ closeLinkContextMenu(); handleEditorClick(e); }} oncontextmenu={handleEditorContextMenu} onkeydown={(e) => { if (readOnly && !e.ctrlKey && !e.metaKey && !e.altKey && e.key.length === 1) { readOnlyFlash = true; setTimeout(() => { readOnlyFlash = false; }, 600); } }}>
{/if}
{#if showHistory}
Version History
{ showHistory = false; historyPreview = null; historySelected = null; }}>
{#if historyLoading}
Loading...
{:else if historyVersions.length === 0}
No versions yet. Versions are created automatically as you edit (at least 5 minutes apart).
{:else}
{#each historyVersions as v}
previewVersion(v)} >
{formatVersionDate(v.timestamp)}
{formatVersionSize(v.size)}
{/each}
{/if} {#if historySelected && historyPreview !== null}
Restore this version
{/if}
{/if}
{#if editorReady && !$sourceMode}
{ headingDropdown = false; colorDropdown = false; highlightDropdown = false; tablePickerOpen = false; alignDropdown = false; insertDropdown = false; }}>
{ e.stopPropagation(); insertDropdown = !insertDropdown; headingDropdown = false; colorDropdown = false; highlightDropdown = false; tablePickerOpen = false; alignDropdown = false; }} title="Insert">
{#if insertDropdown}
e.stopPropagation()}>
{ insertDropdown = false; document.querySelector
('#insert-image-input')?.click(); }}>
Image
{ insertDropdown = false; document.querySelector
('#insert-file-input')?.click(); }}>
File
{ insertDropdown = false; tablePickerOpen = true; }}>
Table
{ insertDropdown = false; editor?.chain().focus().setHorizontalRule().run(); }}>
Horizontal Rule
{ insertDropdown = false; editor?.chain().focus().toggleCodeBlock().run(); }}>
Code Block
{ insertDropdown = false; editor?.chain().focus().toggleBlockquote().run(); }}>
Quote
{ insertDropdown = false; editor?.chain().focus().setDetails().run(); }}>
Collapsible Section
{/if}
{ e.stopPropagation(); headingDropdown = !headingDropdown; colorDropdown = false; highlightDropdown = false; tablePickerOpen = false; alignDropdown = false; insertDropdown = false; }} title="Heading">
{#if headingDropdown}
e.stopPropagation()}>
{ editor?.chain().focus().toggleHeading({ level: 1 }).run(); headingDropdown = false; }}>Heading 1
{ editor?.chain().focus().toggleHeading({ level: 2 }).run(); headingDropdown = false; }}>Heading 2
{ editor?.chain().focus().toggleHeading({ level: 3 }).run(); headingDropdown = false; }}>Heading 3
{ editor?.chain().focus().toggleHeading({ level: 4 }).run(); headingDropdown = false; }}>Heading 4
{ editor?.chain().focus().setParagraph().run(); headingDropdown = false; }}>Paragraph
{/if}
editor?.chain().focus().toggleBold().run()} title="Bold (Ctrl+B)">
editor?.chain().focus().toggleItalic().run()} title="Italic (Ctrl+I)">
editor?.chain().focus().toggleUnderline().run()} title="Underline (Ctrl+U)">
editor?.chain().focus().toggleStrike().run()} title="Strikethrough (Ctrl+Shift+X)">
{ e.stopPropagation(); colorDropdown = !colorDropdown; headingDropdown = false; highlightDropdown = false; tablePickerOpen = false; alignDropdown = false; insertDropdown = false; }} title="Text Color">
{#if colorDropdown}
e.stopPropagation()}> {#each textColors as color}
setTextColor(color.value)} style="background: {color.value || 'var(--text-primary)'}"> {#if (color.value === '' && !editor.getAttributes('textStyle').color) || editor.getAttributes('textStyle').color === color.value}
{/if}
{/each}
{/if}
editor?.chain().focus().toggleBulletList().run()} title="Bullet List (Ctrl+Shift+8)">
editor?.chain().focus().toggleOrderedList().run()} title="Ordered List (Ctrl+Shift+7)">
1
2
3
editor?.chain().focus().toggleTaskList().run()} title="Task List (Ctrl+Shift+9)">
editor?.chain().focus().undo().run()} title="Undo (Ctrl+Z)">
editor?.chain().focus().redo().run()} title="Redo (Ctrl+Shift+Z)">
editor?.chain().focus().toggleCode().run()} title="Inline Code (Ctrl+E)">
editor?.chain().focus().toggleCodeBlock().run()} title="Code Block (Ctrl+Alt+C)">
editor?.chain().focus().toggleBlockquote().run()} title="Quote (Ctrl+Shift+B)">
editor?.chain().focus().setDetails().run()} title="Collapsible Section">
{ e.stopPropagation(); tablePickerOpen = !tablePickerOpen; headingDropdown = false; colorDropdown = false; highlightDropdown = false; alignDropdown = false; insertDropdown = false; }} title="Insert Table">
{#if tablePickerOpen}
e.stopPropagation()}>
{#each Array(8) as _, r} {#each Array(10) as _, c}
tablePickerHover = { rows: r + 1, cols: c + 1 }} onclick={() => insertTable(r + 1, c + 1)} >
{/each} {/each}
{tablePickerHover.rows > 0 ? `${tablePickerHover.rows} x ${tablePickerHover.cols}` : 'Select size'}
{/if}
editor?.chain().focus().setHorizontalRule().run()} title="Horizontal Rule">
{ e.stopPropagation(); highlightDropdown = !highlightDropdown; headingDropdown = false; colorDropdown = false; tablePickerOpen = false; alignDropdown = false; insertDropdown = false; }} title="Highlight (Ctrl+Shift+H)">
{#if highlightDropdown}
e.stopPropagation()}> {#each highlightColors as color}
setHighlightColor(color.value)} style="background: {color.swatch}"> {#if editor.isActive('highlight', { color: color.value })}
{/if}
{/each}
setHighlightColor('')} style="background: var(--bg-tertiary);"> {#if !editor.isActive('highlight')}
{:else}
{/if}
{/if}
editor?.chain().focus().toggleSubscript().run()} title="Subscript">
x
2
editor?.chain().focus().toggleSuperscript().run()} title="Superscript">
x
2
{ e.stopPropagation(); alignDropdown = !alignDropdown; headingDropdown = false; colorDropdown = false; highlightDropdown = false; tablePickerOpen = false; insertDropdown = false; }} title="Text Alignment"> {#if (editorState, editor.isActive({ textAlign: 'center' }))}
{:else if (editorState, editor.isActive({ textAlign: 'right' }))}
{:else if (editorState, editor.isActive({ textAlign: 'justify' }))}
{:else}
{/if}
{#if alignDropdown}
e.stopPropagation()}>
{ editor?.chain().focus().setTextAlign('left').run(); alignDropdown = false; }}>
Left
{ editor?.chain().focus().setTextAlign('center').run(); alignDropdown = false; }}>
Center
{ editor?.chain().focus().setTextAlign('right').run(); alignDropdown = false; }}>
Right
{ editor?.chain().focus().setTextAlign('justify').run(); alignDropdown = false; }}>
Justify
{/if}
{ if (!editor) return; // Try list indent first — run() returns true if it succeeded const sank = editor.chain().focus().sinkListItem('listItem').run(); if (!sank) { const sankTask = editor.chain().focus().sinkListItem('taskItem').run(); if (!sankTask && editor.state.selection.empty) { editor.chain().focus().insertContent('\t').run(); } } }} title="Indent (Tab)">
{ if (!editor) return; const lifted = editor.chain().focus().liftListItem('listItem').run(); if (!lifted) { const liftedTask = editor.chain().focus().liftListItem('taskItem').run(); if (!liftedTask && editor.state.selection.empty) { // Remove leading tab/spaces from current line const { from } = editor.state.selection; const pos = editor.state.doc.resolve(from); const lineStart = pos.start(pos.depth); const lineText = editor.state.doc.textBetween(lineStart, pos.end(pos.depth)); if (lineText.startsWith('\t')) { editor.chain().focus().command(({ tr }) => { tr.delete(lineStart, lineStart + 1); return true; }).run(); } else if (lineText.startsWith(' ')) { editor.chain().focus().command(({ tr }) => { tr.delete(lineStart, lineStart + 4); return true; }).run(); } else if (lineText.startsWith(' ')) { editor.chain().focus().command(({ tr }) => { tr.delete(lineStart, lineStart + 2); return true; }).run(); } } } }} title="Outdent (Shift+Tab)">
{/if} {/if}
{ const file = (e.target as HTMLInputElement).files?.[0]; if (file) insertImage(file); (e.target as HTMLInputElement).value = ''; }} />
{ const file = (e.target as HTMLInputElement).files?.[0]; if (file) { if (file.type.startsWith('image/')) insertImage(file); else if (file.type === 'application/pdf') insertPdf(file); else insertFileAttachment(file); } (e.target as HTMLInputElement).value = ''; }} />
{#if linkContextMenu}
e.stopPropagation()}>
{linkContextMenu.href}
Open Link
{#if isFileLink(linkContextMenu.href)}
Save As...
{/if}
Copy Link
Edit Link
Remove Link
{/if} {#if textContextMenu}
e.stopPropagation()}>
Cut
Ctrl+X
Copy
Ctrl+C
Paste
Ctrl+V
Select All
Ctrl+A
ctxHeadingSubmenu = true} onmouseleave={() => ctxHeadingSubmenu = false}>
Heading
{#if ctxHeadingSubmenu}
ctxSetHeading(1)}>Heading 1
ctxSetHeading(2)}>Heading 2
ctxSetHeading(3)}>Heading 3
ctxSetHeading(4)}>Heading 4
Paragraph
{/if}
Bold
Ctrl+B
Italic
Ctrl+I
Underline
Ctrl+U
Strikethrough
Highlight
Add Link
Ctrl+K
Inline Code
Code Block
Quote
Collapsible Section
Bullet List
1
2
3
Numbered List
Task List
{#if $appConfig?.ai_provider && ($appConfig?.ai_api_key || $appConfig?.openai_api_key)}
AI Actions
{/if}
{/if} {#if tableContextMenu}
e.stopPropagation()}>
Add Row Above
Add Row Below
Delete Row
Add Column Left
Add Column Right
Delete Column
Merge Cells
Split Cell
Toggle Header Row
Toggle Header Column
Cell Color
{#each cellColors as color}
tblSetCellColor(color.value)} >
{/each}
Delete Table
{/if} {#if imageToolbar}
(imageToolbar = null)}>
e.stopPropagation()}>
setImageSize('small')} title="Small (33%)">S
setImageSize('medium')} title="Medium (50%)">M
setImageSize('full')} title="Full width">L
{/if} {#if codeLangDropdown}
e.stopPropagation()}>
selectCodeLang('')} >auto
{#each codeLanguages as lang}
selectCodeLang(lang)} >{lang}
{/each}
{/if} {#if slashMenu}
e.stopPropagation()}> {#if slashTablePicker}
{#each Array(8) as _, r} {#each Array(10) as _, c}
slashTableHover = { rows: r + 1, cols: c + 1 }} onmousedown={(e) => { e.preventDefault(); slashInsertTable(r + 1, c + 1); }} >
{/each} {/each}
{slashTableHover.rows > 0 ? `${slashTableHover.rows} x ${slashTableHover.cols}` : 'Select table size'}
{:else if slashFiltered.length === 0}
No matching commands
{:else} {#each slashFiltered as cmd, i}
slashSelectedIndex = i} onmousedown={(e) => { e.preventDefault(); executeSlashCommand(i); }} >
{@html cmd.icon}
{cmd.label}
{/each} {/if}
{/if} {#if wikiLinkMenu}
e.stopPropagation()}> {#if wikiLinkFiltered.length === 0}
{wikiLinkMenu.query ? 'No matching notes' : 'Type to search notes...'}
{:else} {#each wikiLinkFiltered.slice(0, 12) as entry, i}
wikiLinkSelectedIndex = i} onmousedown={(e) => { e.preventDefault(); insertWikiLink(entry); }} >
{entry.title}
{/each} {/if}
{/if} {#if aiMenu}
e.stopPropagation()}> {#if aiResult !== null || aiLoading}
{#if aiLoading}
Generating... {:else} AI Result {/if}
{#if aiError}
{aiError}
{:else}
{aiResult}
{/if} {#if !aiLoading && aiResult && !aiError}
{aiEmptyNote ? 'Insert Note' : aiWholeNote ? 'Apply to Note' : 'Replace'}
Discard
{/if} {:else if aiShowCustom}
aiShowCustom = false}>
Custom Prompt
{ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); runAiAction('custom', aiCustomPrompt); } }} use:autofocus >
runAiAction('custom', aiCustomPrompt)} disabled={!aiCustomPrompt.trim()}>
Send
{:else if aiTranslateMenu}
aiTranslateMenu = false}>
Translate to
runAiAction('translate_en')}>English
runAiAction('translate_nl')}>Dutch
runAiAction('translate_de')}>German
runAiAction('translate_fr')}>French
runAiAction('translate_es')}>Spanish
{:else if aiEmptyNote}
Generate Note
{ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); runAiAction('custom', aiCustomPrompt); } }} use:autofocus >
runAiAction('custom', aiCustomPrompt)} disabled={!aiCustomPrompt.trim()}>
Generate
{:else}
{aiWholeNote ? 'AI Actions (Entire Note)' : 'AI Actions'}
runAiAction('improve')}>
Improve Writing
runAiAction('fix_grammar')}>
Fix Grammar
runAiAction('shorter')}>
Make Shorter
runAiAction('longer')}>
Make Longer
runAiAction('professional')}>
Professional Tone
runAiAction('friendly')}>
Friendly Tone
runAiAction('summarize')}>
Summarize
runAiAction('explain')}>
Explain
aiTranslateMenu = true}>
Translate
aiShowCustom = true}>
Custom Prompt...
{/if}
{/if} {#if linkModal}
e.stopPropagation()}>
Insert Link
{ if (e.key === 'Enter') linkModalConfirm(); if (e.key === 'Escape') linkModalCancel(); }} placeholder="https://example.com" />
Cancel
{linkModalUrl ? 'Apply' : 'Remove Link'}
{/if} {#if showGraph}
showGraph = false} onnavigate={(path, title) => { showGraph = false; navigateToWikiLink(path, title); }} /> {/if}