Git Product home page Git Product logo

y-prosemirror's Introduction

y-prosemirror

ProseMirror Binding for Yjs - Demo

This binding maps a Y.XmlFragment to the ProseMirror state.

Features

  • Sync ProseMirror state
  • Shared Cursors
  • Shared Undo / Redo (each client has its own undo-/redo-history)
  • Successfully recovers when concurrents edit result in an invalid document schema

Example

import { ySyncPlugin, yCursorPlugin, yUndoPlugin, undo, redo, initProseMirrorDoc } from 'y-prosemirror'
import { exampleSetup } from 'prosemirror-example-setup'
import { keymap } from 'prosemirror-keymap'
..

const type = ydocument.get('prosemirror', Y.XmlFragment)
const { doc, mapping } = initProseMirrorDoc(type, schema)

const prosemirrorView = new EditorView(document.querySelector('#editor'), {
  state: EditorState.create({
    doc,
    schema,
    plugins: [
        ySyncPlugin(type, { mapping }),
        yCursorPlugin(provider.awareness),
        yUndoPlugin(),
        keymap({
          'Mod-z': undo,
          'Mod-y': redo,
          'Mod-Shift-z': redo
        })
      ].concat(exampleSetup({ schema }))
  })
})

Also look here for a working example.

Remote Cursors

The shared cursors depend on the Awareness instance that is exported by most providers. The Awareness protocol handles non-permanent data like the number of users, their user names, their cursor location, and their colors. You can change the name and color of the user like this:

example.binding.awareness.setLocalStateField('user', { color: '#008833', name: 'My real name' })

In order to render cursor information you need to embed custom CSS for the user icon. This is a template that you can use for styling cursor information.

/* this is a rough fix for the first cursor position when the first paragraph is empty */
.ProseMirror > .ProseMirror-yjs-cursor:first-child {
  margin-top: 16px;
}
.ProseMirror p:first-child, .ProseMirror h1:first-child, .ProseMirror h2:first-child, .ProseMirror h3:first-child, .ProseMirror h4:first-child, .ProseMirror h5:first-child, .ProseMirror h6:first-child {
  margin-top: 16px
}
/* This gives the remote user caret. The colors are automatically overwritten*/
.ProseMirror-yjs-cursor {
  position: relative;
  margin-left: -1px;
  margin-right: -1px;
  border-left: 1px solid black;
  border-right: 1px solid black;
  border-color: orange;
  word-break: normal;
  pointer-events: none;
}
/* This renders the username above the caret */
.ProseMirror-yjs-cursor > div {
  position: absolute;
  top: -1.05em;
  left: -1px;
  font-size: 13px;
  background-color: rgb(250, 129, 0);
  font-family: serif;
  font-style: normal;
  font-weight: normal;
  line-height: normal;
  user-select: none;
  color: white;
  padding-left: 2px;
  padding-right: 2px;
  white-space: nowrap;
}

You can also overwrite the default Widget dom by specifying a cursor builder in the yCursorPlugin

/**
 * This function receives the remote users "user" awareness state.
 */
export const myCursorBuilder = user => {
  const cursor = document.createElement('span')
  cursor.classList.add('ProseMirror-yjs-cursor')
  cursor.setAttribute('style', `border-color: ${user.color}`)
  const userDiv = document.createElement('div')
  userDiv.setAttribute('style', `background-color: ${user.color}`)
  userDiv.insertBefore(document.createTextNode(user.name), null)
  cursor.insertBefore(userDiv, null)
  return cursor
}

const prosemirrorView = new EditorView(document.querySelector('#editor'), {
  state: EditorState.create({
    schema,
    plugins: [
        ySyncPlugin(type),
        yCursorPlugin(provider.awareness, { cursorBuilder: myCursorBuilder }),
        yUndoPlugin(),
        keymap({
          'Mod-z': undo,
          'Mod-y': redo,
          'Mod-Shift-z': redo
        })
      ].concat(exampleSetup({ schema }))
  })
})

Utilities

The package includes a number of utility methods for converting back and forth between a Y.Doc and Prosemirror compatible data structures. These can be useful for persisting to a datastore or for importing existing documents.

Note: Serializing and deserializing to JSON will not store collaboration history steps and as such should not be used as the primary storage. You will still need to store the Y.Doc binary update format.

import { prosemirrorToYDoc } from 'y-prosemirror'

// Pass JSON previously output from Prosemirror
const doc = Node.fromJSON(schema, {
  type: "doc",
  content: [...]
})
const ydoc = prosemirrorToYDoc(doc)

Because JSON is a common usecase there is an equivalent method that skips the need to create a Prosemirror Node.

import { prosemirrorJSONToYDoc } from 'y-prosemirror'

// Pass JSON previously output from Prosemirror
const ydoc = prosemirrorJSONToYDoc(schema, {
  type: "doc",
  content: [...]
})
import { yDocToProsemirror } from 'y-prosemirror'

// apply binary updates from elsewhere
const ydoc = new Y.Doc()
ydoc.applyUpdate(update)

const node = yDocToProsemirror(schema, ydoc)

Because JSON is a common usecase there is an equivalent method that outputs JSON directly, this method does not require the Prosemirror schema.

import { yDocToProsemirrorJSON } from 'y-prosemirror'

// apply binary updates from elsewhere
const ydoc = new Y.Doc()
ydoc.applyUpdate(update)

const node = yDocToProsemirrorJSON(ydoc)

Undo/Redo

The package exports undo and redo commands which can be used in place of prosemirror-history by mapping the mod-Z/Y keys - see ProseMirror and Tiptap examples.

Undo and redo are be scoped to the local client, so one peer won't undo another's changes. See Y.UndoManager for more details.

Just like prosemirror-history, you can set a transaction's addToHistory meta property to false to prevent that transaction from being rolled back by undo. This can be helpful for programmatic document changes that aren't initiated by the user.

tr.setMeta("addToHistory", false);

License

The MIT License © Kevin Jahns

y-prosemirror's People

Contributors

adventurebeard avatar ankon avatar boschdev avatar brianhung avatar dennis84 avatar dmonad avatar ericrabil avatar estebanprimost avatar flamenco avatar flaviouk avatar fson avatar gabriel-peracio avatar kentomoriwaki avatar manstie avatar mitar avatar nc avatar nikgraf avatar nkeynes avatar ocavue avatar rubaxa avatar saul-mirone avatar teemukoivisto avatar thatsjonsense avatar tomasdahlqvist avatar tommoor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

