Please save me some time and use the following template. In 90% of all issues I can't reproduce the problem because I don't know what exactly you are doing, in which environment, or which y-* version is responsible. Just use the following template even if you think the problem is obvious.
Checklist
[x] Are you reporting a bug? Use github issues for bug reports and feature requests. For general questions, please use https://discuss.yjs.dev/
[x] Try to report your issue in the correct repository. Yjs consists of many modules. When in doubt, report it to https://github.com/yjs/yjs/issues/
Describe the bug
I am experiencing a warning issue in a ProseMirror-based editor integrated with y-prosemirror plugins. The warning occurs when initializing the editor with the following y-prosemirror plugins: ySyncPlugin, yCursorPlugin, and yUndoPlugin. The exact warning message is as follows:
index.js:221 TextSelection endpoint not pointing into a node with inline content (doc).
To Reproduce
Steps to reproduce the behavior:
Initialize a ProseMirror editor with a custom schema (yschema).
page.tsx:
"use client";
import { EditorState } from "prosemirror-state";
import "prosemirror-view/style/prosemirror.css";
import React, { useRef, useEffect } from "react";
import { exampleSetup } from "prosemirror-example-setup";
import { EditorView } from "prosemirror-view";
import { toggleMark } from "prosemirror-commands";
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import {
ySyncPlugin,
yCursorPlugin,
yUndoPlugin,
undo,
redo,
} from "y-prosemirror";
import { schema as yschema } from "./yschema";
import { keymap } from "prosemirror-keymap";
import "./ystyle.css";
// :: Object
// Specs for the nodes defined in this schema.
export const nodes = {
// :: NodeSpec The top level document node.
doc: {
content: "block+",
},
// :: NodeSpec A plain paragraph textblock. Represented in the DOM
// as a <p> element.
paragraph: {
attrs: { ychange: { default: null } },
content: "inline*",
group: "block",
parseDOM: [{ tag: "p" }],
toDOM(node) {
return ["p", calcYchangeDomAttrs(node.attrs), 0];
},
},
// :: NodeSpec A blockquote (<blockquote>) wrapping one or more blocks.
blockquote: {
attrs: { ychange: { default: null } },
content: "block+",
group: "block",
defining: true,
parseDOM: [{ tag: "blockquote" }],
toDOM(node) {
return ["blockquote", calcYchangeDomAttrs(node.attrs), 0];
},
},
// :: NodeSpec A hard line break, represented in the DOM as <br>.
hard_break: {
inline: true,
group: "inline",
selectable: false,
parseDOM: [{ tag: "br" }],
toDOM() {
return brDOM;
},
},
};
// :: Object Specs for the marks in the schema.
export const marks = {
// :: MarkSpec A link. Has href and title attributes. title
// defaults to the empty string. Rendered and parsed as an <a>
// element.
link: {
attrs: {
href: {},
title: { default: null },
},
inclusive: false,
parseDOM: [
{
tag: "a[href]",
getAttrs(dom) {
return {
href: dom.getAttribute("href"),
title: dom.getAttribute("title"),
};
},
},
],
toDOM(node) {
return ["a", node.attrs, 0];
},
},
// :: MarkSpec An emphasis mark. Rendered as an <em> element.
// Has parse rules that also match <i> and font-style: italic.
em: {
parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
toDOM() {
return emDOM;
},
},
// :: MarkSpec A strong mark. Rendered as <strong>, parse rules
// also match <b> and font-weight: bold.
strong: {
parseDOM: [
{ tag: "strong" },
// This works around a Google Docs misbehavior where
// pasted content will be inexplicably wrapped in <b>
// tags with a font-weight normal.
{
tag: "b",
getAttrs: (node) => node.style.fontWeight !== "normal" && null,
},
{
style: "font-weight",
getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
},
],
toDOM() {
return strongDOM;
},
},
// :: Schema
// This schema rougly corresponds to the document schema used by
// CommonMark, minus the list elements,
// which are defined in the prosemirror-schema-list
// module.
//
// To reuse elements from this schema, extend or read from its
// spec.nodes and spec.marksproperties.
export const schema = new Schema({ nodes, marks });
3. Include ySyncPlugin, yCursorPlugin, and yUndoPlugin from y-prosemirror.
4. Observe the warning in the console when the editor is initialized.
5. The warning appears whenever any of the mentioned y-prosemirror plugins are used. If I remove all three plugins, the warning disappears. However, removing just one or two of the plugins does not resolve the issue.
**Expected behavior**
The editor should initialize without any warnings, and the y-prosemirror plugins should integrate seamlessly with ProseMirror.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment Information**
- Browser / Node.js [e.g. Chrome, Firefox, Node.js]
- Yjs version and the versions of the y-* modules you are using [e.g. yjs v13.0.1, y-webrtc v1.2.1]. Use `npm ls yjs` to find out the exact version you are using.
<img width="722" alt="image" src="https://github.com/yjs/y-prosemirror/assets/41875799/675b756c-98ab-4634-9f81-0ca3f91aa4ef">
**Additional context**
Add any other context about the problem here.
Checklist
Describe the bug I am experiencing a warning issue in a ProseMirror-based editor integrated with y-prosemirror plugins. The warning occurs when initializing the editor with the following y-prosemirror plugins: ySyncPlugin, yCursorPlugin, and yUndoPlugin. The exact warning message is as follows:
index.js:221 TextSelection endpoint not pointing into a node with inline content (doc).
To Reproduce Steps to reproduce the behavior:
const toggleBold = toggleMark(yschema.marks.strong);
const ydoc = new Y.Doc(); const provider = new WebsocketProvider( "ws://localhost:1234", "prosemirror", ydoc ); const type = ydoc.getXmlFragment("prosemirror");
export default function Home() { const editorRef = useRef<HTMLDivElement | null>(null); const editorViewRef = useRef<EditorView | null>(null);
useEffect(() => { editorViewRef.current = new EditorView(editorRef.current, { state: EditorState.create({ schema: yschema, plugins: exampleSetup({ schema: yschema }).concat([ ySyncPlugin(type), yCursorPlugin(provider.awareness), yUndoPlugin(), keymap({ "Mod-z": undo, "Mod-y": redo, "Mod-Shift-z": redo, }), ]), }), });
}, []);
const printContentAsJSON = () => { const editorView = editorViewRef.current; if (editorView) { const content = editorView.state.doc.toJSON(); console.log(content); } };
const applyBold = () => { const editorView = editorViewRef.current; if (editorView) { toggleBold(editorView.state, editorView.dispatch, editorView); editorView.focus(); } };
return (
React ProseMirror Demo
); }
import { Schema } from "prosemirror-model";
const brDOM = ["br"];
const calcYchangeDomAttrs = (attrs, domAttrs = {}) => { domAttrs = Object.assign({}, domAttrs); if (attrs.ychange !== null) { domAttrs.ychange_user = attrs.ychange.user; domAttrs.ychange_state = attrs.ychange.state; } return domAttrs; };
// :: Object // Specs for the nodes defined in this schema. export const nodes = { // :: NodeSpec The top level document node. doc: { content: "block+", },
// :: NodeSpec A plain paragraph textblock. Represented in the DOM // as a
<p>
element. paragraph: { attrs: { ychange: { default: null } }, content: "inline*", group: "block", parseDOM: [{ tag: "p" }], toDOM(node) { return ["p", calcYchangeDomAttrs(node.attrs), 0]; }, },// :: NodeSpec A blockquote (
<blockquote>
) wrapping one or more blocks. blockquote: { attrs: { ychange: { default: null } }, content: "block+", group: "block", defining: true, parseDOM: [{ tag: "blockquote" }], toDOM(node) { return ["blockquote", calcYchangeDomAttrs(node.attrs), 0]; }, },// :: NodeSpec A horizontal rule (
<hr>
). horizontal_rule: { attrs: { ychange: { default: null } }, group: "block", parseDOM: [{ tag: "hr" }], toDOM(node) { return ["hr", calcYchangeDomAttrs(node.attrs)]; }, },// :: NodeSpec A heading textblock, with a
level
attribute that // should hold the number 1 to 6. Parsed and serialized as<h1>
to //<h6>
elements. heading: { attrs: { level: { default: 1 }, ychange: { default: null }, }, 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, calcYchangeDomAttrs(node.attrs), 0]; }, },// :: NodeSpec A code listing. Disallows marks or non-text inline // nodes by default. Represented as a
<pre>
element with a //<code>
element inside of it. code_block: { attrs: { ychange: { default: null } }, content: "text*", marks: "", group: "block", code: true, defining: true, parseDOM: [{ tag: "pre", preserveWhitespace: "full" }], toDOM(node) { return ["pre", calcYchangeDomAttrs(node.attrs), ["code", 0]]; }, },// :: NodeSpec The text node. text: { group: "inline", },
// :: NodeSpec An inline image (
<img>
) node. Supportssrc
, //alt
, andhref
attributes. The latter two default to the empty // string. image: { inline: true, attrs: { ychange: { default: null }, src: {}, alt: { default: null }, title: { default: null }, }, group: "inline", draggable: true, parseDOM: [ { tag: "img[src]", getAttrs(dom) { return { src: dom.getAttribute("src"), title: dom.getAttribute("title"), alt: dom.getAttribute("alt"), }; }, }, ], toDOM(node) { const domAttrs = { src: node.attrs.src, title: node.attrs.title, alt: node.attrs.alt, }; return ["img", calcYchangeDomAttrs(node.attrs, domAttrs)]; }, },// :: NodeSpec A hard line break, represented in the DOM as
<br>
. hard_break: { inline: true, group: "inline", selectable: false, parseDOM: [{ tag: "br" }], toDOM() { return brDOM; }, }, };const emDOM = ["em", 0]; const strongDOM = ["strong", 0]; const codeDOM = ["code", 0];
// :: Object Specs for the marks in the schema. export const marks = { // :: MarkSpec A link. Has
href
andtitle
attributes.title
// defaults to the empty string. Rendered and parsed as an<a>
// element. link: { attrs: { href: {}, title: { default: null }, }, inclusive: false, parseDOM: [ { tag: "a[href]", getAttrs(dom) { return { href: dom.getAttribute("href"), title: dom.getAttribute("title"), }; }, }, ], toDOM(node) { return ["a", node.attrs, 0]; }, },// :: MarkSpec An emphasis mark. Rendered as an
<em>
element. // Has parse rules that also match<i>
andfont-style: italic
. em: { parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], toDOM() { return emDOM; }, },// :: MarkSpec A strong mark. Rendered as
<strong>
, parse rules // also match<b>
andfont-weight: bold
. strong: { parseDOM: [ { tag: "strong" }, // This works around a Google Docs misbehavior where // pasted content will be inexplicably wrapped in<b>
// tags with a font-weight normal. { tag: "b", getAttrs: (node) => node.style.fontWeight !== "normal" && null, }, { style: "font-weight", getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null, }, ], toDOM() { return strongDOM; }, },// :: MarkSpec Code font mark. Represented as a
<code>
element. code: { parseDOM: [{ tag: "code" }], toDOM() { return codeDOM; }, }, ychange: { attrs: { user: { default: null }, state: { default: null }, }, inclusive: false, parseDOM: [{ tag: "ychange" }], toDOM(node) { return [ "ychange", { ychange_user: node.attrs.user, ychange_state: node.attrs.state }, 0, ]; }, }, };// :: Schema // This schema rougly corresponds to the document schema used by // CommonMark, minus the list elements, // which are defined in the
prosemirror-schema-list
// module. // // To reuse elements from this schema, extend or read from its //spec.nodes
andspec.marks
properties. export const schema = new Schema({ nodes, marks });