Repository navigation
fix(muya): insertParagraph immediate-block anchoring in nested blocks (PG13) - #4408
Merged
Merged
Conversation
… (PG13) Restore the legacy immediate-anchor capability for `insertParagraph`. The engine method always resolved `_outmostBlockAtCursor()`, so inside a nested list item / blockquote the new paragraph landed adjacent to the OUTERMOST container instead of as an inner sibling next to the immediate block. Add a third `outMost` flag (default `false`): when false, anchor to the IMMEDIATE block-level parent of the active content leaf (mirrors the legacy `getAnchor`/`getParent` path used by the context-menu "Insert Paragraph Before/After" action); when true, keep the outermost-container behaviour used by "Create Paragraph Below" (which passes `outMost=true` explicitly). Top-level paragraphs are unaffected since the immediate parent and outermost block coincide. Flips parity scoreboard PG13 (parityInsertParagraphNested.spec.ts, 2 specs `it.fails` -> `it`) and adds a blockEditing.spec.ts regression locking the `outMost=true` path. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR restores legacy insertParagraph anchoring behavior in @muyajs/core when the cursor is inside nested structures (e.g., list items, blockquotes), closing parity gap PG13 from the migration follow-ups.
Changes:
- Re-introduces
insertParagraph(location, text, outMost=false)to choose between immediate-block anchoring (default) and outermost-container anchoring (outMost=true). - Adds
_immediateBlockAtCursor()to support the new default anchoring mode. - Updates/extends Vitest coverage by flipping the PG13 parity spec to passing and adding a regression test for the
outMost=truebehavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/muya/src/muya.ts | Adds immediate-vs-outmost anchoring option to insertParagraph. |
| packages/muya/src/tests/parityInsertParagraphNested.spec.ts | Flips PG13 from expected-fail to passing for nested list/blockquote insertion. |
| packages/muya/src/tests/blockEditing.spec.ts | Adds regression coverage for outMost=true anchoring in nested structures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+478
to
+482
| private _immediateBlockAtCursor(): Parent | null { | ||
| const content = this.editor.activeContentBlock ?? this.editor.selection.anchorBlock; | ||
|
|
||
| return content?.parent ?? null; | ||
| } |
Jocs
added a commit
that referenced
this pull request
Jun 8, 2026
… heading-link, source cursor, saved indicator) (#4415) * fix(desktop): map copyAsRich to the engine copyAsRich method (PG9) The legacy "Copy as Rich Text" command was remapped to copyAsHtml, which blanks text/html and puts the HTML source into text/plain, so pasting into Word/email yielded raw HTML markup as literal text. #4411 added a real Muya.copyAsRich() that writes rendered HTML to text/html and plain text to text/plain; point COPY_PASTE_METHOD_MAP.copyAsRich at it. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * feat(desktop): wire heading-copy-link to copyGithubSlug (PG11) #4414 made the engine attach a hover-to-copy affordance to every heading and emit heading-copy-link { key } (key == the heading's stable slug) on click. Re-subscribe in editor.vue and forward the key to editorStore.copyGithubSlug, which copies `#<githubSlug>` to the clipboard — restoring the heading-anchor copy affordance that was a documented gap after the @muyajs/core migration. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(desktop): match exported-TOC anchors to engine heading ids (PG8) editor.vue now exports via @muyajs/core (#4406), and #4412 injects github-compatible heading ids onto exported headings (deduped in document order with a `-N` suffix). getHtmlToc still slugged via the legacy muyajs Slugger, so `href="#slug"` targets no longer matched the injected ids and TOC / [TOC] links were dead. Swap to @muyajs/core's generateGithubSlug and replicate the engine's whole-document `-N` dedup so the anchors resolve. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(desktop): consume selection-change affiliation for menu state (PG1) adaptSelectionChange hardcoded affiliation:[] and copied changes.type ('Caret'/'Range') into start.type/end.type, so createApplicationMenuState's `start.type === 'span'` and functionType guards never fired — the native Paragraph-menu check marks, loose/task-list toggles, table/code-fence detection, and Format-disable-in-code all went dead after the @muyajs/core migration. #4410 added an `affiliation` chain (outermost-first) plus per-endpoint `anchorBlockInfo`/`focusBlockInfo` (`type: 'span'` + `functionType`) to the selection-change payload. Map them onto the legacy shape: - start/end `.type` and `.block.functionType` from the leaf block info, - affiliation passed through, with a derived `functionType` surfaced on `pre`/`figure` containers so table / code-fence detection lights up. Also fix the consumer's loose-list read: the engine affiliation entry carries `isLooseListItem` on the list block directly, not via a `children` chain. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(desktop): restore saved indicator on undo-to-disk (PG15) makeSyntheticHistory minted an ever-incrementing editSeq id on every json-change (including undo/redo), so after edit-then-undo-to-saved the id never matched lastSavedHistoryId and the tab stayed marked dirty even when its content matched disk. Derive the synthetic id from the engine undo-stack DEPTH instead — a stable position marker that returns to its saved value when an edit is undone back to the baseline. Seed lastSavedHistoryId to 0 (the engine's post-setContent baseline depth, since setContent clears history) so a freshly-loaded, never-saved document clears its dirty indicator when undone back to disk content, mirroring the legacy history-index behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * feat(muya): add setCursorByOffset for source-mode cursor restore (PG2) The source-code -> WYSIWYG handoff carries only a CodeMirror {line, ch} index cursor; @muyajs/core had no index-offset -> block-path conversion, so the WYSIWYG caret was lost. Add Muya#setCursorByOffset, reproducing the legacy muyajs approach: inject sentinel strings into the current markdown at the line/ch offsets, rebuild the tree (sentinels embed as literal text), find the content blocks they landed in, then rebuild the clean document and set the cursor by the resolved block paths + offsets. Both setContent calls run synchronously so no intermediate paint occurs, and the method is a no-op for stale/unresolvable cursors. Engine helper only — desktop wiring lands in a separate commit. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(desktop): restore WYSIWYG caret after source-mode edit (PG2) handleFileChange dropped the saved muyaIndexCursor on the source-code -> WYSIWYG handoff, so the caret was lost. Consume the new engine Muya#setCursorByOffset: when the tab has no key-based cursor but carries a CodeMirror {line, ch} index cursor, map it onto a block-key cursor so the caret lands where the source-mode cursor was. Restore the per-tab engine history afterwards (setCursorByOffset re-runs setContent internally, which clears history). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * test(desktop): flip parity e2e for PG1/PG2/PG15, defer PG14 Remove the `test.fail()` markers on the PG1 (Paragraph-menu check mark), PG2 (source-mode caret restore), and PG15 (saved indicator on undo-to-disk) parity e2e tests now that the desktop wiring lands — all three genuinely pass. PG14 (first undo after source mode reverts the bulk edit in one step) stays `test.fail()`: recording the source-mode change as a single undo boundary needs a general whole-document json1 diff through Editor.updateContents' pick/drop walker, which only handles specific op shapes and would risk corrupting the document. Deferred with an explanatory note here and in handleFileChange. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * docs(test): reconcile parity scoreboard after wave-2 (14/15 fixed) Update the Status column to the true post-merge state: all seven Wave-1 engine PRs (#4408-#4414) plus the Wave-2 desktop wiring close PG1-PG13 and PG15. Fix the "Gaps remaining" count (11 -> 1), add the PG2 setCursorByOffset engine spec, and document why PG14 is accept-deferred (single-undo-boundary across the source-mode handoff needs a whole-document json1 diff the op walker can't safely apply). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix: address Copilot review on parity wave-2 - editor.vue adaptSelectionChange: restore start/end block.text from the live anchorBlock/focusBlock so SELECTION_CHANGE can still slice the selected text (search prefill); the previous {functionType}-only block dropped it. - editor.vue isIndexCursor: validate both line AND ch are numbers (factored out isIndexPosition) so a missing ch no longer silently clamps to column 0. - muya setCursorByOffset: snapshot getHistory() and restore it after the internal setContent rebuild so the public API is caret-only and does not clear the undo stack; document the behaviour and cover it with a test. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
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
Follow-up to #4406 (desktop migration to
@muyajs/core). Flips parity scoreboard gap PG13 from #4407.The new engine's
insertParagraph(location, text)always resolved_outmostBlockAtCursor(), so when the cursor sat inside a nested list item / blockquote the new paragraph was inserted adjacent to the outermost container instead of as an inner sibling next to the immediate block. The legacypackages/muyajsinsertParagraph(location, text, outMost=false)took a 3rdoutMostflag and, when false (the context-menu "Insert Paragraph Before/After" path), anchored at the immediate parent block (getAnchor/getParent).Change
insertParagraph(location, text, outMost = false)— re-adds the thirdoutMostflag.outMost === false(default): anchor to the immediate block-level parent of the active content leaf (_immediateBlockAtCursor()→content.parent), so a new paragraph stays as an inner sibling inside the list item / blockquote.outMost === true: keep the existing outermost-container behaviour used by "Create Paragraph Below" (editor.vuealready callsinsertParagraph('after', '', true)).Tests
parityInsertParagraphNested.spec.ts(PG13 ×2):it.fails→it, now passing (nested list stays nested; blockquote sibling stays inside the quote).blockEditing.spec.ts: added a regression locking theoutMost=truepath (cursor in a blockquote +outMost=trueinserts after the whole quote at document root).lint(0 errors),lint:types,check-circular,test(515 pass / 18 expected-fail),test:spec(1347 pass).lint:csshas a pre-existing failure ondevelopunrelated to this change (separate branch handles it).Wave-2 desktop note
This is engine-only (
packages/muya); desktopeditor.vueis untouched. For full parity, the wave-2 desktop context-menu "Insert Paragraph Before/After" path (handleInsertParagraph→insertParagraph(location)) must pass the non-outmost anchor — i.e. callinsertParagraph(location, '', false)(or rely on the newfalsedefault). The "Create Paragraph Below" caller must keep passingtrue.Closes parity gap PG13.
🤖 Generated with Claude Code