y-prosemirror's Issues

Does not support marks on non-TextNodes

Checklist

Describe the bug

This plugin fundamentally does not support marks on nodes that are not text nodes. For example, Tiptap's image node, which is an inline, non-text node: https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/nodes/Image.js.

To Reproduce

  1. Apply a mark to an inline node. For example, apply a hyperlink (https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/marks/Link.js) to an image (https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/nodes/Image.js)
  2. Be the editor of the document. Observe that the JSON version of the prosemirror doc does contain the expected mark, and that the HTML is rendered with the correct <a></a> wrapping the image node.
  3. Be a realtime collaborator on the same document, OR reload the document using the serialized ydoc. Observe that the hyperlink on the image is missing because the mark is not serialized into the ydoc.

Expected behavior

Marks on custom nodes are handled just as attributes on custom nodes are handled.

Update current.cursor references to current[cursorStateField]?

Not sure if this was intended, but since the cursorStateField is customizable I figured the references to current.cursor should be changed to current[cursorStateField]. I may not understand the code fully though so apologies if this is there for a reason.

if (current.cursor == null || !Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.anchor), anchor) || !Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.head), head)) {
awareness.setLocalStateField(cursorStateField, {
anchor, head
})
}
} else if (current.cursor != null && relativePositionToAbsolutePosition(ystate.doc, ystate.type, Y.createRelativePositionFromJSON(current.cursor.anchor), ystate.binding.mapping) !== null) {

problem with IE, Edge & Safari

Hi,
I checked the collaborative edit in IE, Edge & Safari its not working..
what should i do to up the editor in these browsers?

Support for Vite.js

Describe the bug
When using the next generation esm bundler vite, I'm getting this error:

 > node_modules/y-prosemirror/src/lib.js:1:26: error: No matching export for import "ProsemirrorMapping"
    1 │ import { updateYFragment, ProsemirrorMapping } from './plugins/sync-plugin.js' // eslint-disable-line
      ╵                           ~~~~~~~~~~~~~~~~~~

I think this is because ProsemirrorMapping is a type generated in the build process. Since vite (esbuild) is very strict and uses the source files, it can't find this export. Here is a comment from evan you about that.

Do you have any idea how to fix that on your side?

To Reproduce

  1. import y-prosemirror in a vite project
  2. start dev server
  3. See error

Expected behavior
It should not throw an error

Alllow Prosemirror <--> Y.XmlFragment utility functions to support multiple Y.XmlFragments within a single Y.Doc

I have a potential use case where I thinking I'll have multiple Y.XmlFragments within a single Y.Doc. I want to be able to re-use the utility functions in this library, but the utility functions (see below) create a new Y.Doc every time. Would it be possible to pass a single Y.XmlFragment parameter instead of Y.Doc and xmlFragment key?

export function prosemirrorToYDoc (doc, xmlFragment = 'prosemirror') {

Thanks and appreciate all the hard work on this project.

Initialize document

How can you set the initial contents of the Y.doc with some existing ProseMirror node view?

No merge updates when deleting content

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

Describe the bug

Maybe the update generated by this plugin cannot be merged.

To Reproduce

  1. enter anything
  2. empty content
  3. repeat the above steps, the update size will continue to increase

https://codesandbox.io/s/y-promirror-bug-j73qe

Expected behavior

The initial content size and the deleted content size should be the same or similar.

Screenshots

This number is the update binary size

Initial Content Updated Deleted

image

image

image

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.

Additional context
Add any other context about the problem here.

How to restore a snapshot

I already have the doc update but I don't know how to apply this to prose mirror editor, when apply im losing all the history
Im using the next code to apply the snapshot

const newProsemirrorDoc = yDocToProsemirror(editor.schema, yDoc);

 editorView.state.doc = newProsemirrorDoc;
 editorView.dispatch(
    editorView.state.tr.setMeta(ySyncPluginKey, {
      restore: true,
      snapshot: yState.snapshot,
    })
 );

any suggestion for how to apply the update

'Cannot convert undefined or null to object' in equalAttrs

In equalAttrs, yattrs can be null thus crashing when Object.keys(yattrs) is executed.

I suppose this happens because iterating over attribute keys a check is done:

  eq = key === 'ychange' || l === r || (typeof l === 'object' && typeof r === 'object' && equalAttrs(l, r))

Which in case when comparing a regular object {} against null tries to recurse the null because null's typeof is object. A rather unfortunate fact but that's how it is. So maybe a fix could be adding a nullness check in that condition or at the start of the function.

To Reproduce
I'll write a reproduction if needed.

Environment Information
"y-prosemirror": "^1.0.12",

Maybe a Bug in snapshots

I don't know if is a bug or is the expected behavior, but when we applied a snapshot some attrs are gone, well I think is all attrs for deleted items, this line of code:

const attrs = typeMapGetAll(
      el
    );

    if (snapshot !== undefined) {
      if (!isVisible(/** @type {Y.Item} */ el._item, snapshot)) {
        attrs.ychange = computeYChange
          ? computeYChange('removed', /** @type {Y.Item} */ el._item.id)
          : { type: 'removed' };
      } else if (!isVisible(/** @type {Y.Item} */ el._item, prevSnapshot)) {
        attrs.ychange = computeYChange
          ? computeYChange('added', /** @type {Y.Item} */ el._item.id)
          : { type: 'added' };
      }
    }

we tested and if we get all attributes that are also deleted in that function everything works properly and all attributes are printed in the prose mirror state after applied the snapshot, this should take all attributes even if they are deleted when the snapshot is present, I don't know if behind of that behavior is another purpose.

thanks for your excellent work.

ESM issue: ./dist/y-prosemirror.mjs doesn't exist

Describe the bug
In package.json:

"exports": {
    ".": {
      "import": "./dist/y-prosemirror.mjs",
      "require": "./dist/y-prosemirror.cjs"
    }
  },

but the file dist/y-prosemirror.mjs doesn't exist in the package.

To Reproduce
Use y-prosemirror with webpack, it will fail with: Module not found: Error: Can't resolve 'y-prosemirror'

Expected behavior
./dist/y-prosemirror.mjs file should exist or be replaced with ./dist/y-prosemirror.cjs

The issue appear only with the last version. Installing version 1.0.9 fix the bug because no "exports" in package.json

Safari initial render issue

Running into a strange issue with Safari. Chrome and Firefox are fine.

On a hard refresh the doc remains blank but other connected browsers display this initial render correctly. If i navigate to another doc Safari behaves correctly.

Every once in a while i can get Safari to display the initial content if it originated from Safari (super rare though).

I've noticed in the sync-plugin.js line 146 changedInitialContent is always false view.state.doc.content.size is always 2 and emptySize is always 2 where chrome will be the same until if finally syncs.

https://github.com/yjs/y-prosemirror/blob/master/src/plugins/sync-plugin.js#L146

[Feature request] Support multiple marks with same mark type.

Is your feature request related to a problem? Please describe.
ProseMirror marks can have attributes. We can custom the mark with excludes to coexist with the mark of the same type, but different attributes.

For example, the annotation feature allows users to comment on the same text. The mark is defined like:

marks: {
  annotation: {
    attrs: { id: { default: null } },
    excludes: '',
    parseDOM: [{ tag: 'annotation', getAttrs(dom) { Number(dom.getAttributes('data-id')) } }],
    toDOM(node) { return ['annotation', { dataId: node.attrs.id }, 0] }
  }
}

Suppose we have a doc ABC, and two users adding annotations at the same time:
User1: Add annotation to AB. yDoc is <annotation id="1"> -> A -> B -> </annotation> -> C.
User2: Add annotation to BC. yDoc is A -> <annotation id="2"> -> B -> C -> </annotation>.

After merging, yDoc is <annotation id="1"> -> A -> <annotation id="2"> -> B -> </annotation> -> C -> </annotation>. The yDoc data is confusing the closing position of annotations, resulting C to be left out of the annotations.

Describe the solution you'd like
Not sure right now.

Describe alternatives you've considered
N/A

Additional context
N/A

ESM error

import { yDocToProsemirrorJSON, prosemirrorJSONToYDoc } from 'y-prosemirror';
                                ^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Named export 'prosemirrorJSONToYDoc' not found. The requested module 'y-prosemirror' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'y-prosemirror';
const { yDocToProsemirrorJSON, prosemirrorJSONToYDoc } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:181:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)

last version

Remove the dependency ySyncPlugin has on prosemirror-view

Hi there!

I have a use case for collaboratively editing a prosemirror document on a nodejs server. Essentially the code running on the server acts in the same capacity as a client (like a user in the browser) making programatic edits to a prosemirror document. Since the ySyncPlugin exported by y-prosemirror needs to bind to the EditorView instance to dispatch transactions and read editor state, it means I have to include the prosemirror-view module and mock out the dom using jsdom. While this works perfectly well, it would be nice to not have to include the dependencies on prosemirror-view and jsdom.

y-prosemirror actually does seem to have some check in place to see if environment.isBrowser, but prosemirror-view does not and still wants to create dom nodes.

Here are the 2 approaches I've taken to get this working. Both work, but both have drawbacks...

First approach:

import { JSDOM } from 'jsdom'
import { ySyncPlugin } from 'y-prosemirror'

const dom = new JSDOM(`<!DOCTYPE html><body></body>`)

// make sure these are available otherwise we error out when newing up an EditorView
global.window = dom.window
global.document = dom.window.document

class Editor {
  constructor(config) {
    const type = config.ydoc.getXmlFragment('prosemirror')
    this.editorState = EditorState.create({ schema, plugins: [ySyncPlugin(type)] })
    // I havent dug into the code, but I was hoping at least that not supplying a "place"
    // to the EditorView would indicate that I may not be in a browser
    this.view = new EditorView(undefined, { 
      state: this.editorState, 
      dispatchTransaction: (tr) => this.apply(tr)
    })
  }

  apply(tr) {
    this.editorState = this.editorState.apply(tr)
    this.view.updateState(this.editorState)
  }
}

Second approach:

The other approach I've tried which seems to work looks something like this:

import { ProsemirrorBinding, ySyncPlugin } from 'y-prosemirror'

class Editor {
  constructor(config) {
    const type = config.ydoc.getXmlFragment('prosemirror')

    // `this` implements the parts of EditorView that the ProsemirrorBinding class cares about
    // i.e. state, dispatch() and hasFocus()
    this.binding = new ProsemirrorBinding(type, this)

    const ysync = ySyncPlugin(type)

    // patch ySyncPlugin's init function so that it gets its binding up-front
    // since prosemirror-view is absent and ySyncPlugin.view() is never called
    const { init } = ysync.spec.state
    ysync.spec.state.init = (initargs, state) => ({
      ...init.call(ysync, initargs, state),
      binding: this.binding,
    })

    this.editorState = EditorState.create({ schema, plugins: [ysync] })
  }

  get state() {
    return this.editorState
  }

  dispatch(tr) {
    this.apply(tr)
  }

  hasFocus() {
    return false
  }

  apply(tr) {
    this.editorState = this.editorState.apply(tr)
    this.binding._prosemirrorChanged(this.editorState.doc)
  }
}

This approach would force me to take a lot more care when upgrading to future versions of y-prosemirror as implementation details may change. So both approaches have drawbacks.

It feels like the implicit coupling of the ySyncPlugin to the EditorView ideally should be avoided if possible (after all, why should state and state updates be concerned with rendering? 🤔)

Proposed solution

Unfortunately I don't have much of a solution to propose - I'm new to yjs and even to prosemirror, really.. But it might be good to extend the ySyncPlugin api to allow some extra config that gives one more control over how transactions are dispatched and applied. Similar to what I've done in approach 2.

There really does seem to be a bit of an impedance mismatch between yjs and the prosemirror plugin system that introduces some awkwardness in getting the 2 to place nice. Is that a fair assessment? Perhaps there is an argument to be made to implement some of the YJS "stuff" outside of the plugin system...?

I know this way of using your plugin is extremely rare and I'm fully prepared for a "wontfix", I but thought I'd put it out there and let you know that such a use case exists 😀 Whatever the case, I'm curious to hear options on all of the above. Thanks for all the open source goodness!

Incorrect cursor position after undo

Describe the bug
As soon as I create multiple nodes one after the other and want to undo this, the cursor stays in the last line. This issue can be reproduced on the official demo page.

To Reproduce
Steps to reproduce the behavior:

  1. create paragraph
  2. press enter
  3. press cmd+z/ctrl+z

Expected behavior
Cursor should be in the first line instead of the second.

Screenshots
https://user-images.githubusercontent.com/2500670/108116413-ce668880-709b-11eb-89ea-dff5a41d6c47.mp4

Bug: prosemiror-table columnResizing not compatible with YJS sync

Describe the bug
if anyone is resizing the table columns, and others are keep typing in the table, prosemirror collapse due to unexpected position after tr.mapping

To Reproduce
Steps to reproduce the behavior:
https://codesandbox.io/s/relaxed-herschel-el656?file=/src/App.js
site: https://el656.csb.app/

  1. open incognito chrome
  2. open chrome
  3. in incognito chrome page, setInterval insert innerText on chrome dev console in first table cell to simulate edit
    for example, setInterval(() => { document.querySelector('td').innerText += "1" }, 1000 )
  4. in chrome page, resize the table column
  5. chrome page collapse

Expected behavior
The columnResizing in prosemirror-table can get correct position instead of null which lead to error in handleDecorations.

Screenshots
image

Additional context
It seems that after editor got update from server, yjs will turn the whole yXmlElement into prosemirror's slice, so tr.mapping cannot get correct position derivation from the tr.steps.
image
image

Initial render

From @WinstonFasset:

I also noticed an issue with the sync plugin that I was not able to fix in the source but worked around in the app. It seems like the sync plugin does not render until there is an update, so if the type is already loaded when the prosemirror instance is bound to it, it remains blank until an update comes in. I found I could work around the problem by running setTimeout(() => { prosemirrorView.state['y-sync$'].binding._forceRerender() }, 0)

Local decorations lost when remote client changes document

Describe the bug

When widget decorations are present on one client and another remote client makes a change the decorations disappear. Note that the remote edit does not have to be on the same node, it can be elsewhere in the document entirely.

To Reproduce
Steps to reproduce the behavior:

  1. Create a widget decoration and pass to the prosemirror view
  2. Edit the document in another window using the y-prosemirror plugin
  3. Widget decoration disappears

  1. Clone repro from this branch
  2. npm install && npm run dist && npm start
  3. Click to add decorations in one client
  4. Edit in another (see attached video)

Expected behavior

Local decorations should be retained when remote client edits document

Screenshots

Kapture.2021-05-18.at.16.02.04.mp4

Environment Information

Failing test

Found a failing test:

npm run test -- --filter "\[8/" --seed 1771758487

Chinese / japanese input are syncd unexpectedly before compositionend

Checklist

Describe the bug
Chinese / japanese input will includes a composition process.

for example:

image

if I want to input "地" using pinyin,I need to press "di" first then press "blank". Before I press "blank", the "d" / "di" is useless and should not be synced.

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'tiptap collaborative-editing demo'
  2. Open another tiptap collaborative-editing demo. Make sure your two browser tabs have the same room number.
  3. Get a chinese or japanese input on your pc / mac.
  4. input a chinese or japanese charator. for example “地”

Expected behavior
“地” should not appear until you prese "blank", and "d" or "di" should not syncd.

Screenshots
image

image

Environment Information

  • Chrome
  • all latest version on tiptap doc site.

Infinite cursor awareness update loop when two prosemirror docs edit same underlying y.XmlFragment

Describe the bug
I am using TipTap (https://tiptap.dev/) which uses y-prosemirror under the hood to handle collaboration cursors. I have a semi unique situation where I have a page with two editors both which edit the same Y.XmlFragment. When I click on either of the editors to begin editing the cursor starts flickering and runs and infinite cycle of updating the awareness state. I have narrowed down the cycle to this block of code:

const updateCursorInfo = () => {
const ystate = ySyncPluginKey.getState(view.state)
// @note We make implicit checks when checking for the cursor property
const current = awareness.getLocalState() || {}
if (view.hasFocus() && ystate.binding !== null) {
const selection = getSelection(view.state)
/**
* @type {Y.RelativePosition}
*/
const anchor = absolutePositionToRelativePosition(selection.anchor, ystate.type, ystate.binding.mapping)
/**
* @type {Y.RelativePosition}
*/
const head = absolutePositionToRelativePosition(selection.head, ystate.type, ystate.binding.mapping)
if (current.cursor == null || !Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.anchor), anchor) || !Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.head), head)) {
awareness.setLocalStateField(cursorStateField, {
anchor, head
})
}
} else if (current.cursor != null && relativePositionToAbsolutePosition(ystate.doc, ystate.type, Y.createRelativePositionFromJSON(current.cursor.anchor), ystate.binding.mapping) !== null) {
// delete cursor information if current cursor information is owned by this editor binding
awareness.setLocalStateField(cursorStateField, null)
}
}

