y-prosemirror
Advanced tools
Comparing version 1.1.2 to 1.1.3
export function defaultCursorBuilder(user: any): HTMLElement; | ||
export function defaultSelectionBuilder(user: any): import('prosemirror-view').DecorationAttrs; | ||
export function createDecorations(state: any, awareness: Awareness, createCursor: any, createSelection: any): any; | ||
export function createDecorations(state: any, awareness: Awareness, createCursor: (arg0: { | ||
name: string; | ||
color: string; | ||
}) => Element, createSelection: (arg0: { | ||
name: string; | ||
color: string; | ||
}) => import('prosemirror-view').DecorationAttrs): any; | ||
export function yCursorPlugin(awareness: Awareness, { cursorBuilder, selectionBuilder, getSelection }?: { | ||
@@ -5,0 +11,0 @@ cursorBuilder: (arg0: any) => HTMLElement; |
{ | ||
"name": "y-prosemirror", | ||
"version": "1.1.2", | ||
"version": "1.1.3", | ||
"description": "Prosemirror bindings for Yjs", | ||
@@ -58,7 +58,7 @@ "main": "./dist/y-prosemirror.cjs", | ||
"peerDependencies": { | ||
"yjs": "^13.5.38", | ||
"y-protocols": "^1.0.1", | ||
"prosemirror-model": "^1.7.1", | ||
"prosemirror-state": "^1.2.3", | ||
"prosemirror-view": "^1.9.10" | ||
"prosemirror-view": "^1.9.10", | ||
"y-protocols": "^1.0.1", | ||
"yjs": "^13.5.38" | ||
}, | ||
@@ -78,3 +78,3 @@ "devDependencies": { | ||
"rollup": "^2.59.0", | ||
"standard": "^12.0.1", | ||
"standard": "^17.0.0", | ||
"typescript": "^3.9.10", | ||
@@ -81,0 +81,0 @@ "y-protocols": "^1.0.5", |
@@ -1,7 +0,10 @@ | ||
import * as Y from 'yjs' | ||
import { Decoration, DecorationSet } from 'prosemirror-view' // eslint-disable-line | ||
import { Plugin } from 'prosemirror-state' // eslint-disable-line | ||
import { Awareness } from 'y-protocols/awareness' // eslint-disable-line | ||
import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta } from '../lib.js' | ||
import { Decoration, DecorationSet } from "prosemirror-view"; // eslint-disable-line | ||
import { Plugin } from "prosemirror-state"; // eslint-disable-line | ||
import { Awareness } from "y-protocols/awareness"; // eslint-disable-line | ||
import { | ||
absolutePositionToRelativePosition, | ||
relativePositionToAbsolutePosition, | ||
setMeta | ||
} from '../lib.js' | ||
import { yCursorPluginKey, ySyncPluginKey } from './keys.js' | ||
@@ -17,3 +20,3 @@ | ||
*/ | ||
export const defaultCursorBuilder = user => { | ||
export const defaultCursorBuilder = (user) => { | ||
const cursor = document.createElement('span') | ||
@@ -39,6 +42,6 @@ cursor.classList.add('ProseMirror-yjs-cursor') | ||
*/ | ||
export const defaultSelectionBuilder = user => { | ||
export const defaultSelectionBuilder = (user) => { | ||
return { | ||
style: `background-color: ${user.color}70`, | ||
class: `ProseMirror-yjs-selection` | ||
class: 'ProseMirror-yjs-selection' | ||
} | ||
@@ -52,9 +55,19 @@ } | ||
* @param {Awareness} awareness | ||
* @param {function({ name: string, color: string }):Element} createCursor | ||
* @param {function({ name: string, color: string }):import('prosemirror-view').DecorationAttrs} createSelection | ||
* @return {any} DecorationSet | ||
*/ | ||
export const createDecorations = (state, awareness, createCursor, createSelection) => { | ||
export const createDecorations = ( | ||
state, | ||
awareness, | ||
createCursor, | ||
createSelection | ||
) => { | ||
const ystate = ySyncPluginKey.getState(state) | ||
const y = ystate.doc | ||
const decorations = [] | ||
if (ystate.snapshot != null || ystate.prevSnapshot != null || ystate.binding === null) { | ||
if ( | ||
ystate.snapshot != null || ystate.prevSnapshot != null || | ||
ystate.binding === null | ||
) { | ||
// do not render cursors while snapshot is active | ||
@@ -78,4 +91,14 @@ return DecorationSet.create(state.doc, []) | ||
} | ||
let anchor = relativePositionToAbsolutePosition(y, ystate.type, Y.createRelativePositionFromJSON(aw.cursor.anchor), ystate.binding.mapping) | ||
let head = relativePositionToAbsolutePosition(y, ystate.type, Y.createRelativePositionFromJSON(aw.cursor.head), ystate.binding.mapping) | ||
let anchor = relativePositionToAbsolutePosition( | ||
y, | ||
ystate.type, | ||
Y.createRelativePositionFromJSON(aw.cursor.anchor), | ||
ystate.binding.mapping | ||
) | ||
let head = relativePositionToAbsolutePosition( | ||
y, | ||
ystate.type, | ||
Y.createRelativePositionFromJSON(aw.cursor.head), | ||
ystate.binding.mapping | ||
) | ||
if (anchor !== null && head !== null) { | ||
@@ -85,6 +108,16 @@ const maxsize = math.max(state.doc.content.size - 1, 0) | ||
head = math.min(head, maxsize) | ||
decorations.push(Decoration.widget(head, () => createCursor(user), { key: clientId + '', side: 10 })) | ||
decorations.push( | ||
Decoration.widget(head, () => createCursor(user), { | ||
key: clientId + '', | ||
side: 10 | ||
}) | ||
) | ||
const from = math.min(anchor, head) | ||
const to = math.max(anchor, head) | ||
decorations.push(Decoration.inline(from, to, createSelection(user), { inclusiveEnd: true, inclusiveStart: false })) | ||
decorations.push( | ||
Decoration.inline(from, to, createSelection(user), { | ||
inclusiveEnd: true, | ||
inclusiveStart: false | ||
}) | ||
) | ||
} | ||
@@ -109,69 +142,118 @@ } | ||
*/ | ||
export const yCursorPlugin = (awareness, { cursorBuilder = defaultCursorBuilder, selectionBuilder = defaultSelectionBuilder, getSelection = state => state.selection } = {}, cursorStateField = 'cursor') => new Plugin({ | ||
key: yCursorPluginKey, | ||
state: { | ||
init (_, state) { | ||
return createDecorations(state, awareness, cursorBuilder, selectionBuilder) | ||
export const yCursorPlugin = ( | ||
awareness, | ||
{ | ||
cursorBuilder = defaultCursorBuilder, | ||
selectionBuilder = defaultSelectionBuilder, | ||
getSelection = (state) => state.selection | ||
} = {}, | ||
cursorStateField = 'cursor' | ||
) => | ||
new Plugin({ | ||
key: yCursorPluginKey, | ||
state: { | ||
init (_, state) { | ||
return createDecorations( | ||
state, | ||
awareness, | ||
cursorBuilder, | ||
selectionBuilder | ||
) | ||
}, | ||
apply (tr, prevState, _oldState, newState) { | ||
const ystate = ySyncPluginKey.getState(newState) | ||
const yCursorState = tr.getMeta(yCursorPluginKey) | ||
if ( | ||
(ystate && ystate.isChangeOrigin) || | ||
(yCursorState && yCursorState.awarenessUpdated) | ||
) { | ||
return createDecorations( | ||
newState, | ||
awareness, | ||
cursorBuilder, | ||
selectionBuilder | ||
) | ||
} | ||
return prevState.map(tr.mapping, tr.doc) | ||
} | ||
}, | ||
apply (tr, prevState, oldState, newState) { | ||
const ystate = ySyncPluginKey.getState(newState) | ||
const yCursorState = tr.getMeta(yCursorPluginKey) | ||
if ((ystate && ystate.isChangeOrigin) || (yCursorState && yCursorState.awarenessUpdated)) { | ||
return createDecorations(newState, awareness, cursorBuilder, selectionBuilder) | ||
props: { | ||
decorations: (state) => { | ||
return yCursorPluginKey.getState(state) | ||
} | ||
return prevState.map(tr.mapping, tr.doc) | ||
} | ||
}, | ||
props: { | ||
decorations: state => { | ||
return yCursorPluginKey.getState(state) | ||
} | ||
}, | ||
view: view => { | ||
const awarenessListener = () => { | ||
// @ts-ignore | ||
if (view.docView) { | ||
setMeta(view, yCursorPluginKey, { awarenessUpdated: true }) | ||
}, | ||
view: (view) => { | ||
const awarenessListener = () => { | ||
// @ts-ignore | ||
if (view.docView) { | ||
setMeta(view, yCursorPluginKey, { awarenessUpdated: true }) | ||
} | ||
} | ||
} | ||
const updateCursorInfo = () => { | ||
const ystate = ySyncPluginKey.getState(view.state) | ||
// @note We make implicit checks when checking for the cursor property | ||
const current = awareness.getLocalState() || {} | ||
if (ystate.binding == null) { | ||
return | ||
const updateCursorInfo = () => { | ||
const ystate = ySyncPluginKey.getState(view.state) | ||
// @note We make implicit checks when checking for the cursor property | ||
const current = awareness.getLocalState() || {} | ||
if (ystate.binding == null) { | ||
return | ||
} | ||
if (view.hasFocus()) { | ||
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) | ||
} | ||
} | ||
if (view.hasFocus()) { | ||
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 | ||
}) | ||
awareness.on('change', awarenessListener) | ||
view.dom.addEventListener('focusin', updateCursorInfo) | ||
view.dom.addEventListener('focusout', updateCursorInfo) | ||
return { | ||
update: updateCursorInfo, | ||
destroy: () => { | ||
view.dom.removeEventListener('focusin', updateCursorInfo) | ||
view.dom.removeEventListener('focusout', updateCursorInfo) | ||
awareness.off('change', awarenessListener) | ||
awareness.setLocalStateField(cursorStateField, null) | ||
} | ||
} 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) | ||
} | ||
} | ||
awareness.on('change', awarenessListener) | ||
view.dom.addEventListener('focusin', updateCursorInfo) | ||
view.dom.addEventListener('focusout', updateCursorInfo) | ||
return { | ||
update: updateCursorInfo, | ||
destroy: () => { | ||
view.dom.removeEventListener('focusin', updateCursorInfo) | ||
view.dom.removeEventListener('focusout', updateCursorInfo) | ||
awareness.off('change', awarenessListener) | ||
awareness.setLocalStateField(cursorStateField, null) | ||
} | ||
} | ||
} | ||
}) | ||
}) |
@@ -7,3 +7,3 @@ /** | ||
import * as PModel from 'prosemirror-model' | ||
import { Plugin, TextSelection } from 'prosemirror-state' // eslint-disable-line | ||
import { Plugin, TextSelection } from "prosemirror-state"; // eslint-disable-line | ||
import * as math from 'lib0/math' | ||
@@ -16,3 +16,6 @@ import * as object from 'lib0/object' | ||
import * as Y from 'yjs' | ||
import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition } from '../lib.js' | ||
import { | ||
absolutePositionToRelativePosition, | ||
relativePositionToAbsolutePosition | ||
} from '../lib.js' | ||
import * as random from 'lib0/random' | ||
@@ -27,3 +30,8 @@ import * as environment from 'lib0/environment' | ||
*/ | ||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (snapshot.sv.has(item.id.client) && /** @type {number} */ (snapshot.sv.get(item.id.client)) > item.id.clock && !Y.isDeleted(snapshot.ds, item.id)) | ||
export const isVisible = (item, snapshot) => | ||
snapshot === undefined | ||
? !item.deleted | ||
: (snapshot.sv.has(item.id.client) && /** @type {number} */ | ||
(snapshot.sv.get(item.id.client)) > item.id.clock && | ||
!Y.isDeleted(snapshot.ds, item.id)) | ||
@@ -65,4 +73,4 @@ /** | ||
const usedColors = set.create() | ||
colorMapping.forEach(color => usedColors.add(color)) | ||
colors = colors.filter(color => !usedColors.has(color)) | ||
colorMapping.forEach((color) => usedColors.add(color)) | ||
colors = colors.filter((color) => !usedColors.has(color)) | ||
} | ||
@@ -99,3 +107,3 @@ colorMapping.set(user, random.oneOf(colors)) | ||
state: { | ||
init: (initargs, state) => { | ||
init: (_initargs, _state) => { | ||
return { | ||
@@ -124,14 +132,28 @@ type: yXmlFragment, | ||
// always set isChangeOrigin. If undefined, this is not change origin. | ||
pluginState.isChangeOrigin = change !== undefined && !!change.isChangeOrigin | ||
pluginState.isChangeOrigin = change !== undefined && | ||
!!change.isChangeOrigin | ||
if (pluginState.binding !== null) { | ||
if (change !== undefined && (change.snapshot != null || change.prevSnapshot != null)) { | ||
if ( | ||
change !== undefined && | ||
(change.snapshot != null || change.prevSnapshot != null) | ||
) { | ||
// snapshot changed, rerender next | ||
eventloop.timeout(0, () => { | ||
if (pluginState.binding == null || pluginState.binding.isDestroyed) { | ||
if ( | ||
pluginState.binding == null || pluginState.binding.isDestroyed | ||
) { | ||
return | ||
} | ||
if (change.restore == null) { | ||
pluginState.binding._renderSnapshot(change.snapshot, change.prevSnapshot, pluginState) | ||
pluginState.binding._renderSnapshot( | ||
change.snapshot, | ||
change.prevSnapshot, | ||
pluginState | ||
) | ||
} else { | ||
pluginState.binding._renderSnapshot(change.snapshot, change.snapshot, pluginState) | ||
pluginState.binding._renderSnapshot( | ||
change.snapshot, | ||
change.snapshot, | ||
pluginState | ||
) | ||
// reset to current prosemirror state | ||
@@ -141,3 +163,7 @@ delete pluginState.restore | ||
delete pluginState.prevSnapshot | ||
pluginState.binding._prosemirrorChanged(pluginState.binding.prosemirrorView.state.doc) | ||
pluginState.binding.mux(() => { | ||
pluginState.binding._prosemirrorChanged( | ||
pluginState.binding.prosemirrorView.state.doc | ||
) | ||
}) | ||
} | ||
@@ -150,3 +176,3 @@ }) | ||
}, | ||
view: view => { | ||
view: (view) => { | ||
const binding = new ProsemirrorBinding(yXmlFragment, view) | ||
@@ -165,6 +191,16 @@ if (rerenderTimeout != null) { | ||
const pluginState = plugin.getState(view.state) | ||
if (pluginState.snapshot == null && pluginState.prevSnapshot == null) { | ||
if (changedInitialContent || view.state.doc.content.findDiffStart(view.state.doc.type.createAndFill().content) !== null) { | ||
if ( | ||
pluginState.snapshot == null && pluginState.prevSnapshot == null | ||
) { | ||
if ( | ||
changedInitialContent || | ||
view.state.doc.content.findDiffStart( | ||
view.state.doc.type.createAndFill().content | ||
) !== null | ||
) { | ||
changedInitialContent = true | ||
if (pluginState.addToHistory === false && !pluginState.isChangeOrigin) { | ||
if ( | ||
pluginState.addToHistory === false && | ||
!pluginState.isChangeOrigin | ||
) { | ||
const yUndoPluginState = yUndoPluginKey.getState(view.state) | ||
@@ -179,6 +215,8 @@ /** | ||
} | ||
pluginState.doc.transact(tr => { | ||
tr.meta.set('addToHistory', pluginState.addToHistory) | ||
binding._prosemirrorChanged(view.state.doc) | ||
}, ySyncPluginKey) | ||
binding.mux(() => { | ||
pluginState.doc.transact((tr) => { | ||
tr.meta.set('addToHistory', pluginState.addToHistory) | ||
binding._prosemirrorChanged(view.state.doc) | ||
}, ySyncPluginKey) | ||
}) | ||
} | ||
@@ -204,4 +242,14 @@ } | ||
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) { | ||
const anchor = relativePositionToAbsolutePosition(binding.doc, binding.type, relSel.anchor, binding.mapping) | ||
const head = relativePositionToAbsolutePosition(binding.doc, binding.type, relSel.head, binding.mapping) | ||
const anchor = relativePositionToAbsolutePosition( | ||
binding.doc, | ||
binding.type, | ||
relSel.anchor, | ||
binding.mapping | ||
) | ||
const head = relativePositionToAbsolutePosition( | ||
binding.doc, | ||
binding.type, | ||
relSel.head, | ||
binding.mapping | ||
) | ||
if (anchor !== null && head !== null) { | ||
@@ -214,4 +262,12 @@ tr = tr.setSelection(TextSelection.create(tr.doc, anchor, head)) | ||
export const getRelativeSelection = (pmbinding, state) => ({ | ||
anchor: absolutePositionToRelativePosition(state.selection.anchor, pmbinding.type, pmbinding.mapping), | ||
head: absolutePositionToRelativePosition(state.selection.head, pmbinding.type, pmbinding.mapping) | ||
anchor: absolutePositionToRelativePosition( | ||
state.selection.anchor, | ||
pmbinding.type, | ||
pmbinding.mapping | ||
), | ||
head: absolutePositionToRelativePosition( | ||
state.selection.head, | ||
pmbinding.type, | ||
pmbinding.mapping | ||
) | ||
}) | ||
@@ -250,3 +306,6 @@ | ||
if (this.beforeTransactionSelection === null) { | ||
this.beforeTransactionSelection = getRelativeSelection(this, prosemirrorView.state) | ||
this.beforeTransactionSelection = getRelativeSelection( | ||
this, | ||
prosemirrorView.state | ||
) | ||
} | ||
@@ -308,3 +367,4 @@ } | ||
return bounding.bottom >= 0 && bounding.right >= 0 && | ||
bounding.left <= (window.innerWidth || documentElement.clientWidth || 0) && | ||
bounding.left <= | ||
(window.innerWidth || documentElement.clientWidth || 0) && | ||
bounding.top <= (window.innerHeight || documentElement.clientHeight || 0) | ||
@@ -317,3 +377,5 @@ } | ||
} | ||
this.prosemirrorView.dispatch(this._tr.setMeta(ySyncPluginKey, { snapshot, prevSnapshot })) | ||
this.prosemirrorView.dispatch( | ||
this._tr.setMeta(ySyncPluginKey, { snapshot, prevSnapshot }) | ||
) | ||
} | ||
@@ -324,5 +386,15 @@ | ||
this.mux(() => { | ||
const fragmentContent = this.type.toArray().map(t => createNodeFromYElement(/** @type {Y.XmlElement} */ (t), this.prosemirrorView.state.schema, this.mapping)).filter(n => n !== null) | ||
const fragmentContent = this.type.toArray().map((t) => | ||
createNodeFromYElement( | ||
/** @type {Y.XmlElement} */ (t), | ||
this.prosemirrorView.state.schema, | ||
this.mapping | ||
) | ||
).filter((n) => n !== null) | ||
// @ts-ignore | ||
const tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) | ||
const tr = this._tr.replace( | ||
0, | ||
this.prosemirrorView.state.doc.content.size, | ||
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0) | ||
) | ||
tr.setMeta(ySyncPluginKey, { snapshot: null, prevSnapshot: null }) | ||
@@ -336,6 +408,18 @@ this.prosemirrorView.dispatch(tr) | ||
this.mux(() => { | ||
const fragmentContent = this.type.toArray().map(t => createNodeFromYElement(/** @type {Y.XmlElement} */ (t), this.prosemirrorView.state.schema, this.mapping)).filter(n => n !== null) | ||
const fragmentContent = this.type.toArray().map((t) => | ||
createNodeFromYElement( | ||
/** @type {Y.XmlElement} */ (t), | ||
this.prosemirrorView.state.schema, | ||
this.mapping | ||
) | ||
).filter((n) => n !== null) | ||
// @ts-ignore | ||
const tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) | ||
this.prosemirrorView.dispatch(tr.setMeta(ySyncPluginKey, { isChangeOrigin: true })) | ||
const tr = this._tr.replace( | ||
0, | ||
this.prosemirrorView.state.doc.content.size, | ||
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0) | ||
) | ||
this.prosemirrorView.dispatch( | ||
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true }) | ||
) | ||
}) | ||
@@ -356,3 +440,3 @@ } | ||
this.mux(() => { | ||
this.doc.transact(transaction => { | ||
this.doc.transact((transaction) => { | ||
// before rendering, we are going to sanitize ops and split deleted ops | ||
@@ -362,18 +446,41 @@ // if they were deleted by seperate users. | ||
if (pud) { | ||
pud.dss.forEach(ds => { | ||
Y.iterateDeletedStructs(transaction, ds, item => {}) | ||
pud.dss.forEach((ds) => { | ||
Y.iterateDeletedStructs(transaction, ds, (_item) => {}) | ||
}) | ||
} | ||
/** | ||
* @param {'removed'|'added'} type | ||
* @param {Y.ID} id | ||
*/ | ||
const computeYChange = (type, id) => { | ||
const user = type === 'added' ? pud.getUserByClientId(id.client) : pud.getUserByDeletedId(id) | ||
const user = type === 'added' | ||
? pud.getUserByClientId(id.client) | ||
: pud.getUserByDeletedId(id) | ||
return { | ||
user, | ||
type, | ||
color: getUserColor(pluginState.colorMapping, pluginState.colors, user) | ||
color: getUserColor( | ||
pluginState.colorMapping, | ||
pluginState.colors, | ||
user | ||
) | ||
} | ||
} | ||
// Create document fragment and render | ||
const fragmentContent = Y.typeListToArraySnapshot(this.type, new Y.Snapshot(prevSnapshot.ds, snapshot.sv)).map(t => { | ||
if (!t._item.deleted || isVisible(t._item, snapshot) || isVisible(t._item, prevSnapshot)) { | ||
return createNodeFromYElement(t, this.prosemirrorView.state.schema, new Map(), snapshot, prevSnapshot, computeYChange) | ||
const fragmentContent = Y.typeListToArraySnapshot( | ||
this.type, | ||
new Y.Snapshot(prevSnapshot.ds, snapshot.sv) | ||
).map((t) => { | ||
if ( | ||
!t._item.deleted || isVisible(t._item, snapshot) || | ||
isVisible(t._item, prevSnapshot) | ||
) { | ||
return createNodeFromYElement( | ||
t, | ||
this.prosemirrorView.state.schema, | ||
new Map(), | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) | ||
} else { | ||
@@ -384,6 +491,12 @@ // No need to render elements that are not visible by either snapshot. | ||
} | ||
}).filter(n => n !== null) | ||
}).filter((n) => n !== null) | ||
// @ts-ignore | ||
const tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) | ||
this.prosemirrorView.dispatch(tr.setMeta(ySyncPluginKey, { isChangeOrigin: true })) | ||
const tr = this._tr.replace( | ||
0, | ||
this.prosemirrorView.state.doc.content.size, | ||
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0) | ||
) | ||
this.prosemirrorView.dispatch( | ||
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true }) | ||
) | ||
}, ySyncPluginKey) | ||
@@ -399,3 +512,6 @@ }) | ||
const syncState = ySyncPluginKey.getState(this.prosemirrorView.state) | ||
if (events.length === 0 || syncState.snapshot != null || syncState.prevSnapshot != null) { | ||
if ( | ||
events.length === 0 || syncState.snapshot != null || | ||
syncState.prevSnapshot != null | ||
) { | ||
// drop out if snapshot is active | ||
@@ -411,11 +527,32 @@ this.renderSnapshot(syncState.snapshot, syncState.prevSnapshot) | ||
const delType = (_, type) => this.mapping.delete(type) | ||
Y.iterateDeletedStructs(transaction, transaction.deleteSet, struct => struct.constructor === Y.Item && this.mapping.delete(/** @type {Y.ContentType} */ (/** @type {Y.Item} */ (struct).content).type)) | ||
Y.iterateDeletedStructs( | ||
transaction, | ||
transaction.deleteSet, | ||
(struct) => | ||
struct.constructor === Y.Item && | ||
this.mapping.delete( | ||
/** @type {Y.ContentType} */ (/** @type {Y.Item} */ (struct) | ||
.content).type | ||
) | ||
) | ||
transaction.changed.forEach(delType) | ||
transaction.changedParentTypes.forEach(delType) | ||
const fragmentContent = this.type.toArray().map(t => createNodeIfNotExists(/** @type {Y.XmlElement | Y.XmlHook} */ (t), this.prosemirrorView.state.schema, this.mapping)).filter(n => n !== null) | ||
const fragmentContent = this.type.toArray().map((t) => | ||
createNodeIfNotExists( | ||
/** @type {Y.XmlElement | Y.XmlHook} */ (t), | ||
this.prosemirrorView.state.schema, | ||
this.mapping | ||
) | ||
).filter((n) => n !== null) | ||
// @ts-ignore | ||
let tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) | ||
let tr = this._tr.replace( | ||
0, | ||
this.prosemirrorView.state.doc.content.size, | ||
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0) | ||
) | ||
restoreRelativeSelection(tr, this.beforeTransactionSelection, this) | ||
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true }) | ||
if (this.beforeTransactionSelection !== null && this._isLocalCursorInView()) { | ||
if ( | ||
this.beforeTransactionSelection !== null && this._isLocalCursorInView() | ||
) { | ||
tr.scrollIntoView() | ||
@@ -428,8 +565,9 @@ } | ||
_prosemirrorChanged (doc) { | ||
this.mux(() => { | ||
this.doc.transact(tr => { | ||
updateYFragment(this.doc, this.type, doc, this.mapping) | ||
this.beforeTransactionSelection = getRelativeSelection(this, this.prosemirrorView.state) | ||
}, ySyncPluginKey) | ||
}) | ||
this.doc.transact((tr) => { | ||
updateYFragment(this.doc, this.type, doc, this.mapping) | ||
this.beforeTransactionSelection = getRelativeSelection( | ||
this, | ||
this.prosemirrorView.state | ||
) | ||
}, ySyncPluginKey) | ||
} | ||
@@ -455,7 +593,21 @@ | ||
*/ | ||
const createNodeIfNotExists = (el, schema, mapping, snapshot, prevSnapshot, computeYChange) => { | ||
const createNodeIfNotExists = ( | ||
el, | ||
schema, | ||
mapping, | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) => { | ||
const node = /** @type {PModel.Node} */ (mapping.get(el)) | ||
if (node === undefined) { | ||
if (el instanceof Y.XmlElement) { | ||
return createNodeFromYElement(el, schema, mapping, snapshot, prevSnapshot, computeYChange) | ||
return createNodeFromYElement( | ||
el, | ||
schema, | ||
mapping, | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) | ||
} else { | ||
@@ -478,7 +630,21 @@ throw error.methodUnimplemented() // we are currently not handling hooks | ||
*/ | ||
const createNodeFromYElement = (el, schema, mapping, snapshot, prevSnapshot, computeYChange) => { | ||
const createNodeFromYElement = ( | ||
el, | ||
schema, | ||
mapping, | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) => { | ||
const children = [] | ||
const createChildren = type => { | ||
const createChildren = (type) => { | ||
if (type.constructor === Y.XmlElement) { | ||
const n = createNodeIfNotExists(type, schema, mapping, snapshot, prevSnapshot, computeYChange) | ||
const n = createNodeIfNotExists( | ||
type, | ||
schema, | ||
mapping, | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) | ||
if (n !== null) { | ||
@@ -488,5 +654,12 @@ children.push(n) | ||
} else { | ||
const ns = createTextNodesFromYText(type, schema, mapping, snapshot, prevSnapshot, computeYChange) | ||
const ns = createTextNodesFromYText( | ||
type, | ||
schema, | ||
mapping, | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) | ||
if (ns !== null) { | ||
ns.forEach(textchild => { | ||
ns.forEach((textchild) => { | ||
if (textchild !== null) { | ||
@@ -502,3 +675,4 @@ children.push(textchild) | ||
} else { | ||
Y.typeListToArraySnapshot(el, new Y.Snapshot(prevSnapshot.ds, snapshot.sv)).forEach(createChildren) | ||
Y.typeListToArraySnapshot(el, new Y.Snapshot(prevSnapshot.ds, snapshot.sv)) | ||
.forEach(createChildren) | ||
} | ||
@@ -509,5 +683,9 @@ try { | ||
if (!isVisible(/** @type {Y.Item} */ (el._item), snapshot)) { | ||
attrs.ychange = computeYChange ? computeYChange('removed', /** @type {Y.Item} */ (el._item).id) : { type: 'removed' } | ||
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' } | ||
attrs.ychange = computeYChange | ||
? computeYChange('added', /** @type {Y.Item} */ (el._item).id) | ||
: { type: 'added' } | ||
} | ||
@@ -520,3 +698,3 @@ } | ||
// 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.Doc} */ (el.doc).transact((transaction) => { | ||
/** @type {Y.Item} */ (el._item).delete(transaction) | ||
@@ -539,3 +717,10 @@ }, ySyncPluginKey) | ||
*/ | ||
const createTextNodesFromYText = (text, schema, mapping, snapshot, prevSnapshot, computeYChange) => { | ||
const createTextNodesFromYText = ( | ||
text, | ||
schema, | ||
mapping, | ||
snapshot, | ||
prevSnapshot, | ||
computeYChange | ||
) => { | ||
const nodes = [] | ||
@@ -554,3 +739,3 @@ const deltas = text.toDelta(snapshot, prevSnapshot, computeYChange) | ||
// an error occured while creating the node. This is probably a result of a concurrent action. | ||
/** @type {Y.Doc} */ (text.doc).transact(transaction => { | ||
/** @type {Y.Doc} */ (text.doc).transact((transaction) => { | ||
/** @type {Y.Item} */ (text._item).delete(transaction) | ||
@@ -572,3 +757,3 @@ }, ySyncPluginKey) | ||
const type = new Y.XmlText() | ||
const delta = nodes.map(node => ({ | ||
const delta = nodes.map((node) => ({ | ||
// @ts-ignore | ||
@@ -597,3 +782,8 @@ insert: node.text, | ||
} | ||
type.insert(0, normalizePNodeContent(node).map(n => createTypeFromTextOrElementNode(n, mapping))) | ||
type.insert( | ||
0, | ||
normalizePNodeContent(node).map((n) => | ||
createTypeFromTextOrElementNode(n, mapping) | ||
) | ||
) | ||
mapping.set(type, node) | ||
@@ -609,3 +799,6 @@ return type | ||
*/ | ||
const createTypeFromTextOrElementNode = (node, mapping) => node instanceof Array ? createTypeFromTextNodes(node, mapping) : createTypeFromElementNode(node, mapping) | ||
const createTypeFromTextOrElementNode = (node, mapping) => | ||
node instanceof Array | ||
? createTypeFromTextNodes(node, mapping) | ||
: createTypeFromElementNode(node, mapping) | ||
@@ -615,4 +808,6 @@ const isObject = (val) => typeof val === 'object' && val !== null | ||
const equalAttrs = (pattrs, yattrs) => { | ||
const keys = Object.keys(pattrs).filter(key => pattrs[key] !== null) | ||
let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] !== null).length | ||
const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null) | ||
let eq = | ||
keys.length === | ||
Object.keys(yattrs).filter((key) => yattrs[key] !== null).length | ||
for (let i = 0; i < keys.length && eq; i++) { | ||
@@ -622,3 +817,4 @@ const key = keys[i] | ||
const r = yattrs[key] | ||
eq = key === 'ychange' || l === r || (isObject(l) && isObject(r) && equalAttrs(l, r)) | ||
eq = key === 'ychange' || l === r || | ||
(isObject(l) && isObject(r) && equalAttrs(l, r)) | ||
} | ||
@@ -636,3 +832,3 @@ return eq | ||
*/ | ||
const normalizePNodeContent = pnode => { | ||
const normalizePNodeContent = (pnode) => { | ||
const c = pnode.content.content | ||
@@ -662,3 +858,10 @@ const res = [] | ||
const delta = ytext.toDelta() | ||
return delta.length === ptexts.length && delta.every((d, i) => d.insert === /** @type {any} */ (ptexts[i]).text && object.keys(d.attributes || {}).length === ptexts[i].marks.length && ptexts[i].marks.every(mark => equalAttrs(d.attributes[mark.type.name] || {}, mark.attrs))) | ||
return delta.length === ptexts.length && | ||
delta.every((d, i) => | ||
d.insert === /** @type {any} */ (ptexts[i]).text && | ||
object.keys(d.attributes || {}).length === ptexts[i].marks.length && | ||
ptexts[i].marks.every((mark) => | ||
equalAttrs(d.attributes[mark.type.name] || {}, mark.attrs) | ||
) | ||
) | ||
} | ||
@@ -671,7 +874,15 @@ | ||
const equalYTypePNode = (ytype, pnode) => { | ||
if (ytype instanceof Y.XmlElement && !(pnode instanceof Array) && matchNodeName(ytype, pnode)) { | ||
if ( | ||
ytype instanceof Y.XmlElement && !(pnode instanceof Array) && | ||
matchNodeName(ytype, pnode) | ||
) { | ||
const normalizedContent = normalizePNodeContent(pnode) | ||
return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), pnode.attrs) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i])) | ||
return ytype._length === normalizedContent.length && | ||
equalAttrs(ytype.getAttributes(), pnode.attrs) && | ||
ytype.toArray().every((ychild, i) => | ||
equalYTypePNode(ychild, normalizedContent[i]) | ||
) | ||
} | ||
return ytype instanceof Y.XmlText && pnode instanceof Array && equalYTextPText(ytype, pnode) | ||
return ytype instanceof Y.XmlText && pnode instanceof Array && | ||
equalYTextPText(ytype, pnode) | ||
} | ||
@@ -683,3 +894,8 @@ | ||
*/ | ||
const mappedIdentity = (mapped, pcontent) => mapped === pcontent || (mapped instanceof Array && pcontent instanceof Array && mapped.length === pcontent.length && mapped.every((a, i) => pcontent[i] === a)) | ||
const mappedIdentity = (mapped, pcontent) => | ||
mapped === pcontent || | ||
(mapped instanceof Array && pcontent instanceof Array && | ||
mapped.length === pcontent.length && mapped.every((a, i) => | ||
pcontent[i] === a | ||
)) | ||
@@ -705,3 +921,3 @@ /** | ||
if (mappedIdentity(mapping.get(leftY), leftP)) { | ||
foundMappedChild = true// definite (good) match! | ||
foundMappedChild = true // definite (good) match! | ||
} else if (!equalYTypePNode(leftY, leftP)) { | ||
@@ -726,3 +942,3 @@ break | ||
const ytextTrans = ytext => { | ||
const ytextTrans = (ytext) => { | ||
let str = '' | ||
@@ -760,12 +976,20 @@ /** | ||
const { nAttrs, str } = ytextTrans(ytext) | ||
const content = ptexts.map(p => ({ insert: /** @type {any} */ (p).text, attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks)) })) | ||
const { insert, remove, index } = simpleDiff(str, content.map(c => c.insert).join('')) | ||
const content = ptexts.map((p) => ({ | ||
insert: /** @type {any} */ (p).text, | ||
attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks)) | ||
})) | ||
const { insert, remove, index } = simpleDiff( | ||
str, | ||
content.map((c) => c.insert).join('') | ||
) | ||
ytext.delete(index, remove) | ||
ytext.insert(index, insert) | ||
ytext.applyDelta(content.map(c => ({ retain: c.insert.length, attributes: c.attributes }))) | ||
ytext.applyDelta( | ||
content.map((c) => ({ retain: c.insert.length, attributes: c.attributes })) | ||
) | ||
} | ||
const marksToAttributes = marks => { | ||
const marksToAttributes = (marks) => { | ||
const pattrs = {} | ||
marks.forEach(mark => { | ||
marks.forEach((mark) => { | ||
if (mark.type.name !== 'ychange') { | ||
@@ -786,3 +1010,6 @@ pattrs[mark.type.name] = mark.attrs | ||
export const updateYFragment = (y, yDomFragment, pNode, mapping) => { | ||
if (yDomFragment instanceof Y.XmlElement && yDomFragment.nodeName !== pNode.type.name) { | ||
if ( | ||
yDomFragment instanceof Y.XmlElement && | ||
yDomFragment.nodeName !== pNode.type.name | ||
) { | ||
throw new Error('node name mismatch!') | ||
@@ -820,3 +1047,3 @@ } | ||
// find number of matching elements from left | ||
for (;left < minCnt; left++) { | ||
for (; left < minCnt; left++) { | ||
const leftY = yChildren[left] | ||
@@ -834,3 +1061,3 @@ const leftP = pChildren[left] | ||
// find number of matching elements from right | ||
for (;right + left + 1 < minCnt; right++) { | ||
for (; right + left + 1 < minCnt; right++) { | ||
const rightY = yChildren[yChildCnt - right - 1] | ||
@@ -860,13 +1087,29 @@ const rightP = pChildren[pChildCnt - right - 1] | ||
} else { | ||
let updateLeft = leftY instanceof Y.XmlElement && matchNodeName(leftY, leftP) | ||
let updateRight = rightY instanceof Y.XmlElement && matchNodeName(rightY, rightP) | ||
let updateLeft = leftY instanceof Y.XmlElement && | ||
matchNodeName(leftY, leftP) | ||
let updateRight = rightY instanceof Y.XmlElement && | ||
matchNodeName(rightY, rightP) | ||
if (updateLeft && updateRight) { | ||
// decide which which element to update | ||
const equalityLeft = computeChildEqualityFactor(/** @type {Y.XmlElement} */ (leftY), /** @type {PModel.Node} */ (leftP), mapping) | ||
const equalityRight = computeChildEqualityFactor(/** @type {Y.XmlElement} */ (rightY), /** @type {PModel.Node} */ (rightP), mapping) | ||
if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) { | ||
const equalityLeft = computeChildEqualityFactor( | ||
/** @type {Y.XmlElement} */ (leftY), | ||
/** @type {PModel.Node} */ (leftP), | ||
mapping | ||
) | ||
const equalityRight = computeChildEqualityFactor( | ||
/** @type {Y.XmlElement} */ (rightY), | ||
/** @type {PModel.Node} */ (rightP), | ||
mapping | ||
) | ||
if ( | ||
equalityLeft.foundMappedChild && !equalityRight.foundMappedChild | ||
) { | ||
updateRight = false | ||
} else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) { | ||
} else if ( | ||
!equalityLeft.foundMappedChild && equalityRight.foundMappedChild | ||
) { | ||
updateLeft = false | ||
} else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) { | ||
} else if ( | ||
equalityLeft.equalityFactor < equalityRight.equalityFactor | ||
) { | ||
updateLeft = false | ||
@@ -878,10 +1121,22 @@ } else { | ||
if (updateLeft) { | ||
updateYFragment(y, /** @type {Y.XmlFragment} */ (leftY), /** @type {PModel.Node} */ (leftP), mapping) | ||
updateYFragment( | ||
y, | ||
/** @type {Y.XmlFragment} */ (leftY), | ||
/** @type {PModel.Node} */ (leftP), | ||
mapping | ||
) | ||
left += 1 | ||
} else if (updateRight) { | ||
updateYFragment(y, /** @type {Y.XmlFragment} */ (rightY), /** @type {PModel.Node} */ (rightP), mapping) | ||
updateYFragment( | ||
y, | ||
/** @type {Y.XmlFragment} */ (rightY), | ||
/** @type {PModel.Node} */ (rightP), | ||
mapping | ||
) | ||
right += 1 | ||
} else { | ||
yDomFragment.delete(left, 1) | ||
yDomFragment.insert(left, [createTypeFromTextOrElementNode(leftP, mapping)]) | ||
yDomFragment.insert(left, [ | ||
createTypeFromTextOrElementNode(leftP, mapping) | ||
]) | ||
left += 1 | ||
@@ -892,3 +1147,5 @@ } | ||
const yDelLen = yChildCnt - left - right | ||
if (yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y.XmlText) { | ||
if ( | ||
yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y.XmlText | ||
) { | ||
// Edge case handling https://github.com/yjs/y-prosemirror/issues/108 | ||
@@ -915,2 +1172,3 @@ // Only delete the content of the Y.Text to retain remote changes on the same Y.Text object | ||
*/ | ||
const matchNodeName = (yElement, pNode) => !(pNode instanceof Array) && yElement.nodeName === pNode.type.name | ||
const matchNodeName = (yElement, pNode) => | ||
!(pNode instanceof Array) && yElement.nodeName === pNode.type.name |
@@ -5,3 +5,5 @@ export * from './plugins/cursor-plugin.js' | ||
export * from './plugins/keys.js' | ||
export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc, | ||
prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment } from './lib.js' | ||
export { | ||
absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc, | ||
prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment | ||
} from './lib.js' |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
247024
3842