UX: prosemirror rich editor nodes cleanup / slightly better UX (#32361)

We don't want "ghost" `content` within mention/hashtag, as they're
already rendering their non-editable content on `toDOM`.

`atom: true` is not necessary for content-less nodes

Re-use existing heading node spec instead of re-creating.

A UX improvement from these changes is a better Cmd-Left/Home navigation
when these nodes begin a paragraph and the caret is after them.
This commit is contained in:
Renato Atilio
2025-04-18 00:20:55 -03:00
committed by GitHub
parent 2b4521d0b3
commit f06175b72d
5 changed files with 7 additions and 25 deletions

View File

@ -7,8 +7,6 @@ const extension = {
attrs: { name: {} }, attrs: { name: {} },
inline: true, inline: true,
group: "inline", group: "inline",
content: "text*",
atom: true,
draggable: true, draggable: true,
selectable: false, selectable: false,
parseDOM: [ parseDOM: [
@ -53,7 +51,9 @@ const extension = {
span_open(state, token, tokens, i) { span_open(state, token, tokens, i) {
if (token.attrGet("class") === "hashtag-raw") { if (token.attrGet("class") === "hashtag-raw") {
state.openNode(state.schema.nodes.hashtag, { state.openNode(state.schema.nodes.hashtag, {
name: tokens[i + 1].content.slice(1), // this is not ideal, but working around the span_open/close structure
// a text is expected just after the span_open token
name: tokens.splice(i + 1, 1)[0].content.slice(1),
}); });
return true; return true;
} }

View File

@ -1,23 +1,11 @@
import { schema } from "prosemirror-markdown";
/** @type {RichEditorExtension} */ /** @type {RichEditorExtension} */
const extension = { const extension = {
nodeSpec: { nodeSpec: {
heading: { heading: {
attrs: { level: { default: 1 } }, ...schema.nodes.heading.spec,
// Overriding ProseMirror's default to allow inline content
content: "inline*", content: "inline*",
group: "block",
defining: true,
parseDOM: [
{ tag: "h1", attrs: { level: 1 } },
{ tag: "h2", attrs: { level: 2 } },
{ tag: "h3", attrs: { level: 3 } },
{ tag: "h4", attrs: { level: 4 } },
{ tag: "h5", attrs: { level: 5 } },
{ tag: "h6", attrs: { level: 6 } },
],
toDOM(node) {
return ["h" + node.attrs.level, 0];
},
}, },
}, },
}; };

View File

@ -8,8 +8,6 @@ const extension = {
attrs: { name: {} }, attrs: { name: {} },
inline: true, inline: true,
group: "inline", group: "inline",
content: "text*",
atom: true,
draggable: true, draggable: true,
selectable: false, selectable: false,
parseDOM: [ parseDOM: [
@ -55,7 +53,7 @@ const extension = {
getAttrs: (token, tokens, i) => ({ getAttrs: (token, tokens, i) => ({
// this is not ideal, but working around the mention_open/close structure // this is not ideal, but working around the mention_open/close structure
// a text is expected just after the mention_open token // a text is expected just after the mention_open token
name: tokens[i + 1].content.slice(1), name: tokens.splice(i + 1, 1)[0].content.slice(1),
}), }),
}, },
}, },

View File

@ -15,7 +15,6 @@ const extension = {
attrs: { url: {}, html: {} }, attrs: { url: {}, html: {} },
selectable: true, selectable: true,
group: "block", group: "block",
atom: true,
draggable: true, draggable: true,
parseDOM: [ parseDOM: [
{ {
@ -44,7 +43,6 @@ const extension = {
inline: true, inline: true,
group: "inline", group: "inline",
selectable: true, selectable: true,
atom: true,
draggable: true, draggable: true,
parseDOM: [ parseDOM: [
{ {

View File

@ -5,7 +5,6 @@ const extension = {
local_date: { local_date: {
attrs: { date: {}, time: {}, timezone: { default: null } }, attrs: { date: {}, time: {}, timezone: { default: null } },
group: "inline", group: "inline",
atom: true,
inline: true, inline: true,
parseDOM: [ parseDOM: [
{ {
@ -42,7 +41,6 @@ const extension = {
timezone: { default: null }, timezone: { default: null },
}, },
group: "inline", group: "inline",
atom: true,
inline: true, inline: true,
parseDOM: [ parseDOM: [
{ {