The infinite loop is cycling between:

awareness.setLocalStateField(cursorStateField, { 
  anchor, head 
}) 

and

awareness.setLocalStateField(cursorStateField, null)

My guess is that while the cursor plugin beautifully handles multiple prosemirror documents on a page, it has issues when two prosemirror documents edit the same underlying Y.XmlFragment.

Perhaps more logic around the editor gaining and losing focus could resolve this. i.e. only update awareness when editor is actively focused and set awareness to null when editor loses focus.

Expected behavior
I expect multiple editors to be able to seamlessly edit the same Y.XmlFragment without causing the awareness updates to enter an infinite loop.

Environment Information

  • Browser: Chrome
  • Yjs: "^13.5.22"
  • y-webrtc: "^10.2.2"
  • y-prosemirror: "^1.0.13"

Anyways I'm sure this isn't the most common use case but I would greatly appreciate the help. Thank you for such an awesome tool!

Uncaught TypeError: Cannot read properties of null (reading 'matchesNode')

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

Describe the bug
The syncPlugin will run into an error if the editor get's destroyed. It looks like a call to tiptap#unregisterPlugin causes every other plugin view to update.
sync-plugin.js will then schedule a new _forceRenderer() cycle:

// sync-plugin.js

setTimeout(() => {
  binding._forceRerender()
  view.dispatch(view.state.tr.setMeta(ySyncPluginKey, { binding }))
}, 0)

