Hey guys, me again :) I have issue with syncing doc using collaboration module. I am building VueJS app and i am using tiptap library. Within their example, in order to make collaboration working, it should be called this:
socket.on('update', data => this.editor.extensions.options.collaboration.update(data))
But when i update doc from another window, in the first window i am receiving update event with newer version, but nothing happen, and doc remains out of sync. I read the whole discussion here:
https://discuss.prosemirror.net/t/how-to-handle-edge-case-with-collab-module/288/22
But i didn't conclude what am i doing wrong.. Any help?
Is there a proper way to use prosemirror-commands within an InputRule, or are we supposed to compose transactions from scratch using prosemirror-transforms?
I'm trying to modify the original input rules for lists, so that I can not only toggle lists within paragraphs, but within other lists too.
For example, if we're in a bullet list, and 1. is typed, I would want to convert that first bullet list item into an ordered list.
My current approach is to use the commands given in schema list basic: use the liftListItem followed by a wrapInList.
However, I'm noticing that commands require the current state as input: updating the state results in a mismatched transaction, while not updating it between commands results in overwrites.
// most of this is skeleton code of wrappingInputRule
new InputRule(/^([-+*])\s$/, (state, match, start, end) => {
let tr = state.tr.delete(start, end)
let $start = tr.doc.resolve(start), range = $start.blockRange();
let parentType = range.parent.type;
if (parentType.name.includes("item")) {
state = state.apply(tr); // mismatched transaction
if (!sinkListItem(nodeType)(state, newTr => {tr = newTr})) return null
$start = tr.doc.resolve(start), range = $start.blockRange();
}
let wrapping = range && findWrapping(range, nodeType, {})
if (!wrapping) return null
tr.wrap(range, wrapping)
let before = tr.doc.resolve(start - 1).nodeBefore
if (before && before.type == nodeType && canJoin(tr.doc, start - 1))
tr.join(start - 1)
return tr
}),
state.apply(tr)
because there's no dispatch function to update the state in an inputrule 2. all new transforms after the initial one have to work off the previous tr's doc. So I had to hack liftListItem and wrapInList a bit to get a working list conversion inputrule: https://gist.github.com/BrianHung/0159c278885cd975a6a8a690e18df0a4.
gapcursor
, and the other looks like ProseMirror might be mishandling a Chrome bug… Demos here: https://eastern-zircon-algebra.glitch.me/
I'm trying to implement a function to make ParseMirror select the next double quote in the document to the right of the cursor. So far I have the following:
moveToQuote() {
var {size} = this.editor.view.state.doc.content
var from = this.editor.selection.from
var to = this.editor.selection.to
var before = this.editor.state.doc.textBetween(0, from, '\n', ' ')
var after = this.editor.state.doc.textBetween(to, size, '\n', ' ')
var myIndex = after.indexOf('"')
this.editor.setSelection(to+myIndex, to+myIndex+1)
}
And this works well if the double quote is on the same line as the cursor. However, if there is one or more new line between the cursor and the match then the index will be off by one or more characters.
Basically, there seem to be hidden elements that influence the position but don't get returned with .textBetween()
(and they don't seem to be non-text leaf nodes either, because those should be converted into whitespaces in this example).
So my question is basically: is there anyway to do an .indexOf()
in ParseMirror to get the correct pos
to use in .setSelection()
?
I've got a pair of issues with inline nodes that I'd love to get some eyes on — one looks like a missed opportunity for
gapcursor
, and the other looks like ProseMirror might be mishandling a Chrome bug… Demos here: https://eastern-zircon-algebra.glitch.me/
@marijnh Any chance you could help me understand what's going on here?
Run over the tree with
nodesBetween
and it'll pass you the offset of the node you're looking at. You can then scan text nodes' content and add the offset of any matches to the node's offset.
Nice, thank you! :) For anyone interested this is what I ended up doing:
moveToQuote() {
var $editor = this.editor
var {size} = $editor.view.state.doc.content
var from = $editor.selection.from
var to = $editor.selection.to
var $succeeded = false
$editor.state.doc.nodesBetween(to, size, (node, pos, parent, index) => {
if (!$succeeded) {
var text = node.text
if (text) {
var myIndex = text.indexOf('"', to-pos)
}
if (myIndex > -1) {
$editor.setSelection(pos+myIndex, pos+myIndex+1)
$succeeded = true
return false
}
}
})
},
Because nodesBetween
fires for every note in the range I needed to add the control variable $succeeded
so it doesn't move the cursor again once it has found a match.
textBetween
, but that'll obviously drop markup. Or use the markdown serializer from the prosemirror-markdown module (assuming you are also using that schema)
@BrianHung how to address "all new transforms after the initial one have to work off the previous tr's doc"?
I have similar issue where I tried to upload an image with URL.createObjectURL(f) as placeholder, then I replace
it with the uploaded image url:
const attrs = { src: URL.createObjectURL(imageFile), className: "uploading_" + new Date().getTime() }
// placeholder before actually uploaded
dispatch(state.tr.replaceSelectionWith(nodeType.createAndFill(attrs)))
view.focus()
// simulate uploading
setTimeout(() => {
const node = $("." + attrs.className)
const pos = view.posAtDOM(node)
// replace with the uploaded image url
dispatch(state.tr.replaceWith(pos, pos, nodeType.create({ src: "https://avatars-03.gitter.im/group/iv/6/57542d82c43b8c601977d702?s=48" })))
}, 2000)
the second dispatch
will throw Uncaught RangeError: Applying a mismatched transaction
error. but if I comment out the first dispatch
, then the second dispatch
will work perfectly.
Thank Marijn very much! I got the idea.
then I look for the way to get the new state after the initial transaction, I saw let newState = state.apply(tr)
, then I tried to change the code a little as:
let newState = state.apply(state.tr)
dispatch(newState.tr.replaceWith(pos, pos, nodeType.create({ src: "https://avatars-03.gitter.im/group/iv/6/57542d82c43b8c601977d702?s=48" })))
can you guide me further on this?
RangeError: Position X out of range
on ClientA since he has wifi issues, and in the meantime ClientB edits the doc.. Then when ClientA sends the data, his version is for example 410, and on the server it is stored version 400... Then this part of code throws exception
`
let doc = schema.nodeFromJSON(storedData.doc);
let newSteps = steps.map(step => {
const newStep = Step.fromJSON(schema, step);
newStep.clientID = clientID;
// apply step to document
let result = newStep.apply(doc);
doc = result.doc;
return newStep;
});
`
textBetween
. Are you running into a situation where you only have the fragment?
clipboardTextSerializer
but with single \n
as block separatorHi @marijnh , i checked the collab demo from the ProseMirror docs, and i got similar situation like i had right now.. The use case is that two clients are working on the same doc, and then server is down during deploy, or clients got network issues, but they are not aware of it and they continue to type, but as soon as server is up or they come online, multiple new steps are sent, i am getting the following error (this is error from ProseMirror collab server)
Error: Invalid version 91
at Instance.checkVersion (/website/src/collab/server/instance.js:73:17)
at Instance.getEvents (/website/src/collab/server/instance.js:83:10)
at handle (/website/src/collab/server/server.js:140:19)
at finish (/website/src/collab/server/server.js:53:34)
at Object.router.add.args [as handler] (/website/src/collab/server/server.js:67:7)
at routes.some.route (/website/src/collab/server/route.js:47:13)
at Array.some (<anonymous>)
at Router.resolve (/website/src/collab/server/route.js:42:24)
at exports.handleCollabRequest (/website/src/collab/server/server.js:10:17)
at maybeCollab (/website/src/devserver.js:59:9)
function updateHTML(view,html){
let domNode = document.createElement("div");
domNode.innerHTML = html;
let tr = view.state.tr;
let node = DOMParser.fromSchema(schema).parse(domNode);
tr.setSelection(new AllSelection(view.state.doc));
tr.replaceSelectionWith(node);
view.dispatch(tr);
}
contenteditable
in the past and working with other less stable abstractions over that it's a pleasure to use. Many thanks for all the work you've put into it.