Slash menu, markdown input rules, canonical copy/cut and type-to-replace#15
Merged
Conversation
Ported from the notion-affordances branch onto the content-model era: one priority-raised ixsl:onbeforeinput dispatcher (src/input.xsl) intercepts printable-char triggers and next-matches into undo's typing coalescer, so plain typing still snapshots. Triggers act on the HOST the caret sits in and every conversion is content-model-gated - a shorthand whose result the model rejects stays literal text. - / in an empty host opens a filterable, keyboard-navigable menu showing only the commands the context admits (conversions for text-block hosts, Quote where the wrap is legal, lists where the model places one, figure/table always - routed to the existing dialogs); menu popups position via show-at-caret over the refactored show-at-point. - Markdown shorthands in a paragraph host: #/##/### + space, -/* + space, 1. + space, ``` -> convert in place; '> ' WRAPS in a blockquote (blockquote > p - converting the p itself would be invalid). Undo restores the literal marker in one step. - convert-block: an element-level caret on the block itself dangled after replaceWith (PR #10's fix) - restore only nodes that move. New notion.mjs suite (31 assertions incl. context filtering in list items and flow cells, nested-host conversion, I1-I5 invariants). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copy of a selection spanning hosts now puts the STORAGE form on the clipboard instead of the editing DOM: cloneContents of the region-clamped range runs the canonical + cm-normalize pipeline (paste in reverse), so the HTML flavor carries no chrome, contenteditable or marker classes but keeps RDFa attributes - annotated content round-trips between documents. Cut adds the delete machine (one region-keyed undo entry). Within-host copy stays native. The clamp steps are factored into local:clamped-range, shared by the delete machine and copy. Typing a printable character over a cross-host selection now replaces it: the delete machine collapses the caret into a host and the character lands there (local:insert-text-at-caret) - one gesture, one undo entry, from host focus and from the page background alike. Enter/Tab/paste stay suppressed. select.mjs: type-to-replace (stage 2 and sweep-with-seam), canonical copy assertions (no editing ephemera, RDFa kept, plain-text flavor), native within-host copy, cut with merge + one-undo restore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The figure/table dialog saves predated the content-model era: they inserted after the TOP-LEVEL block (insertAfterBlock), so a table requested from an empty list item landed after the whole list - valid markup, wrong place - while the +paragraph/+list buttons already nested per the model. The dialogs now record the invoking HOST (insertHost) and both saves place through local:insert-block-at-caret: an empty list item or cell grows the composite INSIDE itself (li/td are %Flow;), a sibling where the parent admits it, after the top-level block otherwise. Chrome injection rides the helper's top-level guard - the old unconditional injection would have put a drag handle on a nested composite. The slash table path also gets the toolbar's 3x3 + header defaults (it only focused the input). notion.mjs: the reported shape (slash table in an empty item nests in the item, no chrome on the nested composite, caret in the first cell, one undo removes it) and figure-in-cell placement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The browser confines a native drag-selection to the contenteditable host it starts in, so no mouse gesture could create a multi-block selection (two-stage Ctrl+A was the only real one; select.mjs masked the gap by building ranges programmatically). Synthesize the gestures in select.xsl: mousedown arms a sweep anchor (caretRangeFromPoint, caretPositionFromPoint fallback), mousemove rebuilds the selection anchor-to-pointer via setBaseAndExtent once the pointer leaves the anchor host (backward selections keep the anchor fixed), clamped to one region, with viewport nudging replacing the dead native autoscroll. Shift+Click extends from the standing anchor and re-arms for Shift+drag; Shift+Up/Down extend block-granularly past host edges (native within the host). In-host drags stay native, the drag handle never arms a sweep, sweep state clears on mouseup/dragstart/undo restore. Downstream machinery (delete machine, type-to-replace, canonical copy/cut) consumes the selections unchanged. New select.mjs cases drive the gestures with real mouse events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Three editor-feel features. The slash menu and markdown rules are a manual port of the corresponding halves of #10 (
notion-affordances) onto the content-model era — that branch predates both the M2 state container and #13, so a rebase was not viable; its design survives (priority-raisedbeforeinputtrigger dispatcher withnext-matchinto undo's coalescer,show-at-pointpositioning split, the slash menu as keyboard framing over existing toolbar actions), its code was retargeted at today's primitives. The@-mention and block-handle menu halves of #10 are not included — they can be ported later onto this same foundation, so this does not close #10.src/input.xsl):/in an empty host opens a filterable, keyboard-navigable menu showing only the commands the caret context admits — conversions for text-block hosts, Quote where the wrap is legal, lists where the model places one, Figure…/Table… routed to the existing dialogs. An empty list item offers just Figure/Table; an empty cell paragraph offers everything.#/##/###,-/*,1.,```, and>act on the host (top-level p, quote-p, cell-p) and are content-model-gated — a shorthand whose result the model rejects stays literal. `> ` wraps (`blockquote > p`) rather than converting, matching the storage contract. One undo restores the literal marker.src/select.xsl): a cross-host selection is copied in its storage form —cloneContentsthrough the canonical + cm-normalize pipeline — so the clipboard HTML has no chrome/contenteditable/marker classes but keeps RDFa attributes; annotated content round-trips between documents. Cut adds the delete machine (one region-keyed undo entry); within-host copy stays native.convert-blockbug (element-level caret on the converted block dangled afterreplaceWith— Add @-mention and Notion-style UX quick wins #10's fix, which empty-block slash/markdown conversions would have tripped constantly).Test plan
tests/browser/notion.mjs(31 assertions): open/filter/keyboard-pick/click paths, context filtering in list items and flow cells, figure routing to the dialog, all five markdown rules incl. nested-host conversion and literal guards, undo granularity, I1–I5 invariants after every mutation.tests/browser/select.mjsextended: type-to-replace (stage 2 and sweep-with-seam), canonical copy (no editing ephemera, RDFa kept, plain-text flavor), native within-host copy untouched, cut with merge + exact one-undo restore.🤖 Generated with Claude Code