When the editor gets destroyed in the meantime, the scheduled function will cause the following error:

index.es.js:4914 Uncaught TypeError: Cannot read properties of null (reading 'matchesNode')
    at EditorView.updateStateInner (index.es.js:4914)
    at EditorView.updateState (index.es.js:4885)
    at Editor.dispatchTransaction (tiptap-core.esm.js:3102)
    at EditorView.dispatch (index.es.js:5162)
    at sync-plugin.js:282
    at ProsemirrorBinding.mux (mutex.js:35)
    at ProsemirrorBinding._forceRerender (sync-plugin.js:278)
    at sync-plugin.js:134

just noticed,... the same is true for setMeta which calls eventloop.timeout:

index.es.js:4914 Uncaught TypeError: Cannot read properties of null (reading 'matchesNode')
    at EditorView.updateStateInner (index.es.js:4914)
    at EditorView.updateState (index.es.js:4885)
    at Editor.dispatchTransaction (tiptap-core.esm.js:3102)
    at EditorView.dispatch (index.es.js:5162)
    at lib.js:30
    at Map.forEach (<anonymous>)
    at updateMetas (lib.js:25)

To Reproduce
Steps to reproduce the behavior:

  • unregister a plugin right before the editor get's destroyed
  • watch binding._forceRenderer() throw an error

