feat(editor): interaction trio host + Notion-style UX (v0.3) - #63
Merged
Conversation
Land host side of Phase A — consume sibling editor-core slash runtime.
- preferencesStore: add `slash` to `EDITOR_PLUGIN_IDS` (enabled by default)
- NoteEditor:
- opt into `slashCommandPlugin()` when `enabledPlugins` contains 'slash'
- subscribe to `EditorEventType.SlashTriggerChange`, mirror match into
component state, drop on `match.active: false`
- wire `host.getSlashItems` through new `interactionProviders.ts`
- SlashCommandPopover.tsx (new): Radix Popover anchored at `match.screenRect`,
renders grouped items with `activeIndex` highlighted; captures keydown
on editor's contentDOM and routes ArrowDown/ArrowUp/Enter/Escape to
`slash.{next,prev,confirm,dismiss}` via `editorControl.execCommand`
- interactionProviders.ts (new): `getSlashItems(query, signal)` returns
"Jump to: <title>" items from `fileTreeStore` notes (fuzzy match);
empty query returns first 8 notes
- Settings general.tsx: add Slash 命令 row in 编辑器插件 section
Depends on sibling commit
swarm-apps/swarmnote-editor@feat/interaction-trio-phase-a-slash · 0a23111
Frontend typecheck + lint:ci + build pass. Manual `pnpm tauri dev`
smoke test deferred to user.
Build on sibling's expanded slash surface (commandId path + per-item
priority + onItemConfirmed) to deliver a Notion-style picker:
- interactionProviders: add a basic block catalog (Heading 1/2/3,
Bulleted / Numbered / Check list, Quote, Divider, Today's date).
Most route through commandId references (toggleHeading via run for
level arg; toggleList / toggleBlockquote / insertHorizontalRule via
commandId metadata only).
- MRU: persist recently confirmed item ids to localStorage
(swarmnote.slash.mru, capped at 20). On each getSlashItems call,
matching items get item.priority bumped to MRU_PRIORITY_BASE + recency
rank and re-sectioned as "Recent" so the popover groups them at the
top. Empty store → no behavior change.
- NoteEditor: pass onItemConfirmed → bumpSlashMru into
slashCommandPlugin({ ... }) so every Enter on the popover updates MRU.
Frontend typecheck + lint:ci + build pass. Depends on sibling commit
c33a10a (execCommandFacet + per-plugin slash items + SlashItem.priority).
Render each SlashItem as a <button> with onMouseDown (not onClick) +
preventDefault. mousedown fires before the editor's blur event, so the
trigger stays active long enough to dispatch; click would arrive after
SlashTriggerChange { active: false } already unmounted the popover.
On pick, call `editorControl.execCommand("slash.confirmAt", index)`
to atomically jump to the clicked index and commit in one round-trip.
Land host side of Phase B — consume sibling editor-core wikilink runtime.
- preferencesStore: add `wikilink` to EDITOR_PLUGIN_IDS (default enabled)
- NoteEditor:
- opt into `wikilinkPlugin()` when enabled
- subscribe to `EditorEventType.WikilinkTriggerChange`, mirror match
into component state, drop on `match.active: false`
- wire `host.getWikilinkItems` from interactionProviders
- render `<WikilinkPopover />` alongside existing slash popover
- WikilinkPopover.tsx (new): Radix Popover anchored at `match.screenRect`,
identical keyboard / click routing as slash but dispatches `wikilink.*`
commands; static "Link to note" header
- interactionProviders: `getWikilinkItems(query, signal)` returns matching
note titles from `fileTreeStore` (empty query returns first 8)
- Settings general.tsx: add Wikilink row in 编辑器插件 section
Frontend typecheck + lint:ci + build pass. Depends on sibling commit
879cb2f (wikilink runtime + types stable).
Land host side of Phase C — the trio is complete on desktop.
- preferencesStore: add `selectionToolbar` to EDITOR_PLUGIN_IDS (default enabled)
- NoteEditor:
- opt into `selectionToolbarPlugin()` when enabled
- subscribe to `EditorEventType.SelectionToolbarChange`, mirror match
into component state, drop on `match.active: false`
- render `<SelectionToolbar />` alongside the slash + wikilink popovers
- SelectionToolbar.tsx (new): Radix Popover with `side="top"` floating
above the selection. Renders the merged action buttons (bold / italic /
strike / inline-code / link by default) as <button onMouseDown> so the
editor selection is preserved through the click. Each button dispatches
`action.commandId` via `editorControl.execCommand`.
Uses lucide icons mapped from action.icon strings (extensible registry).
- Settings general.tsx: add Selection 工具栏 row in 编辑器插件 section
Frontend typecheck + lint:ci + build pass. Depends on sibling commit
b9b2955 (selection toolbar runtime + types stable).
This completes the v0.3 interaction trio (slash + wikilink +
selectionToolbar). Plugin SDK surface is fully stable: all three
register* / get* / event types previously marked @unstable in v0.1 are
now load-bearing and locked.
- dev-notes/knowledge/editor.md: new "Interaction trigger 三类" section covering CharTrigger family abstraction, Selection family, payload DOM-agnostic + requestMeasure pattern, SDK surface (v0.3 stable), execCommandFacet, popover-click vs blur gotcha, Notion-style UX (built-in plugin slash items, basic block catalog, MRU), and third-party plugin example. - dev-notes/plans/editor-core-host-boundary.md §二: banner pointing at the v0.3 interaction-trio OpenSpec change.
Host-side companion to the editor-core link/wikilink polish. Extract shared CharTriggerPopover: - SlashCommandPopover (161) + WikilinkPopover (120) had ~85% duplication across keyboard routing, Radix popover anchor, section grouping, and item rendering - New CharTriggerPopover.tsx (211) owns the shared rendering, generic over CharTriggerItem (SlashItem / WikilinkItem both satisfy) - SlashCommandPopover (20) + WikilinkPopover (21) are now thin shells forwarding `commandPrefix` / `emptyLabel` / `headerLabel`. Future hashtag / mention popovers reuse the same component - Net code reduction ~29 lines, duplication eliminated Internal link routing: - onEvent LinkOpen handler now resolves via `resolveInternalLink(url)` first → loadDocument internal note; otherwise openUrl() external - Previously `openUrl` was called for every url, so wikilink targets and `.md` relative paths were never routed to in-app navigation interactionProviders simplification: - basicBlockItems() function call → BASIC_BLOCK_ITEMS module-level const (no per-call rebuild; closures look up store lazily) - Heading 1/2/3 three near-duplicate object literals → HEADING_LEVELS array + map - getSlashItems / getWikilinkItems duplicate "filter notes by query" → matchNotes(query) helper - Cleaned up doc blocks (resolveInternalLink doc was sandwiched between getSlashItems and getWikilinkItems)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Host-side adoption of editor-core v0.3 interaction trio (slash + wikilink + selectionToolbar) plus the Notion-style polish that came with it.
Highlights
Cross-repo refs
Sibling `@swarmnote/editor-core` change: `swarm-apps/swarmnote-editor#feat/interaction-trio-phase-a-slash`
Test plan