feat(text-editor): own ProseMirror schema assembly#3988
feat(text-editor): own ProseMirror schema assembly#3988john-traas wants to merge 11 commits intomainfrom
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR refactors ProseMirror schema and plugin setup by replacing Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-3988/ |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/superpowers/plans/2026-03-30-custom-prosemirror-schema.md`:
- Line 34: Update the table summary row that currently reads "`package.json` |
Remove `prosemirror-example-setup`, add 4 direct deps`" to match Task 8 by
changing "4" to "8" (or alternatively adjust Task 8 to only install 4 packages);
ensure the phrase exactly reflects the action in Task 8 so the docs and the task
remain consistent.
- Line 822: The inline code spans in the sentence containing examples like `> `,
`1. `, and `# ` include trailing spaces which trigger markdownlint MD038; update
that line (the sentence "Auto-formatting shortcuts work (type `> ` for
blockquote, `1. ` for ordered list, `# ` for heading)") to remove spaces inside
the backticks so the spans read `>`, `1.`, and `#` respectively, preserving the
text and punctuation around them.
In `@package.json`:
- Around line 126-137: The project relies on the external module "orderedmap"
(imported in nodes.ts and marks.ts) but it is not declared in package.json,
causing brittle transitive-dependency behavior; add "orderedmap" to the
"dependencies" block in package.json (e.g., "orderedmap": "^1.0.0") and run
install to ensure the module is explicitly installed so imports in
src/components/text-editor/prosemirror-adapter/schema/nodes.ts and
src/components/text-editor/prosemirror-adapter/schema/marks.ts resolve reliably.
In `@src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts`:
- Around line 352-363: The menu shortcuts (e.g.,
'Mod-b','Mod-B','Mod-i','Mod-I','Mod-Shift-1','Mod-Shift-2','Mod-Shift-3','Mod-Shift-x','Mod-Shift-X','Mod-`','Mod-Shift-c','Mod-Shift-C')
are shadowed by the base keymap built in buildEditorKeymap(...), so
MenuCommandFactory handlers never run; fix by making precedence explicit: either
move the plugin registration of keymap(this.menuCommandFactory.buildKeymap()) to
run before the base keymap plugin (so MenuCommandFactory's buildKeymap takes
priority), or remove those overlapping entries from
menuCommandFactory.buildKeymap() so they are only present in
buildEditorKeymap(); update the code paths that construct/install the plugins
(where keymap(...) and buildEditorKeymap(...) are invoked) and adjust tests if
any.
In `@src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts`:
- Around line 38-40: The redo mapping lacks the common Windows/Linux shortcut;
in the keys mapping inside base-plugins.ts add a mapping for 'Mod-y' to the
existing redo handler so Ctrl+Y (or Cmd+Y on mac where appropriate) triggers
redo; specifically update the keys object (where keys['Shift-Mod-z'] = redo and
keys['Backspace'] = undoInputRule are defined) to include keys['Mod-y'] = redo
to provide parity across platforms.
In `@src/components/text-editor/prosemirror-adapter/plugins/image/node.ts`:
- Around line 123-125: The image HTML builder currently interpolates raw attrs
(attrs.title, attrs.src, attrs.alt) into the returned string (where
titleAttribute and styleAttribute are composed), which allows attribute
injection; fix it by running all serialized attribute values through an
HTML-attribute-escaping helper (e.g., escapeHtmlAttribute or similar) before
composing titleAttribute/styleAttribute and the final `<img ... />` string so
quotes, ampersands and angle brackets are encoded and the attributes remain
safe; update uses in the template-returning function to reference the escaped
values instead of raw attrs.
In `@src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx`:
- Around line 403-405: The custom keymap from menuCommandFactory.buildKeymap is
being shadowed by the base keymap created in buildBasePlugins (which registers
buildEditorKeymap) because plugin order matters; move the
keymap(keymap(this.menuCommandFactory.buildKeymap())) to appear before
...buildBasePlugins(this.schema) so the custom bindings (Mod-b, Mod-i, Mod-`)
are checked first, ensuring createTriggerPlugin and other plugins remain in the
same relative order.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7c30d255-abbf-4e7a-9f4e-ea3f52e0c907
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
docs/superpowers/plans/2026-03-30-custom-prosemirror-schema.mdpackage.jsonsrc/components/text-editor/prosemirror-adapter/menu/menu-commands.tssrc/components/text-editor/prosemirror-adapter/plugins/base-plugins.tssrc/components/text-editor/prosemirror-adapter/plugins/image/node.tssrc/components/text-editor/prosemirror-adapter/plugins/input-rules.tssrc/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsxsrc/components/text-editor/prosemirror-adapter/schema/index.tssrc/components/text-editor/prosemirror-adapter/schema/marks.tssrc/components/text-editor/prosemirror-adapter/schema/nodes.tssrc/components/text-editor/prosemirror-adapter/schema/schema.spec.ts
| |------|--------| | ||
| | `src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx` | Replace imports, simplify `initializeSchema()` and `createEditorState()` | | ||
| | `src/components/text-editor/prosemirror-adapter/plugins/image/node.ts` | Add `draggable: true` and `title` attribute | | ||
| | `package.json` | Remove `prosemirror-example-setup`, add 4 direct deps | |
There was a problem hiding this comment.
Dependency count in file map is inconsistent with Task 8.
Line 34 says “add 4 direct deps,” but Task 8 installs 8 direct prosemirror-* packages. Please align the summary row to avoid plan ambiguity.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/superpowers/plans/2026-03-30-custom-prosemirror-schema.md` at line 34,
Update the table summary row that currently reads "`package.json` | Remove
`prosemirror-example-setup`, add 4 direct deps`" to match Task 8 by changing "4"
to "8" (or alternatively adjust Task 8 to only install 4 packages); ensure the
phrase exactly reflects the action in Task 8 so the docs and the task remain
consistent.
| - Bold, italic, strikethrough formatting works | ||
| - Lists (bullet and ordered) work | ||
| - Blockquote, code block, heading work | ||
| - Auto-formatting shortcuts work (type `> ` for blockquote, `1. ` for ordered list, `# ` for heading) |
There was a problem hiding this comment.
Fix markdownlint MD038 by removing spaces inside inline code spans.
The inline examples include trailing spaces inside backticks, which triggers lint warnings.
Suggested fix
-- Auto-formatting shortcuts work (type `> ` for blockquote, `1. ` for ordered list, `# ` for heading)
+- Auto-formatting shortcuts work (type `>` + space for blockquote, `1.` + space for ordered list, `#` + space for heading)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - Auto-formatting shortcuts work (type `> ` for blockquote, `1. ` for ordered list, `# ` for heading) | |
| - Auto-formatting shortcuts work (type `>` + space for blockquote, `1.` + space for ordered list, `#` + space for heading) |
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 822-822: Spaces inside code span elements
(MD038, no-space-in-code)
[warning] 822-822: Spaces inside code span elements
(MD038, no-space-in-code)
[warning] 822-822: Spaces inside code span elements
(MD038, no-space-in-code)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/superpowers/plans/2026-03-30-custom-prosemirror-schema.md` at line 822,
The inline code spans in the sentence containing examples like `> `, `1. `, and
`# ` include trailing spaces which trigger markdownlint MD038; update that line
(the sentence "Auto-formatting shortcuts work (type `> ` for blockquote, `1. `
for ordered list, `# ` for heading)") to remove spaces inside the backticks so
the spans read `>`, `1.`, and `#` respectively, preserving the text and
punctuation around them.
| "dependencies": { | ||
| "diff": "^8.0.3", | ||
| "prosemirror-commands": "^1.7.1", | ||
| "prosemirror-dropcursor": "^1.8.2", | ||
| "prosemirror-gapcursor": "^1.4.1", | ||
| "prosemirror-history": "^1.5.0", | ||
| "prosemirror-inputrules": "^1.5.1", | ||
| "prosemirror-keymap": "^1.2.3", | ||
| "prosemirror-schema-list": "^1.5.1", | ||
| "prosemirror-state": "^1.4.4", | ||
| "prosemirror-view": "^1.41.7", | ||
| "sortablejs": "^1.15.6" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Direct imports of orderedmap:"
rg -n "from 'orderedmap'|from \"orderedmap\"" src/components/text-editor/prosemirror-adapter
echo
node - <<'NODE'
const pkg = require('./package.json');
console.log('dependencies.orderedmap =', pkg.dependencies?.orderedmap ?? null);
console.log('devDependencies.orderedmap =', pkg.devDependencies?.orderedmap ?? null);
NODERepository: Lundalogik/lime-elements
Length of output: 367
🌐 Web query:
orderedmap npm package latest version current
💡 Result:
The latest version of the orderedmap npm package is 2.1.1, published on May 17, 2023. It is a persistent ordered mapping from strings to values, maintained by Marijn Haverbeke, with the repository at https://github.com/marijnh/orderedmap. No newer versions have been released as of 2026-03-30.
Citations:
- 1: https://www.npmjs.com/package/orderedmap
- 2: https://registry.npmjs.org/orderedmap
- 3: https://github.com/marijnh/orderedmap
Declare orderedmap explicitly before importing it directly.
orderedmap is imported directly in src/components/text-editor/prosemirror-adapter/schema/nodes.ts and src/components/text-editor/prosemirror-adapter/schema/marks.ts but not declared in package.json. This currently relies on transitive dependency hoisting and will break in stricter installs.
Suggested fix
"dependencies": {
"diff": "^8.0.3",
+ "orderedmap": "^2.1.1",
"prosemirror-commands": "^1.7.1",
"prosemirror-dropcursor": "^1.8.2",
"prosemirror-gapcursor": "^1.4.1",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "dependencies": { | |
| "diff": "^8.0.3", | |
| "prosemirror-commands": "^1.7.1", | |
| "prosemirror-dropcursor": "^1.8.2", | |
| "prosemirror-gapcursor": "^1.4.1", | |
| "prosemirror-history": "^1.5.0", | |
| "prosemirror-inputrules": "^1.5.1", | |
| "prosemirror-keymap": "^1.2.3", | |
| "prosemirror-schema-list": "^1.5.1", | |
| "prosemirror-state": "^1.4.4", | |
| "prosemirror-view": "^1.41.7", | |
| "sortablejs": "^1.15.6" | |
| "dependencies": { | |
| "diff": "^8.0.3", | |
| "orderedmap": "^2.1.1", | |
| "prosemirror-commands": "^1.7.1", | |
| "prosemirror-dropcursor": "^1.8.2", | |
| "prosemirror-gapcursor": "^1.4.1", | |
| "prosemirror-history": "^1.5.0", | |
| "prosemirror-inputrules": "^1.5.1", | |
| "prosemirror-keymap": "^1.2.3", | |
| "prosemirror-schema-list": "^1.5.1", | |
| "prosemirror-state": "^1.4.4", | |
| "prosemirror-view": "^1.41.7", | |
| "sortablejs": "^1.15.6" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` around lines 126 - 137, The project relies on the external
module "orderedmap" (imported in nodes.ts and marks.ts) but it is not declared
in package.json, causing brittle transitive-dependency behavior; add
"orderedmap" to the "dependencies" block in package.json (e.g., "orderedmap":
"^1.0.0") and run install to ensure the module is explicitly installed so
imports in src/components/text-editor/prosemirror-adapter/schema/nodes.ts and
src/components/text-editor/prosemirror-adapter/schema/marks.ts resolve reliably.
| 'Mod-b': this.getCommand(EditorMenuTypes.Bold), | ||
| 'Mod-B': this.getCommand(EditorMenuTypes.Bold), | ||
| 'Mod-i': this.getCommand(EditorMenuTypes.Italic), | ||
| 'Mod-I': this.getCommand(EditorMenuTypes.Italic), | ||
| 'Mod-Shift-1': this.getCommand(EditorMenuTypes.HeaderLevel1), | ||
| 'Mod-Shift-2': this.getCommand(EditorMenuTypes.HeaderLevel2), | ||
| 'Mod-Shift-3': this.getCommand(EditorMenuTypes.HeaderLevel3), | ||
| 'Mod-Shift-x': this.getCommand(EditorMenuTypes.Strikethrough), | ||
| 'Mod-Shift-X': this.getCommand(EditorMenuTypes.Strikethrough), | ||
| 'Mod-`': this.getCommand(EditorMenuTypes.Code), | ||
| 'Mod-Shift-c': this.getCommand(EditorMenuTypes.CodeBlock), | ||
| 'Mod-Shift-C': this.getCommand(EditorMenuTypes.CodeBlock), |
There was a problem hiding this comment.
These new shortcuts are shadowed by the base keymap and won’t hit MenuCommandFactory.
Line 352, Line 354, Line 359, and Line 362 overlap with bindings in buildEditorKeymap(...). Because base plugins are registered first, these menu handlers are effectively unreachable for those chords.
Suggested direction
Use a single source of truth for overlapping shortcuts:
- either move
keymap(this.menuCommandFactory.buildKeymap())before the base keymap plugin registration, or - remove overlapping entries from one map so precedence is explicit.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts` around
lines 352 - 363, The menu shortcuts (e.g.,
'Mod-b','Mod-B','Mod-i','Mod-I','Mod-Shift-1','Mod-Shift-2','Mod-Shift-3','Mod-Shift-x','Mod-Shift-X','Mod-`','Mod-Shift-c','Mod-Shift-C')
are shadowed by the base keymap built in buildEditorKeymap(...), so
MenuCommandFactory handlers never run; fix by making precedence explicit: either
move the plugin registration of keymap(this.menuCommandFactory.buildKeymap()) to
run before the base keymap plugin (so MenuCommandFactory's buildKeymap takes
priority), or remove those overlapping entries from
menuCommandFactory.buildKeymap() so they are only present in
buildEditorKeymap(); update the code paths that construct/install the plugins
(where keymap(...) and buildEditorKeymap(...) are invoked) and adjust tests if
any.
| keys['Mod-z'] = undo; | ||
| keys['Shift-Mod-z'] = redo; | ||
| keys['Backspace'] = undoInputRule; |
There was a problem hiding this comment.
Add Mod-y for redo shortcut parity on Windows/Linux.
Line 39 defines Shift-Mod-z redo, but Ctrl+Y/Mod-y is missing and users typically expect it in editors.
Suggested fix
keys['Mod-z'] = undo;
keys['Shift-Mod-z'] = redo;
+ keys['Mod-y'] = redo;
keys['Backspace'] = undoInputRule;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| keys['Mod-z'] = undo; | |
| keys['Shift-Mod-z'] = redo; | |
| keys['Backspace'] = undoInputRule; | |
| keys['Mod-z'] = undo; | |
| keys['Shift-Mod-z'] = redo; | |
| keys['Mod-y'] = redo; | |
| keys['Backspace'] = undoInputRule; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts`
around lines 38 - 40, The redo mapping lacks the common Windows/Linux shortcut;
in the keys mapping inside base-plugins.ts add a mapping for 'Mod-y' to the
existing redo handler so Ctrl+Y (or Cmd+Y on mac where appropriate) triggers
redo; specifically update the keys object (where keys['Shift-Mod-z'] = redo and
keys['Backspace'] = undoInputRule are defined) to include keys['Mod-y'] = redo
to provide parity across platforms.
| const titleAttribute = attrs.title ? ` title="${attrs.title}"` : ''; | ||
|
|
||
| return `<img src="${attrs.src}" alt="${attrs.alt}"${styleAttribute} />`; | ||
| return `<img src="${attrs.src}" alt="${attrs.alt}"${titleAttribute}${styleAttribute} />`; |
There was a problem hiding this comment.
Escape serialized attributes before emitting image HTML.
Line 123-Line 125 interpolates attrs.title directly into HTML. A crafted title can break attribute context and inject markup/script when this HTML is rendered.
Suggested fix
+function escapeAttr(value: string): string {
+ return value
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+}
+
function getImageHTML(attrs: ImageNodeAttrs): string {
const style = [];
@@
- const titleAttribute = attrs.title ? ` title="${attrs.title}"` : '';
-
- return `<img src="${attrs.src}" alt="${attrs.alt}"${titleAttribute}${styleAttribute} />`;
+ const titleAttribute = attrs.title
+ ? ` title="${escapeAttr(attrs.title)}"`
+ : '';
+
+ return `<img src="${escapeAttr(attrs.src)}" alt="${escapeAttr(
+ attrs.alt
+ )}"${titleAttribute}${styleAttribute} />`;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const titleAttribute = attrs.title ? ` title="${attrs.title}"` : ''; | |
| return `<img src="${attrs.src}" alt="${attrs.alt}"${styleAttribute} />`; | |
| return `<img src="${attrs.src}" alt="${attrs.alt}"${titleAttribute}${styleAttribute} />`; | |
| function escapeAttr(value: string): string { | |
| return value | |
| .replace(/&/g, '&') | |
| .replace(/"/g, '"') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>'); | |
| } | |
| function getImageHTML(attrs: ImageNodeAttrs): string { | |
| const style = []; | |
| const styleAttribute = style.length > 0 ? ` style="${style.join(';')}"` : ''; | |
| const titleAttribute = attrs.title | |
| ? ` title="${escapeAttr(attrs.title)}"` | |
| : ''; | |
| return `<img src="${escapeAttr(attrs.src)}" alt="${escapeAttr( | |
| attrs.alt | |
| )}"${titleAttribute}${styleAttribute} />`; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/text-editor/prosemirror-adapter/plugins/image/node.ts` around
lines 123 - 125, The image HTML builder currently interpolates raw attrs
(attrs.title, attrs.src, attrs.alt) into the returned string (where
titleAttribute and styleAttribute are composed), which allows attribute
injection; fix it by running all serialized attribute values through an
HTML-attribute-escaping helper (e.g., escapeHtmlAttribute or similar) before
composing titleAttribute/styleAttribute and the final `<img ... />` string so
quotes, ampersands and angle brackets are encoded and the attributes remain
safe; update uses in the template-returning function to reference the escaped
values instead of raw attrs.
| ...buildBasePlugins(this.schema), | ||
| keymap(this.menuCommandFactory.buildKeymap()), | ||
| createTriggerPlugin( |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Plugin order in adapter =="
rg -n -A20 -B5 'plugins:\s*\[' src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
echo
echo "== Base keymap registration in buildBasePlugins =="
rg -n -A8 -B3 'keymap\(baseKeymap\)|buildBasePlugins' src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts
echo
echo "== Potentially overlapping custom key bindings =="
rg -nP 'Mod-b|Mod-B|Shift-Enter|Mod-z|Mod-y|Enter' src/components/text-editor/prosemirror-adapter/menu/menu-commands.tsRepository: Lundalogik/lime-elements
Length of output: 1800
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find baseKeymap definition =="
rg -n 'baseKeymap' src/components/text-editor/prosemirror-adapter/ --type ts --type tsx
echo
echo "== Check imports in base-plugins.ts =="
head -30 src/components/text-editor/prosemirror-adapter/plugins/base-plugins.tsRepository: Lundalogik/lime-elements
Length of output: 145
🌐 Web query:
ProseMirror plugin execution order precedence keymaps
💡 Result:
In ProseMirror, the execution order and precedence of plugins, particularly for keymaps, is determined by the order in which plugins are provided in the EditorState's plugins array. Plugins are executed in the order they appear in the array (index 0 first). Earlier plugins have higher precedence. For keymaps specifically: - Keymap plugins (created via prosemirror-keymap's keymap function) define key bindings that are checked during keyboard input handling. - When a key is pressed, ProseMirror's EditorView checks plugins providing a 'handleKeyDown' prop (which keymap plugins do). These are called in plugin order until one returns true (handled). - Within a single keymap plugin, commands for matching keys are chained—tried in the order of the bindings object until one returns true. - Multiple keymap plugins: the first (earliest in array) whose binding matches and returns true takes precedence. Official docs state: "You can add multiple keymap plugins to an editor. The order in which they appear determines their precedence (the ones early in the array get to dispatch first)." Example from ProseMirror forum: Custom keymaps placed before baseKeymap will override its bindings for the same keys. To control precedence, order plugins explicitly when creating EditorState: plugins: [ customKeymap1, // Highest precedence customKeymap2, keymap(baseKeymap), // Lower history, ] This applies generally to plugin props like handleDOMEvents, filterTransaction, etc., which are resolved by iterating plugins in order.
Citations:
- 1: https://prosemirror.net/docs/ref/
- 2: https://prosemirror.net/docs/ref/version/0.12.0.html
- 3: https://github.com/ProseMirror/prosemirror-keymap
- 4: https://prosekit.dev/references/pm/keymap/
- 5: https://prosemirror.net/docs/ref
- 6: https://prosemirror.net/docs/guide
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check imports in base-plugins.ts =="
head -40 src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts
echo
echo "== Find where baseKeymap is imported from =="
rg -n 'import.*baseKeymap' src/components/text-editor/
echo
echo "== Check all custom keybindings in menu-commands.ts =="
rg -n "'\w+(-\w+)*':" src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts | head -30Repository: Lundalogik/lime-elements
Length of output: 1401
🌐 Web query:
ProseMirror baseKeymap contents prosemirror-commands Mod-b
💡 Result:
In the prosemirror-commands package, baseKeymap is an object exported from src/commands.ts that provides a standard set of keyboard shortcuts for common editing actions in ProseMirror editors. It maps key combinations (using the keymap notation where "Mod" means Cmd on Mac or Ctrl on other platforms) to commands from the package. The full contents are not explicitly listed in the official documentation or reference manual, but the README lists all exported commands used in baseKeymap, including: chainCommands, deleteSelection, joinBackward, selectNodeBackward, joinTextblockBackward, joinForward, selectNodeForward, joinTextblockForward, joinUp, joinDown, lift, newlineInCode, exitCode, createParagraphNear, liftEmptyBlock, splitBlock, splitBlockAs, splitBlockKeepMarks, selectParentNode, selectAll, selectTextblockStart, selectTextblockEnd, wrapIn, setBlockType, toggleMark, autoJoin, baseKeymap, pcBaseKeymap, macBaseKeymap. Common bindings include: - Mod-b: toggleMark(schema.marks.strong) for bold text - Mod-i: toggleMark(schema.marks.em) for italic - Mod-: toggleMark(schema.marks.code) or similar for code - Enter: splitBlock or chainCommands(newlineInCode, createParagraphNear) - Backspace: deleteSelection or chainCommands(joinBackward, deleteSelection) "Mod-b" specifically binds to the toggleMark command for strong/bold formatting, allowing users to toggle bold on the current selection with Cmd+B (Mac) or Ctrl+B (other platforms). To see the exact current implementation, view the source at https://github.com/ProseMirror/prosemirror-commands/blob/master/src/commands.ts (32KB file containing baseKeymap definition). There are platform-specific variants pcBaseKeymap and macBaseKeymap. The keymap plugin from prosemirror-keymap is used to activate it: keymap(baseKeymap(schema)).
Citations:
- 1: https://github.com/ProseMirror/prosemirror-commands
- 2: https://github.com/ProseMirror/prosemirror-commands/blob/master/src/README.md
- 3: https://www.npmjs.com/package/prosemirror-commands
- 4: https://prosekit.dev/references/pm/keymap/
- 5: https://github.com/ProseMirror/prosemirror-keymap
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Full buildEditorKeymap implementation =="
sed -n '30,120p' src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts
echo
echo "== All custom keybindings in menu-commands.ts =="
rg -A200 'buildKeymap\(\)' src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts | grep -E "^\s+'[^']+': " | head -20Repository: Lundalogik/lime-elements
Length of output: 3735
Reorder plugins to prevent custom keybindings from being shadowed by base keybindings.
Custom key bindings for bold (Mod-b), italic (Mod-i), and code (Mod-```) are currently unreachable. The buildEditorKeymapinbuildBasePlugins` (line 403) is registered before the custom keymap (line 404). Since ProseMirror checks plugins in array order, the base bindings dispatch first and prevent the custom commands from executing.
Suggested fix
return EditorState.create({
doc: initialDoc,
plugins: [
- ...buildBasePlugins(this.schema),
keymap(this.menuCommandFactory.buildKeymap()),
+ ...buildBasePlugins(this.schema),
createTriggerPlugin(
this.triggerCharacters,
this.contentConverter
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ...buildBasePlugins(this.schema), | |
| keymap(this.menuCommandFactory.buildKeymap()), | |
| createTriggerPlugin( | |
| keymap(this.menuCommandFactory.buildKeymap()), | |
| ...buildBasePlugins(this.schema), | |
| createTriggerPlugin( |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx`
around lines 403 - 405, The custom keymap from menuCommandFactory.buildKeymap is
being shadowed by the base keymap created in buildBasePlugins (which registers
buildEditorKeymap) because plugin order matters; move the
keymap(keymap(this.menuCommandFactory.buildKeymap())) to appear before
...buildBasePlugins(this.schema) so the custom bindings (Mod-b, Mod-i, Mod-`)
are checked first, ensuring createTriggerPlugin and other plugins remain in the
same relative order.
18004b2 to
6a1d303
Compare
The custom image node spec was missing draggable: true (present in prosemirror-schema-basic's image spec), making images non-draggable. Also adds title attribute support so <img title='...'> is preserved during parse/serialize cycles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-picks strong, em, code from prosemirror-schema-basic. Uses our own link mark (security attrs) and strikethrough. Basic link mark intentionally excluded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-picks base nodes from prosemirror-schema-basic (doc, paragraph, blockquote, heading, code_block, horizontal_rule, text, hard_break). Excludes basic image — uses our own. Adds custom elements, lists, tables (HTML only), and image node via buildNodes(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single entry point for schema creation. Assembles nodes and marks from the cherry-picked specs in nodes.ts and marks.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replicates the input rules from prosemirror-example-setup: smart quotes, ellipsis, em dash, and markdown-style shortcuts for blockquote, lists, code block, and headings. Schema-aware — only creates rules for nodes present in the schema. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explicit plugin assembly replacing exampleSetup(): input rules, baseKeymap, dropCursor, gapCursor, history. Each from its own stable ProseMirror package. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies createSchema() includes all expected nodes and marks, conditionally adds tables for HTML mode, includes custom elements, and confirms image node has draggable and title. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inline schema assembly and exampleSetup() with createSchema() and buildBasePlugins(). The adapter now delegates all schema and base plugin construction to the schema/ and plugins/ modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove prosemirror-example-setup. Add prosemirror-inputrules, prosemirror-dropcursor, prosemirror-gapcursor, prosemirror-history, prosemirror-commands, prosemirror-keymap, prosemirror-state, and prosemirror-view as direct dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
prosemirror-schema-list was a transitive dep of the now-removed prosemirror-example-setup. Add it as a direct dependency. Also applies Prettier formatting fixes to new schema module files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move all keybindings into MenuCommandFactory.buildKeymap(). The factory is now the single source of truth for keyboard shortcuts. base-plugins.ts returns to infrastructure only (inputRules, baseKeymap, dropCursor, gapCursor, history). Fix key casing: ProseMirror treats Mod-B as Cmd+Shift+B and Mod-b as Cmd+B. Now binds both cases for mark toggles. Add EditorMenuTypes: Paragraph, HeaderLevel4/5/6, HardBreak, HorizontalRule, SplitListItem, LiftListItem, SinkListItem, WrapInBulletList, WrapInOrderedList, WrapInBlockquote. Add corresponding command factories and mapping entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6a1d303 to
395c0c5
Compare
Summary
prosemirror-example-setupwith explicit, owned schema and plugin assemblyschema/module (nodes.ts, marks.ts, index.ts) that cherry-picks specs fromprosemirror-schema-basicinstead of importing the pre-built schema objectplugins/input-rules.tsandplugins/base-plugins.tsto replace the opaqueexampleSetup()call with direct imports from stable ProseMirror packagesdraggable: trueandtitleattribute (existing oversight discovered during audit)MenuCommandFactory.buildKeymap()—Mod-B(uppercase) only matchedCmd+Shift+B, not plainCmd+BWhy
prosemirror-example-setupis explicitly intended for demos, not production use. We were passingmenuBar: falsebut still getting everything else as a black box. Our customlinkandimagespecs were silently replacing the basic ones viaOrderedMap.append()— not obvious from the code. This change makes every node, mark, and plugin in the editor explicit and auditable.Dependencies
prosemirror-example-setupprosemirror-inputrules,prosemirror-dropcursor,prosemirror-gapcursor,prosemirror-history,prosemirror-commands,prosemirror-keymap,prosemirror-state,prosemirror-view,prosemirror-schema-listTest plan
npm run build)npm run lint)>,1.,#,```)🤖 Generated with Claude Code
Summary by CodeRabbit