Expected behavior
setTimeout handler should get cleared on destroy.

Environment Information

  • Browser: Chromium
  • "y-prosemirror": "^1.0.9"
  • "y-protocols": "^1.0.5"
  • "@tiptap/core": "^2.0.0-beta.122"
  • "@tiptap/extension-collaboration": "^2.0.0-beta.25"

Additional Information

I currently monkeypatch the plugin:

const fragment = this.options.fragment
                         ? this.options.fragment
                         : this.options.document.getXmlFragment(this.options.field);
const syncPlugin: Plugin = ySyncPlugin(fragment);
let changedInitialContent = false;
let handler: number;
syncPlugin.spec.view = view => {
    const binding = new ProsemirrorBinding(fragment, view);
    // Make sure this is called in a separate context
    if (handler) {
        clearTimeout(handler);
    }
    handler = setTimeout(() => {
        binding._forceRerender();
        view.dispatch(view.state.tr.setMeta(ySyncPluginKey, { binding }));
    }, 0) as unknown as number;
    return {
        update: () => {
            const pluginState = syncPlugin.getState(view.state);
            if (pluginState.snapshot == null && pluginState.prevSnapshot == null) {
                if (changedInitialContent || view.state.doc.content.findDiffStart(view.state.doc.type.createAndFill().content) !== null) {
                    changedInitialContent = true;
                    binding._prosemirrorChanged(view.state.doc);
                }
            }
        },
        destroy: () => {
            clearTimeout(handler);
            binding.destroy();
        }
    };
};

If I got time, I will prepare a PR for it right away :)

Remote selection not properly rendered.

Description from a user: We just found a weird awareness behavior. If user select text in a client, other client can see the correct awareness with the same range selected, but if user select a node ( which we implemented as prosemirror nodeview ), the other client will see the wrong awareness. Just as above recorded video, if user select the resources node, other user will show the following text is selected.

Basically, the issue can be reproduced by selecting an


element. The remote selection of that cursor information is rendered with a offset, not selecting the target element.

Tests on master are failing

Describe the bug
npm run test fails on master, because tests run as part of prepublish it's not possible to publish the project. Seems similar to #25.

To Reproduce
Steps to reproduce the behavior:

  1. git clone https://github.com/yjs/y-prosemirror.git
  2. cd y-prosemirror
  3. npm install
  4. npm run test

Expected behavior
Tests on master should pass.

Screenshots
image

Environment Information

  • Node.js: v12.16.1

Get mapping without sync-plugin

Checklist

[ ] Are you reporting a bug? Use github issues for bug reports and feature requests. For general questions, please use https://discuss.yjs.dev/
[ ] 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/

Is your feature request related to a problem? Please describe.
I want to use relativePositionToAbsoultePosition and absolutePositionToRelativePosition at the websocket server side. These helper functions require a mapping parameter. Currently, I just copy and paste createNodeFromYElement and its related functions from sync-plugin.js but since it's private API. I want to ask is it possible to expose a public API to do get the mapping from Y.js document?

Describe the solution you'd like
Expose a helper function to get the mapping

Describe alternatives you've considered

Additional context

Feature: need absolutePositionToRelativePosition method support CellSelection of Prosemirror-tables

Is your feature request related to a problem?
absolutePositionToRelativePosition and relativePositionToAbsolutePosition calculate anchor and head inaccurately when selection is CellSelection of prosemirror-tables

Describe the solution you'd like
when editor current selection is instance of CellSelection of table, getRelativeSelection and restoreRelativeSelection can restore the cellSelection correctly.

Additional context
image
the problem not comes from the source code : textSelection.create(xxx), cus I have changed that by telling the type of last selection, the code is as blow:
image

