From 0e9ab84afbd9c1b2df517b9e5c6b2e76e9fd88a2 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 7 Mar 2026 18:26:30 -0500 Subject: [PATCH] Workspace note editor: toolbar, link suggestions, wikilink button, Ctrl+S MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MarkdownToolbar to the workspace note editor (was missing entirely) - Add [[ button to MarkdownToolbar so wikilinks are discoverable in all editors; clicking inserts [[ which immediately triggers the WikilinkSuggestion dropdown - Add link suggestions strip: polls /api/notes/link-suggestions 2.5s after edit, shows unlinked note-title mentions as clickable chips to wrap in [[...]], plus "All" button to apply everything at once — directly addresses the heading→wikilink workflow (note title appearing as heading text gets detected and offered for linking) - Add Ctrl+S keyboard shortcut on title input and editor area to save - Replace ✕ delete icon on note list items with trash can SVG (consistency with task panel) Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/MarkdownToolbar.vue | 6 + .../src/components/WorkspaceNoteEditor.vue | 148 +++++++++++++++++- 2 files changed, 148 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/MarkdownToolbar.vue b/frontend/src/components/MarkdownToolbar.vue index 6931425..cccb8b9 100644 --- a/frontend/src/components/MarkdownToolbar.vue +++ b/frontend/src/components/MarkdownToolbar.vue @@ -73,6 +73,12 @@ const buttons = [ command: () => props.editor?.chain().focus().toggleCodeBlock().run(), isActive: () => props.editor?.isActive("codeBlock") ?? false, }, + { + label: "[[", + title: "Insert wikilink (type note title to search)", + command: () => props.editor?.chain().focus().insertContent("[[").run(), + isActive: () => false, + }, ]; diff --git a/frontend/src/components/WorkspaceNoteEditor.vue b/frontend/src/components/WorkspaceNoteEditor.vue index 922d9ca..f6513a7 100644 --- a/frontend/src/components/WorkspaceNoteEditor.vue +++ b/frontend/src/components/WorkspaceNoteEditor.vue @@ -1,12 +1,14 @@ @@ -276,7 +355,7 @@ onMounted(loadProjectNotes); title="Delete note" @click="requestDelete(note.id, $event)" > - ✕ + @@ -443,6 +522,63 @@ onMounted(loadProjectNotes); } .btn-dismiss-suggestions:hover { color: var(--color-text); } +.toolbar-row { + padding: 0.3rem 0.6rem; + border-bottom: 1px solid var(--color-border); + flex-shrink: 0; +} + +.link-suggest-strip { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.25rem; + padding: 0.3rem 0.6rem; + border-bottom: 1px solid var(--color-border); + background: color-mix(in srgb, var(--color-primary) 5%, var(--color-surface)); + flex-shrink: 0; +} + +.link-suggest-label { + font-size: 0.7rem; + color: var(--color-text-muted); + flex-shrink: 0; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.link-suggest-chip { + display: inline-flex; +} + +.btn-chip-link { + background: none; + border: 1px solid var(--color-primary); + border-radius: 999px; + padding: 0.1rem 0.45rem; + font-size: 0.7rem; + color: var(--color-primary); + cursor: pointer; + font-family: monospace; + white-space: nowrap; +} +.btn-chip-link:hover { + background: color-mix(in srgb, var(--color-primary) 15%, transparent); +} + +.btn-link-all { + background: none; + border: 1px solid var(--color-border); + border-radius: 4px; + padding: 0.1rem 0.4rem; + font-size: 0.7rem; + color: var(--color-text-muted); + cursor: pointer; + margin-left: 0.1rem; +} +.btn-link-all:hover { border-color: var(--color-primary); color: var(--color-primary); } + .editor-area { flex: 1; overflow-y: auto;