Skip to content

Commit

Permalink
Add "trailing hacks" in more scenarios.
Browse files Browse the repository at this point in the history
Browsers have very particular opinions about where they will allow
users to place selections in contenteditable elements. ProseMirror
manages this in part by placing <br> elements in locations that
browsers otherwise wouldn't allow user selections.

This PR brings react-prosemirror closer to matching all of the
situations that ProseMirror itself uses these "trailing hacks". Rather
than only in empty textblocks, we now also place trailing hacks when
a textblock node ends with a non-text node, a widget, or a text node
that ends with a newline.
  • Loading branch information
smoores-dev committed Mar 15, 2024
1 parent ff0c846 commit 602029d
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 30 deletions.
40 changes: 35 additions & 5 deletions demo/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ const schema = new Schema({
cellAttributes: {},
tableGroup: "block",
}),
footnote: {
group: "inline",
content: "text*",
inline: true,
// This makes the view treat the node as a leaf, even though it
// technically has content
atom: true,
attrs: { number: { default: 0 } },
},
list: {
group: "block",
content: "list_item+",
Expand Down Expand Up @@ -99,10 +108,10 @@ const editorState = EditorState.create({
]),
schema.text(" the first paragraph"),
]),
schema.nodes.paragraph.create(
{},
schema.text("This is the second paragraph")
),
schema.nodes.paragraph.create({}, [
schema.text("This is the second paragraph"),
schema.nodes.footnote.create({ number: 1 }, schema.text("Footnote")),
]),
schema.nodes.paragraph.create(),
schema.nodes.image.create(),
schema.nodes.image.create(),
Expand Down Expand Up @@ -164,6 +173,22 @@ const ListItem = forwardRef(function ListItem(
);
});

const Footnote = forwardRef(function Footnote(
{ nodeProps, ...props }: NodeViewComponentProps,
ref: Ref<HTMLButtonElement>
) {
return (
<button
ref={ref}
{...props}
suppressContentEditableWarning
contentEditable="false"
>
{nodeProps.node.attrs.number}
</button>
);
});

const TestWidget = forwardRef(function TestWidget(
{ widget, pos, ...props }: WidgetViewComponentProps,
ref: ForwardedRef<HTMLSpanElement>
Expand Down Expand Up @@ -301,7 +326,12 @@ function DemoEditor() {
plugins={plugins}
nodeViews={
showReactNodeViews
? { paragraph: Paragraph, list: List, list_item: ListItem }
? {
paragraph: Paragraph,
list: List,
list_item: ListItem,
footnote: Footnote,
}
: undefined
}
customNodeViews={showReactNodeViews ? undefined : customNodeViews}
Expand Down
26 changes: 13 additions & 13 deletions docs/assets/index-fb909589.js → docs/assets/index-0b42cd6e.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React-ProseMirror Demo</title>
<script type="module" crossorigin src="/react-prosemirror/assets/index-fb909589.js"></script>
<script type="module" crossorigin src="/react-prosemirror/assets/index-0b42cd6e.js"></script>
<link rel="stylesheet" href="/react-prosemirror/assets/index-523693cd.css">
</head>
<body>
Expand Down
23 changes: 12 additions & 11 deletions src/components/ChildNodeViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -427,17 +427,18 @@ export function ChildNodeViews({
reactKeys?.posToKey
);

if (!children.length) {
childElements.push(
<TrailingHackView
key={createKey(
editorState?.doc,
innerPos,
{ type: "trailinghack", offset: 0 },
reactKeys?.posToKey
)}
/>
);
const lastChild = children[children.length - 1];

if (
!lastChild ||
lastChild.type !== "node" ||
(lastChild.node.isInline && !lastChild.node.isText) ||
// RegExp.test actually handles undefined just fine
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
/\n$/.test(lastChild.node.text!)
) {
// TODO: ProseMirror also adds an img hack after non-contenteditable views
childElements.push(<TrailingHackView key="trailing-hack" />);
}

return <>{childElements}</>;
Expand Down

0 comments on commit 602029d

Please sign in to comment.