I found that the anchor and head is not correct at all when selection is instance of CellSelection, seems absolutePositionToRelativePosition not get correct relativePosition.. however i do not have a clue how to modify the method of
absolutePositionToRelativePosition..... : (

Passing colors to ySync plugin does not change cursor colors

Describe the bug

getUserColor is never called, passing colors ColorDef into the plugin has no effect.

To Reproduce
Steps to reproduce the behavior:

  1. Pass an array of ColorDef's into the y-sync plugin as options
  2. Connect multiple users
  3. All users receive the same color, getUserColor method is never called if you place a breakpoint inside it

Expected behavior

Different connected users are assigned colors – getUserColor is called

Environment Information

Additional context
Add any other context about the problem here.

Prosemirror SelectedNode Issue

I checked everywhere and there is no solution to it.

Describe the bug
Prosemirror provides the ability to create NodeSelection. But when content is updated through YJS. The node selection gets removed and become the TextSelection.

To Reproduce
Steps to reproduce the behavior:

  1. Go to Y-Prosemirror Demo
  2. Insert an Image and click on the Image, the Image shows a border, indication of the NodeSelection.
  3. Now if The other collaborator does any change in the document. The border goes away and it creates a TextSelection of that image.
  4. This behaviour is very weird because I am showing a toolbar on Image Selection and that gets disappear every time.

Expected behavior
Ideally it should retain the NodeSelection and not convert it to TextSelection.

Environment Information
Everywhere

Additional context
I can detect the changes and move TextSelection back to NodeSelection, that requires an additional update to the state and behaviour will not be smooth at all. That doesn't seem the right way to me. Ideally y-prosemirror should handle this scenario itself.

Repo has implicit dependencies on lib0/y-protocols

Describe the bug
Repo has implicit dependencies on lib0/y-protocols, it's not clear exactly what the requirements are to work on the repo independently.

To Reproduce
Steps to reproduce the behavior:

  1. clone repo
  2. run npm install
  3. run npm run dist or npm run debug
  4. Fails with error
[!] Error: Could not load /Users/tom/projects/y-prosemirror/../lib0/testing.js (imported by test/index.js): ENOENT: no such file or directory, open '/Users/tom/projects/y-prosemirror/../lib0/testing.js'

Expected behavior

The repo should be useable and allow for development without additional repos implicitly required. Or, the development instructions should make it clear what the setup should be to allow for local development.

Screenshots

image

Additional context

Working on a fork to add additional functionality

Can't run in node

Hi @dmonad. We are trying to use your this binding from node (this function particularly), but found some problems on require:

> const yProsemirror = require('y-prosemirror')
Thrown:
/home/ejp4/dev/testing-y-prosemirror/node_modules/y-protocols/awareness.js:5
import * as encoding from 'lib0/encoding.js'
       ^

SyntaxError: Unexpected token *

I think this PR should solve it.

Thanks.

Editor is not fully in sync when applying custom marks

Describe the bug
I've created a custom plugin to apply a custom mark for highlighting text, and it turns out when another mark is applied on a selected text that has the custom mark, the mark will be partially applied on the text as shown in the gif below. Bold is partially applied on the the other side, while it's fully applied on the side where it was applied.
prosemirror

Here's the code for the custom mark

const highlightTextPlugin = shouldHighlight => {
  return new Plugin({
    props: {
      handleKeyDown(view) {
        const { state, dispatch } = view;
        let { schema, selection, storedMarks } = state;
        let { $cursor } = selection;

        const markType = schema.marks[MARK_TEXT_HIGHLIGHT];
        if (!$cursor) return false;

        if (markType.isInSet(storedMarks || $cursor.marks())) {
          if (!shouldHighlight) {
            dispatch(state.tr.removeStoredMark(markType));
          }
        } else {
          if (shouldHighlight) {
            dispatch(state.tr.addStoredMark(markType.create({
              highlightColor: HIGHLIGHT_COLOR
            })));
          }
        }

        return false;
      },
    },
  });
};
 const marks = {
...,
  [MARK_TEXT_HIGHLIGHT]: {
    attrs: {
      highlightColor: '',
    },
    inline: true,
    group: 'inline',
    parseDOM: [
      {
        tag: 'span[style*=background-color]',
        getAttrs: (dom) => {
          const { backgroundColor } = dom.style;
          return {
            highlightColor: backgroundColor,
          };
        },
      },
    ],
    toDOM(node) {
      const { highlightColor } = node.attrs;
      let style = '';
      if (highlightColor) {
        style += `background-color: ${highlightColor};`;
      }
      return ['span', { style }, 0];
    },
  },
}
  const plugins = [
      ySyncPlugin(type),
      yCursorPlugin(provider.current.awareness),
      yUndoPlugin(),
      highlightTextPlugin(shouldHighlight),
]

Tests occassionally fail

While working on a different issue, I noticed the tests of y-prosemirror occasionally fail, possibly indicating a bug.

To reproduce, simply run the tests multiple times, or change the testRepeatGenerateProsemirrorChanges300 to run 10 times:

export const testRepeatGenerateProsemirrorChanges300 = tc => {
  for (let i = 0; i < 10; i++) {
    checkResult(applyRandomTests(tc, pmChanges, 300, createNewProsemirrorView))
  }
}

The error I get:

[3/3] prosemirror: repeat generate prosemirror changes300
  X Objects have a different number of attributes obj["content"][3]["content"][0]["content"][0]["content"][0]["content"][0]["content"][15]["content"][4]["content"][2]["content"][7]["content"][2]

onError handler for invalid nodes

Is your feature request related to a problem? Please describe.

Sometimes the content that is passed into the editor is invalid. It may have been valid at the time it was written but the end-user is opening the editor for the first time in months. Nodes and marks get removed from the schema, or perhaps changed from block to inline, or given new attributes etc.

We need a way of handling these changes, currently the sync plugin removes any nodes that are not "valid" to the schema, see:

// an error occured while creating the node. This is probably a result of a concurrent action.
/** @type {Y.Doc} */ (el.doc).transact(transaction => {
/** @type {Y.Item} */ (el._item).delete(transaction)
}, ySyncPluginKey)
mapping.delete(el)
return null

Describe the solution you'd like

Remirror offers an API for this problem that could be a model, I think that the sync plugin should expose something similar – although it seems like it would be neccessary to perform operations on the CRDT items rather than Prosemirror nodes/transforms?

https://remirror.io/docs/concepts/error-handling/

The default handling could be to remove, as today.

Additional context

Thoughts?

Abstraction for multi-line decorations

Describe the bug
We’re trying to set up annotations with tiptap 2 + y-prosemirror and good news first: It’s working.

We’re storing the annotations and their relative position in a Y.js map. For changes within the same editor we just apply the ProseMirror mapping to the deocoration. When Y.js updates the document, we’re recalculating the absolute position with relativePositionToAbsolutePosition.

Unfortunately, there are few cases where that functions returns unexpected results. I’m not really sure if we’re using it wrong, or if it’s a bug in y-prosemirror.

Steps to reproduce the behavior

  1. Go to https://sbdbm.csb.app/
  2. Add a selection to the above editor
  3. Click on the “comment“ button and add some text
  4. The editor and the one below should both have the same annotation
  5. Click before or in the middle of the annotation and hit Enter
  6. Both editors have different annotations.

Expected behavior
Somehow relativePositionToAbsolutePosition doesn’t seem to work well when the nodes are splitted. I’d expect to get the position inside of the newly created node, but all results seem to be stuck inside the first node.

GIF
annotations

Environment Information

  • Chrome
  • y-prosemirror 1.0.7
  • yjs 13.5.4

Additional context
Here is how we store the relative positions of an annotation:
https://codesandbox.io/s/tiptap-collaboration-annotation-sbdbm?file=/src/extension/AnnotationState.ts:1157-1713

And here is how we create decorations from those relative positions:
https://codesandbox.io/s/tiptap-collaboration-annotation-sbdbm?file=/src/extension/AnnotationState.ts:2224-2767

I hope you’ll just say we need to use another utility function and it’s working fine. :) Anyway, I know it’s pretty complex, so let me know what I can do to make debugging easier for you.

