diff --git a/src/lib/components/MarkdownNotes.svelte b/src/lib/components/MarkdownNotes.svelte index 2c31a8e..ec8c149 100644 --- a/src/lib/components/MarkdownNotes.svelte +++ b/src/lib/components/MarkdownNotes.svelte @@ -14,9 +14,11 @@ import { untrack } from "svelte"; import { Bold, Italic, Strikethrough, Link2, Heading, List, ListOrdered, Quote } from "@lucide/svelte"; import type { Crepe as CrepeEditor } from "@milkdown/crepe"; - import { callCommand } from "@milkdown/kit/utils"; + import { $prose as proseWrapper, callCommand } from "@milkdown/kit/utils"; + import type { Ctx } from "@milkdown/kit/ctx"; import type { CmdKey } from "@milkdown/kit/core"; - import { editorViewCtx } from "@milkdown/kit/core"; + import { commandsCtx, editorViewCtx } from "@milkdown/kit/core"; + import { Plugin } from "@milkdown/kit/prose/state"; import { toggleStrongCommand, toggleEmphasisCommand, @@ -25,8 +27,17 @@ wrapInOrderedListCommand, wrapInBlockquoteCommand, toggleLinkCommand, + isMarkSelectedCommand, + isNodeSelectedCommand, + strongSchema, + emphasisSchema, + linkSchema, + headingSchema, + bulletListSchema, + orderedListSchema, + blockquoteSchema, } from "@milkdown/kit/preset/commonmark"; - import { toggleStrikethroughCommand } from "@milkdown/kit/preset/gfm"; + import { toggleStrikethroughCommand, strikethroughSchema } from "@milkdown/kit/preset/gfm"; import { linkInputRule, linkifyPastedSelection } from "./markdownNotesPlugins"; interface Props { @@ -51,11 +62,45 @@ // create() can't resurrect a torn-down editor. let gen = 0; + // Which toolbar buttons match the mark/node the caret currently sits in — + // e.g. **bold** text selected lights up the Bold button. Recomputed by + // selectionTracker below on every editor transaction (typing, arrow keys, + // clicks), the same mechanism Crepe's own floating toolbar uses internally. + let active = $state>({}); + /** Dispatch a Milkdown command into Crepe's editor. */ function cmd(key: CmdKey, payload?: T) { crepe?.editor.action(callCommand(key, payload)); } + function computeActive(ctx: Ctx) { + const commands = ctx.get(commandsCtx); + active = { + bold: commands.call(isMarkSelectedCommand.key, strongSchema.type(ctx)), + italic: commands.call(isMarkSelectedCommand.key, emphasisSchema.type(ctx)), + strike: commands.call(isMarkSelectedCommand.key, strikethroughSchema.type(ctx)), + link: commands.call(isMarkSelectedCommand.key, linkSchema.type(ctx)), + heading: commands.call(isNodeSelectedCommand.key, headingSchema.type(ctx)), + bulletList: commands.call(isNodeSelectedCommand.key, bulletListSchema.type(ctx)), + orderedList: commands.call(isNodeSelectedCommand.key, orderedListSchema.type(ctx)), + blockquote: commands.call(isNodeSelectedCommand.key, blockquoteSchema.type(ctx)), + }; + } + + // A bare ProseMirror plugin (Crepe exposes no selection-change hook of its + // own) whose view.update fires on every transaction, keeping `active` in + // sync with wherever the caret/selection currently is. Its view() factory + // runs synchronously while the EditorView is still being constructed — + // milkdown's editorViewCtx isn't populated yet, so computeActive would read + // an undefined view.state and throw. The initial snapshot is instead taken + // from rebuild() once `create()` resolves and ctx is fully live. + const selectionTracker = proseWrapper( + (ctx) => + new Plugin({ + view: () => ({ update: () => computeActive(ctx) }), + }), + ); + function addLink() { const url = window.prompt("Link URL"); if (url) cmd(toggleLinkCommand.key, { href: url }); @@ -97,6 +142,8 @@ // Teach the editor to turn typed `[text](url)` into a link — the preset // has no link input rule of its own. c.editor.use(linkInputRule); + c.editor.use(selectionTracker); + active = {}; let created = false; c.on((listener) => { listener.markdownUpdated((_ctx, md) => { @@ -129,6 +176,7 @@ ); crepe = c; ready = true; + computeActive(c.editor.ctx); // If the bound value moved while we were loading (a task switch raced the // import), remount on the fresh content rather than showing stale. const cur = untrack(() => value); @@ -174,15 +222,15 @@
onblur?.()}> {#if ready} {/if}
@@ -229,6 +277,12 @@ .tb:hover { background: var(--surface-alt); color: var(--text); } } .tb:active { background: var(--line); } + /* the mark/node the caret currently sits in — e.g. clicking into **bold** + text lights the Bold button, matching Crepe's own floating toolbar */ + .tb.active { background: var(--surface-alt); color: var(--text); } + @media (hover: hover) { + .tb.active:hover { background: var(--line); } + } .tb-sep { width: 1px; align-self: stretch; margin: 3px 4px; background: var(--line-strong); } .md-surface { min-width: 0; } diff --git a/src/lib/components/TaskItem.svelte b/src/lib/components/TaskItem.svelte index a04c735..23bce08 100644 --- a/src/lib/components/TaskItem.svelte +++ b/src/lib/components/TaskItem.svelte @@ -159,6 +159,7 @@