Error on relativePositionToAbsolutePosition (local)

Hi @dmonad, excellent work with this binding!. We are trying to start testing it and can't run demo locally because of next error:

y-prosemirror.js:297 Uncaught TypeError: Cannot read property 'constructor' of undefined
    at relativePositionToAbsolutePosition (y-prosemirror.js:297)
    at y-prosemirror.js:407
    at ProsemirrorBinding.mux (mutex.js:29)
    at ProsemirrorBinding._typeChanged (y-prosemirror.js:398)
    at callAll (function.js:11)
    at callEventHandlerListeners (yjs.mjs:341)
    at yjs.mjs:1058
    at Map.forEach (<anonymous>)
    at transact (yjs.mjs:1047)
    at Doc.transact (yjs.mjs:1200)

Steps to reproduce:

git clone [email protected]:y-js/yjs-demos.git
cd yjs-demos
yarn
yarn start

Go to `ProseMirror Binding` and start typing.

This demo is working perfectly on your live site.

Thanks in advance!

Did not deal with the problem of overlapping marks with same name

Describe the bug
Prosemirror supports overlapping marks with same name(as long as they have different attributes):

excludes: ?⁠string
Determines which other marks this mark can coexist with. Should be a space-separated strings naming other marks or groups of marks. When a mark is added to a set, all marks that it excludes are removed in the process. If the set contains any mark that excludes the new mark but is not, itself, excluded by the new mark, the mark can not be added an the set. You can use the value "_" to indicate that the mark excludes all marks in the schema.Defaults to only being exclusive with marks of the same type. You can set it to an empty string (or any string not containing the mark's own name) to allow multiple marks of a given type to coexist (as long as they have different attributes).

https://prosemirror.net/docs/ref/#model.MarkSpec.excludes

But prosemirror-binding seems didn't handle it well, i have found the problem code line:

pattrs[mark.type.name] = mark.attrs

If a prosemirror text with the marks:[{type: 'comment', attrs: {id: 1}}, {type: 'comment', attrs: {id: 2}}]

It will be convert to:{comment: {id: 2}}

You can see lost a mark.

Y-prosemirror missing doc attrs

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

Describe the bug
The prosemirrorJSONToYDoc function removes all doc.attrs on conversion.

Any Prosemirror Node can have attrs including state.doc (See: https://prosemirror.net/docs/ref/#model.Node). I wrote a post on it here… (It requires a custom step to set) https://discuss.prosemirror.net/t/changing-doc-attrs/784/18

Currently, doc.attrs are reset back to the defaults configured in the prosemirror schema. This also means that the doc.attrs are excluded from any collaboration within the document.

More context here: https://discuss.yjs.dev/t/y-prosemirror-missing-doc-attrs/510/4

To Reproduce

See: https://codesandbox.io/s/y-prosemirror-doc-attrs-forked-d3rn6?file=%2Fsrc%2Findex.js

Expected behavior
doc.attrs should be maintained and should be part of the collaborative Y.Doc.

Screenshots
NA

Environment Information
NA

Additional context
NA

Cursor-Plugin check for isChangeOrigin is always undefined

Describe the bug
The cursor-plugin checks for isChangeOrigin by looking at the sync plugin's state (ySyncPluginKey.getState(newState))

I believe this should actually be looking at the ysync transaction meta for this field (tr.getMeta(ySyncPluginKey))

In my testing, the former is always undefined, where the latter is true for cases where the doc is changed by another browser instance.

Creating new decorations still works but I assume it is less performant.

To Reproduce
Steps to reproduce the behavior:

  1. Add a console.log to yCursorPlugin > state > apply with the value of ySyncPluginKey.getState(newState)
  2. Open a document in 2 different browser windows and type in one of them
  3. Observe that the value is always undefined

Expected behavior
The isChangeOrigin check properly reflects if the doc was changed by another user.

Additional context
Discovered this while trying to mimic the cursor plugin behavior in our own plugin.

Updating attributes is not working when there is no text content and using a custom document content expression

Describe the bug
Updating attributes is not working when there is no text content. But this only happens when using a custom document content expression. In my example (gif below) I’m using content: 'taskList'. When using the default content: 'block+' everything works as expected.

To Reproduce
Steps to reproduce the behavior:

  1. Use a custom content expression for document
  2. Create a document without text content
  3. Update attributes of a node without text content

Expected behavior
Attributes should always be synced.

Screenshots
pv5nb-j3j4r

Environment Information

Provide esm export

Checklist

[ ] Are you reporting a bug? Use github issues for bug reports and feature requests. For general questions, please use https://discuss.yjs.dev/
[ ] 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/

Is your feature request related to a problem? Please describe.
We were using esm in our application running on Node.js. And we noticed that an instanceof condition will always fail in Y.js. Because Y.js export two version cjs and esm. Our application will use esm version to create data structure and y-prosemirror will use cjs version. The instanceof condition will never pass as the two versions are difference in Node.js. It will be better to provide esm version of y-prosemirror for consistent in my opinion.

Describe the solution you'd like
Provide esm export

Describe alternatives you've considered

Additional context

TypeError: Cannot read property 'mapping' of null

Hello, this is the first issue that I do, first of all I would like to thank the amazing and brilliant work that has been done for the development of this library. It is a very powerful library and it has somehow saved me.

Description of the bug

I am currently using the prosemirror editor together with Yjs in an application made in React. In this application I have developed my own component that creates a tiptap editor (uses prosemirror, I was guided by the examples recommended in the documentation), this since I need to be able to add and remove editors dynamically. The error occurs on line 127 of the file "cursors-plugin.js" of the library "y-prosemirror" located in "[...]/node_modules/y-prosemirror/src/plugins/cursor-plugin.js: 127". And it happens when a user adds a new editor (input) while another user is editing in another editor (input).

To reproduce
Due to the context, it is a case that is perhaps difficult to reproduce, however broadly the steps to reproduce the behavior are:

  1. Create a project in React.
  2. Create a context that stores the y-document and provider.
  3. Create a component that instantiates a new TipTap editor in the document.
  4. Generate for each element of an array (it can be an array with numbers) a component (the one that was created previously).
  5. Create a button to add a new element to the array.
  6. Open two tabs in the browser.
  7. In a tab, leave the cursor inside one of the editors already created according to the array.
  8. In the other tab, press the button that adds a new element to the array.
  9. See the error in the console.

Expected behavior
The desired behavior is that if a user is editing a field and another user adds a new element to an array (thus a new editor is generated), the application does not break and that user who was editing a field can continue editing it.

Screenshots

image

Environment Information

  • Browser: Chrome.
  • Back-end: Node.js v12.18.3
  • The dependencies used in this project are:
  • "dependencies": {
    "@material-ui/core": "^4.11.3",
    "@material-ui/icons": "^4.11.2",
    "@material-ui/lab": "^4.0.0-alpha.57",
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.3",
    "@testing-library/user-event": "^12.6.2",
    "@tiptap/core": "^2.0.0-beta.5",
    "@tiptap/extension-character-count": "^2.0.0-beta.1",
    "@tiptap/extension-collaboration": "^2.0.0-beta.4",
    "@tiptap/extension-collaboration-cursor": "^2.0.0-beta.4",
    "@tiptap/extension-document": "^2.0.0-beta.1",
    "@tiptap/extension-paragraph": "^2.0.0-beta.1",
    "@tiptap/extension-text": "^2.0.0-beta.1",
    "@tiptap/react": "^2.0.0-beta.4",
    "@tiptap/starter-kit": "^2.0.0-beta.4",
    "chart.js": "^2.9.4",
    "query-string": "^6.14.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-indiana-drag-scroll": "^1.8.0",
    "react-query": "^3.6.0",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.1",
    "socket.io-client": "^3.1.1",
    "web-vitals": "^0.2.4",
    "y-websocket": "^1.3.11",
    "yjs": "^13.5.3"
    },

Proposed Solution
The solution that worked for me has been to change line 127 of the file "[...]/node_modules/y-prosemirror/src/plugins/cursor-plugin.js" which was like this:

} else if (current.cursor != null && relativePositionToAbsolutePosition (ystate.doc, ystate.type, Y.createRelativePositionFromJSON (current.cursor.anchor), ystate.binding.mapping) !== null) {

Adding the validation "ystate.binding!= Null":

} else if (current.cursor != null && ystate.binding != null && relativePositionToAbsolutePosition (ystate.doc, ystate.type, Y.createRelativePositionFromJSON (current.cursor.anchor), ystate.binding.mapping) !== null) {

However, I would like to have an answer or opinion as to whether what I just did is okay or not. Thank you very much in advance for your time.

Add meta to all ysync triggered transactions

Is your feature request related to a problem? Please describe.
If you want to track transactions and depend on knowing what to intercept and what to pass, having some way of knowing what plugin created what transaction is quite crucial.

Describe the solution you'd like
So as of now, the ySync triggers some transactions with its own pluginKey to notify itself to update its state. However, it also triggers other transactions that modify the document without any meta fields added. For example, the _forceRerender replaces the whole document and it's quite impossible to detect where it originated from. In my own repo, I added a field tr.setMeta('_forceRerender', true) to be able to omit those transactions as non-user originated.

Describe alternatives you've considered
The other solution is to add meta to all user-created transactions yourself but that seems quite burdensome and flaky.

Additional context
na

Scroll position changes when other clients are editing

To Reproduce

  • Create a doc long enough to scroll
  • In one client place your cursor at the bottom of the screen and scroll to the top (common behavior when reading)
  • In another client type at the top of the doc
  • The scroll position in the first client will jump to the bottom

Screencast

From the y-prosemirror demo site:

Kapture 2020-12-08 at 15 01 08

I tracked it down to this line – could you enlighten in which circumstances this is supposed to trigger? It feels like it would be fine if remote edits never caused the local scroll state to change.

if (this.beforeTransactionSelection !== null && this._isLocalCursorInView()) {
tr.scrollIntoView()
}

Error: Cannot find module 'y-protocols/dist/awareness.cjs'

Describe the bug
I'm desperate. I can't understand the nature of this bug. This happens in AWS Elastic Beanstalk Linux environment. In my local nodejs v14 environment on my MacOS machine it all works without errors. I'm developing a collaboration application based on @hocuspocus backend solution. This backend uses the @hocuspocus/transformer package that requires y-prosemirror.
I compile source files to js with typescript using "CommonJS" module option. When I deploy compiled code to Beanstalk I get this error:

Error: Cannot find module 'y-protocols/dist/awareness.cjs'
Require stack:
- /var/app/current/node_modules/y-prosemirror/dist/y-prosemirror.cjs
- /var/app/current/node_modules/@hocuspocus/transformer/dist/hocuspocus-transformer.cjs
- /var/app/current/lib/index.js
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:815:15)
at Function.Module._load (internal/modules/cjs/loader.js:667:27)
at Module.require (internal/modules/cjs/loader.js:887:19)
at require (internal/modules/cjs/helpers.js:74:18)
at Object.<anonymous> (/var/app/current/node_modules/y-prosemirror/dist/y-prosemirror.cjs:8:1)
at Module._compile (internal/modules/cjs/loader.js:999:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
at Module.load (internal/modules/cjs/loader.js:863:32)
at Function.Module._load (internal/modules/cjs/loader.js:708:14)
at Module.require (internal/modules/cjs/loader.js:887:19) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'/var/app/current/node_modules/y-prosemirror/dist/y-prosemirror.cjs',
'/var/app/current/node_modules/@hocuspocus/transformer/dist/hocuspocus-transformer.cjs',
'/var/app/current/lib/index.js'
]
}

To Reproduce
Well, I'll try to describe the process...

  1. Create AWS Elastic Beanstalk application.
  2. Write some ts code that uses @hocuspocus/transformer TiptapTransformer.toYdoc function and compile it to js.
    Here is a part of tsconfig.json:
{
...
  "compilerOptions": {
      "esModuleInterop": true,
      "module": "CommonJS",
      "noImplicitAny": false,
      "noImplicitReturns": true,
      "noUnusedLocals": true,
      "outDir": "lib",
      "sourceMap": false,
      "strict": false,
      "target": "ES2017",
      "skipLibCheck": true
    }
...
}
  1. Upload this code as a zip file to Beanstalk.
  2. See Health "Severe" status and open logs to look at the error.

Expected behavior
I wanted my application to work on AWS Elastic Beanstalk as it did locally.

Environment Information

Additional context
Could this be related? yjs/y-protocols#3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.