@kerebron/extension-yjs 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"CoreEditor.d.ts","sourceRoot":"","sources":["../../../src/editor/src/CoreEditor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,IAAI,IAAI,eAAe,EACvB,KAAK,MAAM,EACZ,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAkB,MAAM,8BAA8B,CAAC;AAgC/E,qBAAa,UAAW,SAAQ,WAAW;IACzC,SAAgB,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAG7C;IACF,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,cAAc,CAAiB;IAChC,IAAI,EAAG,UAAU,CAAC;IAClB,KAAK,EAAG,WAAW,CAAC;gBAEf,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM;IAyBhD,IAAW,MAAM,qBAEhB;IAEM,KAAK,IAAI,eAAe;IAIxB,GAAG,IAAI,eAAe;IAI7B,OAAO,CAAC,UAAU;IAaX,mBAAmB,CAAC,WAAW,EAAE,WAAW;IAcnD,OAAO,CAAC,YAAY;IAcb,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM;IAwC7C,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM;IAkB9B,OAAO,IAAI,WAAW;IAItB,KAAK,CAAC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,UAAU;IAOvD,KAAK,CAAC,GAAG,CAAC,EAAE,eAAe;CAMnC"}
1
+ {"version":3,"file":"CoreEditor.d.ts","sourceRoot":"","sources":["../../../src/editor/src/CoreEditor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,IAAI,IAAI,eAAe,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAkB,MAAM,8BAA8B,CAAC;AAgC/E,qBAAa,UAAW,SAAQ,WAAW;IACzC,SAAgB,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAG7C;IACF,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,cAAc,CAAiB;IAChC,IAAI,EAAG,UAAU,CAAC;IAClB,KAAK,EAAG,WAAW,CAAC;gBAEf,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM;IAyBhD,IAAW,MAAM,qBAEhB;IAEM,KAAK,IAAI,eAAe;IAIxB,GAAG,IAAI,eAAe;IAI7B,OAAO,CAAC,UAAU;IAaX,mBAAmB,CAAC,WAAW,EAAE,WAAW;IAcnD,OAAO,CAAC,YAAY;IAcb,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM;IAwC7C,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM;IAkB9B,OAAO,IAAI,WAAW;IAItB,KAAK,CAAC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,UAAU;IAOvD,KAAK,CAAC,GAAG,CAAC,EAAE,eAAe;CAMnC"}
@@ -1,5 +1,5 @@
1
1
  import { EditorView } from 'prosemirror-view';
2
- import { Node as ProseMirrorNode, } from 'prosemirror-model';
2
+ import { Node as ProseMirrorNode } from 'prosemirror-model';
3
3
  import { ExtensionManager } from './ExtensionManager.js';
4
4
  import { EditorState } from 'prosemirror-state';
5
5
  import { createNodeFromContent } from './utilities/createNodeFromContent.js';
@@ -21,12 +21,12 @@ function ensureDocSchema(doc, schema) {
21
21
  };
22
22
  // TODO fix readonly warnings
23
23
  doc.type = findNode(doc.type.name);
24
- doc.marks.forEach(mark => {
24
+ doc.marks.forEach((mark) => {
25
25
  mark.type = findMark(mark.type.name);
26
26
  });
27
- doc.descendants(node => {
27
+ doc.descendants((node) => {
28
28
  node.type = findNode(node.type.name);
29
- node.marks.forEach(mark => {
29
+ node.marks.forEach((mark) => {
30
30
  mark.type = findMark(mark.type.name);
31
31
  });
32
32
  });
@@ -182,7 +182,7 @@ export class CoreEditor extends EventTarget {
182
182
  clone(options = {}) {
183
183
  return new CoreEditor({
184
184
  ...options,
185
- extensions: [...this.options.extensions]
185
+ extensions: [...this.options.extensions],
186
186
  });
187
187
  }
188
188
  debug(doc) {
@@ -1 +1 @@
1
- {"version":3,"file":"nodeToTreeString.d.ts","sourceRoot":"","sources":["../../../src/editor/src/nodeToTreeString.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,mBAAmB,CAAC;AAYvC,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,SAAS,IAAI,EAAE,EAAE,KAAK,SAAI,EAAE,UAAU,SAAI,UAmDhG;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,QAE5C"}
1
+ {"version":3,"file":"nodeToTreeString.d.ts","sourceRoot":"","sources":["../../../src/editor/src/nodeToTreeString.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAYzC,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,SAAS,IAAI,EAAE,EACrC,KAAK,SAAI,EACT,UAAU,SAAI,UA0Df;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,QAE5C"}
@@ -13,7 +13,9 @@ export function nodeToTreeString(node, level = 0, currentPos = 0) {
13
13
  let output = '';
14
14
  if (Array.isArray(node)) {
15
15
  for (const child of node) {
16
- output += delim + nodeToTreeString(child, level + 1, currentPos).replace(/\s+$/gm, '') + '\n';
16
+ output += delim +
17
+ nodeToTreeString(child, level + 1, currentPos).replace(/\s+$/gm, '') +
18
+ '\n';
17
19
  }
18
20
  return output;
19
21
  }
@@ -44,7 +46,8 @@ export function nodeToTreeString(node, level = 0, currentPos = 0) {
44
46
  output += (delim + ' "' + trimText(node.text) + '"') + '\n';
45
47
  }
46
48
  node.forEach((child, offset) => {
47
- output += nodeToTreeString(child, level + 1, currentPos + offset + 1).replace(/\s+$/gm, '') + '\n'; // + (node.isLeaf ? 1 : 2)
49
+ output +=
50
+ nodeToTreeString(child, level + 1, currentPos + offset + 1).replace(/\s+$/gm, '') + '\n'; // + (node.isLeaf ? 1 : 2)
48
51
  });
49
52
  return output;
50
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createNodeFromContent.d.ts","sourceRoot":"","sources":["../../../../src/editor/src/utilities/createNodeFromContent.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,IAAI,IAAI,eAAe,EACvB,MAAM,EACP,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,4BAA4B,GAAG;IACzC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,QAAQ,EAC7C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,4BAA4B,GACrC,eAAe,GAAG,QAAQ,CAiC5B;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,QAAQ,EAC7C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,4BAA4B,GACrC,eAAe,GAAG,QAAQ,CAY5B"}
1
+ {"version":3,"file":"createNodeFromContent.d.ts","sourceRoot":"","sources":["../../../../src/editor/src/utilities/createNodeFromContent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,4BAA4B,GAAG;IACzC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,QAAQ,EAC7C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,4BAA4B,GACrC,eAAe,GAAG,QAAQ,CAiC5B;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,QAAQ,EAC7C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,4BAA4B,GACrC,eAAe,GAAG,QAAQ,CAY5B"}
@@ -1,4 +1,4 @@
1
- import { Fragment, Node as ProseMirrorNode, } from 'prosemirror-model';
1
+ import { Fragment, Node as ProseMirrorNode } from 'prosemirror-model';
2
2
  export function createNodeFromObject(content, schema, options) {
3
3
  try {
4
4
  // if the JSON Content is an array of nodes, create a fragment for each node
@@ -1,6 +1,6 @@
1
1
  import { Extension } from '../../editor/src/mod.js';
2
2
  import { initProseMirrorDoc, redo, undo, yUndoPlugin } from 'y-prosemirror';
3
- import { ySyncPlugin } from './SyncPlugin.js';
3
+ import { ySyncPlugin } from 'y-prosemirror';
4
4
  import { yCursorPlugin } from './yCursorPlugin.js';
5
5
  export class ExtensionYjs extends Extension {
6
6
  constructor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerebron/extension-yjs",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "module": "./esm/extension-yjs/src/ExtensionYjs.js",
6
6
  "exports": {
@@ -1,84 +0,0 @@
1
- import * as PModel from 'prosemirror-model';
2
- import { Plugin } from 'prosemirror-state';
3
- import * as Y from 'yjs';
4
- import { mutex } from 'lib0/mutex';
5
- import { EditorView } from 'prosemirror-view';
6
- import { ProsemirrorMapping } from 'y-prosemirror';
7
- import { YSyncOpts } from 'y-prosemirror';
8
- /**
9
- * @param {Y.Item} item
10
- * @param {Y.Snapshot} [snapshot]
11
- */
12
- export declare const isVisible: (item: any, snapshot: any) => any;
13
- export declare const ySyncPlugin: (yXmlFragment: Y.XmlFragment, { colors, colorMapping, permanentUserData, onFirstRender, mapping, }?: YSyncOpts) => Plugin<any>;
14
- export declare const getRelativeSelection: (pmbinding: any, state: any) => {
15
- anchor: any;
16
- head: any;
17
- };
18
- /**
19
- * Binding for prosemirror.
20
- *
21
- * @protected
22
- */
23
- export declare class ProsemirrorBinding {
24
- private yXmlFragment;
25
- private mapping;
26
- type: Y.XmlFragment;
27
- mux: mutex;
28
- private _observeFunction;
29
- private prosemirrorView;
30
- private doc;
31
- beforeTransactionSelection: {
32
- anchor: any;
33
- head: any;
34
- };
35
- afterAllTransactions: () => void;
36
- beforeAllTransactions: (arg0: Y.Doc) => void;
37
- constructor(yXmlFragment: Y.XmlFragment, mapping?: ProsemirrorMapping);
38
- /**
39
- * Create a transaction for changing the prosemirror state.
40
- *
41
- * @returns
42
- */
43
- get _tr(): import("prosemirror-state").Transaction | undefined;
44
- _isLocalCursorInView(): any;
45
- _isDomSelectionInView(): boolean;
46
- renderSnapshot(snapshot: Y.Snapshot, prevSnapshot: Y.Snapshot): void;
47
- unrenderSnapshot(): void;
48
- _forceRerender(): void;
49
- _renderSnapshot(snapshot: Y.Snapshot | Uint8Array, prevSnapshot: Y.Snapshot | Uint8Array, pluginState: Object): void;
50
- _typeChanged(events: Array<Y.YEvent<any>>, transaction: Y.Transaction): void;
51
- _prosemirrorChanged(doc: any): void;
52
- /**
53
- * View is ready to listen to changes. Register observers.
54
- * @param {any} prosemirrorView
55
- */
56
- initView(prosemirrorView: EditorView): void;
57
- destroy(): void;
58
- }
59
- /**
60
- * @private
61
- * @param {Y.XmlElement} el
62
- * @param {any} schema
63
- * @param {ProsemirrorMapping} mapping
64
- * @param {Y.Snapshot} [snapshot]
65
- * @param {Y.Snapshot} [prevSnapshot]
66
- * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
67
- * @return {PModel.Node | null} Returns node if node could be created. Otherwise it deletes the yjs type and returns null
68
- */
69
- export declare const createNodeFromYElement: (el: Y.XmlElement, schema: any, mapping: ProsemirrorMapping, snapshot: Y.Snapshot, prevSnapshot: Y.Snapshot, computeYChange: (arg0: "removed" | "added", arg1: Y.ID) => any) => PModel.Node | null;
70
- /**
71
- * Update a yDom node by syncing the current content of the prosemirror node.
72
- *
73
- * This is a y-prosemirror internal feature that you can use at your own risk.
74
- *
75
- * @private
76
- * @unstable
77
- *
78
- * @param {{transact: Function}} y
79
- * @param {Y.XmlFragment} yDomFragment
80
- * @param {any} pNode
81
- * @param {ProsemirrorMapping} mapping
82
- */
83
- export declare const updateYFragment: (y: any, yDomFragment: Y.XmlFragment, pNode: any, mapping: ProsemirrorMapping) => void;
84
- //# sourceMappingURL=SyncPlugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SyncPlugin.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/SyncPlugin.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAiB,MAAM,mBAAmB,CAAC;AAO1D,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAUzB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C;;;GAGG;AACH,eAAO,MAAM,SAAS,mCAKmB,CAAC;AA8C1C,eAAO,MAAM,WAAW,iBAAkB,CAAC,CAAC,WAAW,yEAMpD,SAAS,gBAsIX,CAAC;AA0BF,eAAO,MAAM,oBAAoB;;;CAW/B,CAAC;AAEH;;;;GAIG;AACH,qBAAa,kBAAkB;IAW3B,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IAXjB,IAAI,EAAE,CAAC,CAAC,WAAW,CAAC;IACpB,GAAG,EAAE,KAAK,CAAC;IACX,OAAO,CAAC,gBAAgB,CAA0C;IAClE,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,GAAG,CAAQ;IACnB,0BAA0B,EAAE;QAAE,MAAM,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC;IACvD,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,qBAAqB,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC;gBAGnC,YAAY,EAAE,CAAC,CAAC,WAAW,EAC3B,OAAO,GAAE,kBAA8B;IA+BjD;;;;OAIG;IACH,IAAI,GAAG,wDAEN;IAED,oBAAoB;IAYpB,qBAAqB;IA4BrB,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,QAAQ;IAS7D,gBAAgB;IAqBhB,cAAc;IA+Bd,eAAe,CACb,QAAQ,EAAE,CAAC,CAAC,QAAQ,GAAG,UAAU,EACjC,YAAY,EAAE,CAAC,CAAC,QAAQ,GAAG,UAAU,EACrC,WAAW,EAAE,MAAM;IAuFrB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW;IAuDrE,mBAAmB,CAAC,GAAG,KAAA;IAUvB;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,UAAU;IAQpC,OAAO;CAOR;AA4BD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,OAC7B,CAAC,CAAC,UAAU,UACR,GAAG,WACF,kBAAkB,YACjB,CAAC,CAAC,QAAQ,gBACN,CAAC,CAAC,QAAQ,kBACR,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG,KAC7D,MAAM,CAAC,IAAI,GAAG,IAgFhB,CAAC;AAwPF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,yBAEZ,CAAC,CAAC,WAAW,uBAElB,kBAAkB,SA4J5B,CAAC"}
@@ -1,924 +0,0 @@
1
- /**
2
- * @module bindings/prosemirror
3
- */
4
- import { createMutex } from 'lib0/mutex';
5
- import * as PModel from 'prosemirror-model';
6
- import { Plugin, TextSelection } from 'prosemirror-state'; // eslint-disable-line
7
- import * as math from 'lib0/math';
8
- import * as object from 'lib0/object';
9
- import * as set from 'lib0/set';
10
- import { simpleDiff } from 'lib0/diff';
11
- import * as error from 'lib0/error';
12
- import { ySyncPluginKey, yUndoPluginKey } from 'y-prosemirror';
13
- import * as Y from 'yjs';
14
- import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, } from 'y-prosemirror';
15
- import * as random from 'lib0/random';
16
- import * as environment from 'lib0/environment';
17
- import * as dom from 'lib0/dom';
18
- import * as eventloop from 'lib0/eventloop';
19
- /**
20
- * @param {Y.Item} item
21
- * @param {Y.Snapshot} [snapshot]
22
- */
23
- export const isVisible = (item, snapshot) => snapshot === undefined
24
- ? !item.deleted
25
- : (snapshot.sv.has(item.id.client) && /** @type {number} */
26
- (snapshot.sv.get(item.id.client)) > item.id.clock &&
27
- !Y.isDeleted(snapshot.ds, item.id));
28
- /**
29
- * Either a node if type is YXmlElement or an Array of text nodes if YXmlText
30
- * @typedef {Map<Y.AbstractType<any>, PModel.Node | Array<PModel.Node>>} ProsemirrorMapping
31
- */
32
- /**
33
- * @typedef {Object} ColorDef
34
- * @property {string} ColorDef.light
35
- * @property {string} ColorDef.dark
36
- */
37
- /**
38
- * @typedef {Object} YSyncOpts
39
- * @property {Array<ColorDef>} [YSyncOpts.colors]
40
- * @property {Map<string,ColorDef>} [YSyncOpts.colorMapping]
41
- * @property {Y.PermanentUserData|null} [YSyncOpts.permanentUserData]
42
- * @property {ProsemirrorMapping} [YSyncOpts.mapping]
43
- * @property {function} [YSyncOpts.onFirstRender] Fired when the content from Yjs is initially rendered to ProseMirror
44
- */
45
- /**
46
- * @type {Array<ColorDef>}
47
- */
48
- const defaultColors = [{ light: '#ecd44433', dark: '#ecd444' }];
49
- /**
50
- * @param {Map<string,ColorDef>} colorMapping
51
- * @param {Array<ColorDef>} colors
52
- * @param {string} user
53
- * @return {ColorDef}
54
- */
55
- const getUserColor = (colorMapping, colors, user) => {
56
- // @todo do not hit the same color twice if possible
57
- if (!colorMapping.has(user)) {
58
- if (colorMapping.size < colors.length) {
59
- const usedColors = set.create();
60
- colorMapping.forEach((color) => usedColors.add(color));
61
- colors = colors.filter((color) => !usedColors.has(color));
62
- }
63
- colorMapping.set(user, random.oneOf(colors));
64
- }
65
- return /** @type {ColorDef} */ (colorMapping.get(user));
66
- };
67
- export const ySyncPlugin = (yXmlFragment, { colors = defaultColors, colorMapping = new Map(), permanentUserData = null, onFirstRender = () => { }, mapping, } = {}) => {
68
- let initialContentChanged = false;
69
- const binding = new ProsemirrorBinding(yXmlFragment, mapping);
70
- const plugin = new Plugin({
71
- props: {
72
- editable: (state) => {
73
- const syncState = ySyncPluginKey.getState(state);
74
- return syncState.snapshot == null && syncState.prevSnapshot == null;
75
- },
76
- },
77
- key: ySyncPluginKey,
78
- state: {
79
- /**
80
- * @returns {any}
81
- */
82
- init: (_initargs, _state) => {
83
- return {
84
- type: yXmlFragment,
85
- doc: yXmlFragment.doc,
86
- binding,
87
- snapshot: null,
88
- prevSnapshot: null,
89
- isChangeOrigin: false,
90
- isUndoRedoOperation: false,
91
- addToHistory: true,
92
- colors,
93
- colorMapping,
94
- permanentUserData,
95
- };
96
- },
97
- apply: (tr, pluginState) => {
98
- const change = tr.getMeta(ySyncPluginKey);
99
- if (change !== undefined) {
100
- pluginState = Object.assign({}, pluginState);
101
- for (const key in change) {
102
- pluginState[key] = change[key];
103
- }
104
- }
105
- pluginState.addToHistory = tr.getMeta('addToHistory') !== false;
106
- // always set isChangeOrigin. If undefined, this is not change origin.
107
- pluginState.isChangeOrigin = change !== undefined &&
108
- !!change.isChangeOrigin;
109
- pluginState.isUndoRedoOperation = change !== undefined &&
110
- !!change.isChangeOrigin && !!change.isUndoRedoOperation;
111
- if (binding.prosemirrorView !== null) {
112
- if (change !== undefined &&
113
- (change.snapshot != null || change.prevSnapshot != null)) {
114
- // snapshot changed, rerender next
115
- eventloop.timeout(0, () => {
116
- if (binding.prosemirrorView == null) {
117
- return;
118
- }
119
- if (change.restore == null) {
120
- binding._renderSnapshot(change.snapshot, change.prevSnapshot, pluginState);
121
- }
122
- else {
123
- binding._renderSnapshot(change.snapshot, change.snapshot, pluginState);
124
- // reset to current prosemirror state
125
- delete pluginState.restore;
126
- delete pluginState.snapshot;
127
- delete pluginState.prevSnapshot;
128
- binding.mux(() => {
129
- binding._prosemirrorChanged(binding.prosemirrorView.state.doc);
130
- });
131
- }
132
- });
133
- }
134
- }
135
- return pluginState;
136
- },
137
- },
138
- view: (view) => {
139
- binding.initView(view);
140
- if (mapping == null) {
141
- // force rerender to update the bindings mapping
142
- binding._forceRerender();
143
- }
144
- onFirstRender();
145
- return {
146
- update: () => {
147
- const pluginState = plugin.getState(view.state);
148
- if (pluginState.snapshot == null && pluginState.prevSnapshot == null) {
149
- if (
150
- // If the content doesn't change initially, we don't render anything to Yjs
151
- // If the content was cleared by a user action, we want to catch the change and
152
- // represent it in Yjs
153
- initialContentChanged ||
154
- view.state.doc.content.findDiffStart(view.state.doc.type.createAndFill().content) !== null) {
155
- initialContentChanged = true;
156
- if (pluginState.addToHistory === false &&
157
- !pluginState.isChangeOrigin) {
158
- const yUndoPluginState = yUndoPluginKey.getState(view.state);
159
- /**
160
- * @type {Y.UndoManager}
161
- */
162
- const um = yUndoPluginState && yUndoPluginState.undoManager;
163
- if (um) {
164
- um.stopCapturing();
165
- }
166
- }
167
- binding.mux(() => {
168
- /** @type {Y.Doc} */ (pluginState.doc).transact((tr) => {
169
- tr.meta.set('addToHistory', pluginState.addToHistory);
170
- binding._prosemirrorChanged(view.state.doc);
171
- }, ySyncPluginKey);
172
- });
173
- }
174
- }
175
- },
176
- destroy: () => {
177
- binding.destroy();
178
- },
179
- };
180
- },
181
- });
182
- return plugin;
183
- };
184
- const restoreRelativeSelection = (tr, relSel, binding) => {
185
- if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
186
- const anchor = relativePositionToAbsolutePosition(binding.doc, binding.type, relSel.anchor, binding.mapping);
187
- const head = relativePositionToAbsolutePosition(binding.doc, binding.type, relSel.head, binding.mapping);
188
- if (anchor !== null && head !== null) {
189
- tr = tr.setSelection(TextSelection.create(tr.doc, anchor, head));
190
- }
191
- }
192
- };
193
- export const getRelativeSelection = (pmbinding, state) => ({
194
- anchor: absolutePositionToRelativePosition(state.selection.anchor, pmbinding.type, pmbinding.mapping),
195
- head: absolutePositionToRelativePosition(state.selection.head, pmbinding.type, pmbinding.mapping),
196
- });
197
- /**
198
- * Binding for prosemirror.
199
- *
200
- * @protected
201
- */
202
- export class ProsemirrorBinding {
203
- constructor(yXmlFragment, mapping = new Map()) {
204
- Object.defineProperty(this, "yXmlFragment", {
205
- enumerable: true,
206
- configurable: true,
207
- writable: true,
208
- value: yXmlFragment
209
- });
210
- Object.defineProperty(this, "mapping", {
211
- enumerable: true,
212
- configurable: true,
213
- writable: true,
214
- value: mapping
215
- });
216
- Object.defineProperty(this, "type", {
217
- enumerable: true,
218
- configurable: true,
219
- writable: true,
220
- value: void 0
221
- });
222
- Object.defineProperty(this, "mux", {
223
- enumerable: true,
224
- configurable: true,
225
- writable: true,
226
- value: void 0
227
- });
228
- Object.defineProperty(this, "_observeFunction", {
229
- enumerable: true,
230
- configurable: true,
231
- writable: true,
232
- value: void 0
233
- });
234
- Object.defineProperty(this, "prosemirrorView", {
235
- enumerable: true,
236
- configurable: true,
237
- writable: true,
238
- value: void 0
239
- });
240
- Object.defineProperty(this, "doc", {
241
- enumerable: true,
242
- configurable: true,
243
- writable: true,
244
- value: void 0
245
- });
246
- Object.defineProperty(this, "beforeTransactionSelection", {
247
- enumerable: true,
248
- configurable: true,
249
- writable: true,
250
- value: void 0
251
- });
252
- Object.defineProperty(this, "afterAllTransactions", {
253
- enumerable: true,
254
- configurable: true,
255
- writable: true,
256
- value: void 0
257
- });
258
- Object.defineProperty(this, "beforeAllTransactions", {
259
- enumerable: true,
260
- configurable: true,
261
- writable: true,
262
- value: void 0
263
- });
264
- this.type = yXmlFragment;
265
- /**
266
- * this will be set once the view is created
267
- * @type {any}
268
- */
269
- this.prosemirrorView = null;
270
- this.mux = createMutex();
271
- this._observeFunction = this._typeChanged.bind(this);
272
- this.doc = yXmlFragment.doc;
273
- /**
274
- * current selection as relative positions in the Yjs model
275
- */
276
- this.beforeTransactionSelection = null;
277
- this.beforeAllTransactions = () => {
278
- if (this.beforeTransactionSelection === null && this.prosemirrorView != null) {
279
- this.beforeTransactionSelection = getRelativeSelection(this, this.prosemirrorView.state);
280
- }
281
- };
282
- this.afterAllTransactions = () => {
283
- this.beforeTransactionSelection = null;
284
- };
285
- this._domSelectionInView = null;
286
- }
287
- /**
288
- * Create a transaction for changing the prosemirror state.
289
- *
290
- * @returns
291
- */
292
- get _tr() {
293
- return this.prosemirrorView?.state.tr.setMeta('addToHistory', false);
294
- }
295
- _isLocalCursorInView() {
296
- if (!this.prosemirrorView.hasFocus())
297
- return false;
298
- if (environment.isBrowser && this._domSelectionInView === null) {
299
- // Calculate the domSelectionInView and clear by next tick after all events are finished
300
- eventloop.timeout(0, () => {
301
- this._domSelectionInView = null;
302
- });
303
- this._domSelectionInView = this._isDomSelectionInView();
304
- }
305
- return this._domSelectionInView;
306
- }
307
- _isDomSelectionInView() {
308
- const selection = this.prosemirrorView._root.getSelection();
309
- const range = this.prosemirrorView._root.createRange();
310
- range.setStart(selection.anchorNode, selection.anchorOffset);
311
- range.setEnd(selection.focusNode, selection.focusOffset);
312
- // This is a workaround for an edgecase where getBoundingClientRect will
313
- // return zero values if the selection is collapsed at the start of a newline
314
- // see reference here: https://stackoverflow.com/a/59780954
315
- const rects = range.getClientRects();
316
- if (rects.length === 0) {
317
- // probably buggy newline behavior, explicitly select the node contents
318
- if (range.startContainer && range.collapsed) {
319
- range.selectNodeContents(range.startContainer);
320
- }
321
- }
322
- const bounding = range.getBoundingClientRect();
323
- const documentElement = dom.doc.documentElement;
324
- return bounding.bottom >= 0 && bounding.right >= 0 &&
325
- bounding.left <=
326
- (globalThis.innerWidth || documentElement.clientWidth || 0) &&
327
- bounding.top <=
328
- (globalThis.innerHeight || documentElement.clientHeight || 0);
329
- }
330
- renderSnapshot(snapshot, prevSnapshot) {
331
- if (!prevSnapshot) {
332
- prevSnapshot = Y.createSnapshot(Y.createDeleteSet(), new Map());
333
- }
334
- this.prosemirrorView.dispatch(this._tr.setMeta(ySyncPluginKey, { snapshot, prevSnapshot }));
335
- }
336
- unrenderSnapshot() {
337
- this.mapping.clear();
338
- this.mux(() => {
339
- const fragmentContent = this.type.toArray().map((t) => createNodeFromYElement(
340
- /** @type {Y.XmlElement} */ (t), this.prosemirrorView.state.schema, this.mapping)).filter((n) => n !== null);
341
- // @ts-ignore
342
- const tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0));
343
- tr.setMeta(ySyncPluginKey, { snapshot: null, prevSnapshot: null });
344
- this.prosemirrorView.dispatch(tr);
345
- });
346
- }
347
- _forceRerender() {
348
- this.mapping.clear();
349
- this.mux(() => {
350
- // If this is a forced rerender, this might neither happen as a pm change nor within a Yjs
351
- // transaction. Then the "before selection" doesn't exist. In this case, we need to create a
352
- // relative position before replacing content. Fixes #126
353
- const sel = this.beforeTransactionSelection !== null
354
- ? null
355
- : this.prosemirrorView.state.selection;
356
- const fragmentContent = this.type.toArray().map((t) => createNodeFromYElement(
357
- /** @type {Y.XmlElement} */ (t), this.prosemirrorView.state.schema, this.mapping)).filter((n) => n !== null);
358
- // @ts-ignore
359
- const tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0));
360
- if (sel) {
361
- tr.setSelection(TextSelection.create(tr.doc, sel.anchor, sel.head));
362
- }
363
- this.prosemirrorView.dispatch(tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, binding: this }));
364
- });
365
- }
366
- _renderSnapshot(snapshot, prevSnapshot, pluginState) {
367
- /**
368
- * The document that contains the full history of this document.
369
- * @type {Y.Doc}
370
- */
371
- let historyDoc = this.doc;
372
- if (!snapshot) {
373
- snapshot = Y.snapshot(this.doc);
374
- }
375
- if (snapshot instanceof Uint8Array || prevSnapshot instanceof Uint8Array) {
376
- if (!(snapshot instanceof Uint8Array) ||
377
- !(prevSnapshot instanceof Uint8Array)) {
378
- // expected both snapshots to be v2 updates
379
- error.unexpectedCase();
380
- }
381
- historyDoc = new Y.Doc({ gc: false });
382
- Y.applyUpdateV2(historyDoc, prevSnapshot);
383
- prevSnapshot = Y.snapshot(historyDoc);
384
- Y.applyUpdateV2(historyDoc, snapshot);
385
- snapshot = Y.snapshot(historyDoc);
386
- }
387
- // clear mapping because we are going to rerender
388
- this.mapping.clear();
389
- this.mux(() => {
390
- historyDoc.transact((transaction) => {
391
- // before rendering, we are going to sanitize ops and split deleted ops
392
- // if they were deleted by seperate users.
393
- const pud = pluginState.permanentUserData;
394
- if (pud) {
395
- pud.dss.forEach((ds) => {
396
- Y.iterateDeletedStructs(transaction, ds, (_item) => { });
397
- });
398
- }
399
- const computeYChange = (type, id) => {
400
- const user = type === 'added'
401
- ? pud.getUserByClientId(id.client)
402
- : pud.getUserByDeletedId(id);
403
- return {
404
- user,
405
- type,
406
- color: getUserColor(pluginState.colorMapping, pluginState.colors, user),
407
- };
408
- };
409
- // Create document fragment and render
410
- const fragmentContent = Y.typeListToArraySnapshot(this.type, new Y.Snapshot(prevSnapshot.ds, snapshot.sv)).map((t) => {
411
- if (!t._item.deleted || isVisible(t._item, snapshot) ||
412
- isVisible(t._item, prevSnapshot)) {
413
- return createNodeFromYElement(t, this.prosemirrorView.state.schema, new Map(), snapshot, prevSnapshot, computeYChange);
414
- }
415
- else {
416
- // No need to render elements that are not visible by either snapshot.
417
- // If a client adds and deletes content in the same snapshot the element is not visible by either snapshot.
418
- return null;
419
- }
420
- }).filter((n) => n !== null);
421
- const tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0));
422
- this.prosemirrorView.dispatch(tr.setMeta(ySyncPluginKey, { isChangeOrigin: true }));
423
- }, ySyncPluginKey);
424
- });
425
- }
426
- _typeChanged(events, transaction) {
427
- if (this.prosemirrorView == null)
428
- return;
429
- const syncState = ySyncPluginKey.getState(this.prosemirrorView.state);
430
- if (events.length === 0 || syncState.snapshot != null ||
431
- syncState.prevSnapshot != null) {
432
- // drop out if snapshot is active
433
- this.renderSnapshot(syncState.snapshot, syncState.prevSnapshot);
434
- return;
435
- }
436
- this.mux(() => {
437
- const delType = (_, type) => this.mapping.delete(type);
438
- Y.iterateDeletedStructs(transaction, transaction.deleteSet, (struct) => {
439
- if (struct.constructor === Y.Item) {
440
- const type =
441
- /** @type {Y.ContentType} */ ( /** @type {Y.Item} */(struct)
442
- .content).type;
443
- type && this.mapping.delete(type);
444
- }
445
- });
446
- transaction.changed.forEach(delType);
447
- transaction.changedParentTypes.forEach(delType);
448
- const fragmentContent = this.type.toArray().map((t) => createNodeIfNotExists(
449
- /** @type {Y.XmlElement | Y.XmlHook} */ (t), this.prosemirrorView.state.schema, this.mapping)).filter((n) => n !== null);
450
- // @ts-ignore
451
- let tr = this._tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0));
452
- restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
453
- tr = tr.setMeta(ySyncPluginKey, {
454
- isChangeOrigin: true,
455
- isUndoRedoOperation: transaction.origin instanceof Y.UndoManager,
456
- });
457
- if (this.beforeTransactionSelection !== null && this._isLocalCursorInView()) {
458
- tr.scrollIntoView();
459
- }
460
- this.prosemirrorView.dispatch(tr);
461
- });
462
- }
463
- _prosemirrorChanged(doc) {
464
- this.doc.transact(() => {
465
- updateYFragment(this.doc, this.type, doc, this.mapping);
466
- this.beforeTransactionSelection = getRelativeSelection(this, this.prosemirrorView.state);
467
- }, ySyncPluginKey);
468
- }
469
- /**
470
- * View is ready to listen to changes. Register observers.
471
- * @param {any} prosemirrorView
472
- */
473
- initView(prosemirrorView) {
474
- if (this.prosemirrorView != null)
475
- this.destroy();
476
- this.prosemirrorView = prosemirrorView;
477
- this.doc.on('beforeAllTransactions', this.beforeAllTransactions);
478
- this.doc.on('afterAllTransactions', this.afterAllTransactions);
479
- this.type.observeDeep(this._observeFunction);
480
- }
481
- destroy() {
482
- if (this.prosemirrorView == null)
483
- return;
484
- this.prosemirrorView = null;
485
- this.type.unobserveDeep(this._observeFunction);
486
- this.doc.off('beforeAllTransactions', this.beforeAllTransactions);
487
- this.doc.off('afterAllTransactions', this.afterAllTransactions);
488
- }
489
- }
490
- const createNodeIfNotExists = (el, schema, mapping, snapshot, prevSnapshot, computeYChange) => {
491
- const node = /** @type {PModel.Node} */ (mapping.get(el));
492
- if (node === undefined) {
493
- if (el instanceof Y.XmlElement) {
494
- return createNodeFromYElement(el, schema, mapping, snapshot, prevSnapshot, computeYChange);
495
- }
496
- else {
497
- throw error.methodUnimplemented(); // we are currently not handling hooks
498
- }
499
- }
500
- return node;
501
- };
502
- /**
503
- * @private
504
- * @param {Y.XmlElement} el
505
- * @param {any} schema
506
- * @param {ProsemirrorMapping} mapping
507
- * @param {Y.Snapshot} [snapshot]
508
- * @param {Y.Snapshot} [prevSnapshot]
509
- * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
510
- * @return {PModel.Node | null} Returns node if node could be created. Otherwise it deletes the yjs type and returns null
511
- */
512
- export const createNodeFromYElement = (el, schema, mapping, snapshot, prevSnapshot, computeYChange) => {
513
- const children = [];
514
- const createChildren = (type) => {
515
- if (type.constructor === Y.XmlElement) {
516
- const n = createNodeIfNotExists(type, schema, mapping, snapshot, prevSnapshot, computeYChange);
517
- if (n !== null) {
518
- children.push(n);
519
- }
520
- }
521
- else {
522
- // If the next ytext exists and was created by us, move the content to the current ytext.
523
- // This is a fix for #160 -- duplication of characters when two Y.Text exist next to each
524
- // other.
525
- const nextytext = type._item.right?.content.type;
526
- if (nextytext != null && !nextytext._item.deleted &&
527
- nextytext._item.id.client === nextytext.doc.clientID) {
528
- type.applyDelta([
529
- { retain: type.length },
530
- ...nextytext.toDelta(),
531
- ]);
532
- nextytext.doc.transact((tr) => {
533
- nextytext._item.delete(tr);
534
- });
535
- }
536
- // now create the prosemirror text nodes
537
- const ns = createTextNodesFromYText(type, schema, mapping, snapshot, prevSnapshot, computeYChange);
538
- if (ns !== null) {
539
- ns.forEach((textchild) => {
540
- if (textchild !== null) {
541
- children.push(textchild);
542
- }
543
- });
544
- }
545
- }
546
- };
547
- if (snapshot === undefined || prevSnapshot === undefined) {
548
- el.toArray().forEach(createChildren);
549
- }
550
- else {
551
- Y.typeListToArraySnapshot(el, new Y.Snapshot(prevSnapshot.ds, snapshot.sv))
552
- .forEach(createChildren);
553
- }
554
- try {
555
- const attrs = el.getAttributes(snapshot);
556
- if (snapshot !== undefined) {
557
- if (!isVisible(/** @type {Y.Item} */ (el._item), snapshot)) {
558
- attrs.ychange = computeYChange
559
- ? computeYChange('removed', /** @type {Y.Item} */ (el._item).id)
560
- : { type: 'removed' };
561
- }
562
- else if (!isVisible(/** @type {Y.Item} */ (el._item), prevSnapshot)) {
563
- attrs.ychange = computeYChange
564
- ? computeYChange('added', /** @type {Y.Item} */ (el._item).id)
565
- : { type: 'added' };
566
- }
567
- }
568
- const node = schema.node(el.nodeName, attrs, children);
569
- mapping.set(el, node);
570
- return node;
571
- }
572
- catch (e) {
573
- // an error occured while creating the node. This is probably a result of a concurrent action.
574
- /** @type {Y.Doc} */ (el.doc).transact((transaction) => {
575
- /** @type {Y.Item} */ (el._item).delete(transaction);
576
- }, ySyncPluginKey);
577
- mapping.delete(el);
578
- return null;
579
- }
580
- };
581
- const createTextNodesFromYText = (text, schema, _mapping, snapshot, prevSnapshot, computeYChange) => {
582
- const nodes = [];
583
- const deltas = text.toDelta(snapshot, prevSnapshot, computeYChange);
584
- try {
585
- for (let i = 0; i < deltas.length; i++) {
586
- const delta = deltas[i];
587
- const marks = [];
588
- for (const markName in delta.attributes) {
589
- marks.push(schema.mark(markName, delta.attributes[markName]));
590
- }
591
- nodes.push(schema.text(delta.insert, marks));
592
- }
593
- }
594
- catch (e) {
595
- // an error occured while creating the node. This is probably a result of a concurrent action.
596
- /** @type {Y.Doc} */ (text.doc).transact((transaction) => {
597
- /** @type {Y.Item} */ (text._item).delete(transaction);
598
- }, ySyncPluginKey);
599
- return null;
600
- }
601
- // @ts-ignore
602
- return nodes;
603
- };
604
- const createTypeFromTextNodes = (nodes, mapping) => {
605
- const type = new Y.XmlText();
606
- const delta = nodes.map((node) => ({
607
- // @ts-ignore
608
- insert: node.text,
609
- attributes: marksToAttributes(node.marks),
610
- }));
611
- type.applyDelta(delta);
612
- mapping.set(type, nodes);
613
- return type;
614
- };
615
- const createTypeFromElementNode = (node, mapping) => {
616
- const type = new Y.XmlElement(node.type.name);
617
- for (const key in node.attrs) {
618
- const val = node.attrs[key];
619
- if (val !== null && key !== 'ychange') {
620
- type.setAttribute(key, val);
621
- }
622
- }
623
- type.insert(0, normalizePNodeContent(node).map((n) => createTypeFromTextOrElementNode(n, mapping)));
624
- mapping.set(type, node);
625
- return type;
626
- };
627
- const createTypeFromTextOrElementNode = (node, mapping) => node instanceof Array
628
- ? createTypeFromTextNodes(node, mapping)
629
- : createTypeFromElementNode(node, mapping);
630
- const isObject = (val) => typeof val === 'object' && val !== null;
631
- const equalAttrs = (pattrs, yattrs) => {
632
- const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
633
- let eq = keys.length ===
634
- Object.keys(yattrs).filter((key) => yattrs[key] !== null).length;
635
- for (let i = 0; i < keys.length && eq; i++) {
636
- const key = keys[i];
637
- const l = pattrs[key];
638
- const r = yattrs[key];
639
- eq = key === 'ychange' || l === r ||
640
- (isObject(l) && isObject(r) && equalAttrs(l, r));
641
- }
642
- return eq;
643
- };
644
- /**
645
- * @typedef {Array<Array<PModel.Node>|PModel.Node>} NormalizedPNodeContent
646
- */
647
- /**
648
- * @param {any} pnode
649
- * @return {NormalizedPNodeContent}
650
- */
651
- const normalizePNodeContent = (pnode) => {
652
- const c = pnode.content.content;
653
- const res = [];
654
- for (let i = 0; i < c.length; i++) {
655
- const n = c[i];
656
- if (n.isText) {
657
- const textNodes = [];
658
- for (let tnode = c[i]; i < c.length && tnode.isText; tnode = c[++i]) {
659
- textNodes.push(tnode);
660
- }
661
- i--;
662
- res.push(textNodes);
663
- }
664
- else {
665
- res.push(n);
666
- }
667
- }
668
- return res;
669
- };
670
- const equalYTextPText = (ytext, ptexts) => {
671
- const delta = ytext.toDelta();
672
- return delta.length === ptexts.length &&
673
- delta.every((d, i) => d.insert === /** @type {any} */ (ptexts[i]).text &&
674
- object.keys(d.attributes || {}).length === ptexts[i].marks.length &&
675
- ptexts[i].marks.every((mark) => equalAttrs(d.attributes[mark.type.name] || {}, mark.attrs)));
676
- };
677
- const equalYTypePNode = (ytype, pnode) => {
678
- if (ytype instanceof Y.XmlElement && !(pnode instanceof Array) &&
679
- matchNodeName(ytype, pnode)) {
680
- const normalizedContent = normalizePNodeContent(pnode);
681
- return ytype._length === normalizedContent.length &&
682
- equalAttrs(ytype.getAttributes(), pnode.attrs) &&
683
- ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i]));
684
- }
685
- return ytype instanceof Y.XmlText && pnode instanceof Array &&
686
- equalYTextPText(ytype, pnode);
687
- };
688
- const mappedIdentity = (mapped, pcontent) => mapped === pcontent ||
689
- (mapped instanceof Array && pcontent instanceof Array &&
690
- mapped.length === pcontent.length &&
691
- mapped.every((a, i) => pcontent[i] === a));
692
- const computeChildEqualityFactor = (ytype, pnode, mapping) => {
693
- const yChildren = ytype.toArray();
694
- const pChildren = normalizePNodeContent(pnode);
695
- const pChildCnt = pChildren.length;
696
- const yChildCnt = yChildren.length;
697
- const minCnt = math.min(yChildCnt, pChildCnt);
698
- let left = 0;
699
- let right = 0;
700
- let foundMappedChild = false;
701
- for (; left < minCnt; left++) {
702
- const leftY = yChildren[left];
703
- const leftP = pChildren[left];
704
- if (mappedIdentity(mapping.get(leftY), leftP)) {
705
- foundMappedChild = true; // definite (good) match!
706
- }
707
- else if (!equalYTypePNode(leftY, leftP)) {
708
- break;
709
- }
710
- }
711
- for (; left + right < minCnt; right++) {
712
- const rightY = yChildren[yChildCnt - right - 1];
713
- const rightP = pChildren[pChildCnt - right - 1];
714
- if (mappedIdentity(mapping.get(rightY), rightP)) {
715
- foundMappedChild = true;
716
- }
717
- else if (!equalYTypePNode(rightY, rightP)) {
718
- break;
719
- }
720
- }
721
- return {
722
- equalityFactor: left + right,
723
- foundMappedChild,
724
- };
725
- };
726
- const ytextTrans = (ytext) => {
727
- let str = '';
728
- let n = ytext._start;
729
- const nAttrs = {};
730
- while (n !== null) {
731
- if (!n.deleted) {
732
- if (n.countable && n.content instanceof Y.ContentString) {
733
- str += n.content.str;
734
- }
735
- else if (n.content instanceof Y.ContentFormat) {
736
- nAttrs[n.content.key] = null;
737
- }
738
- }
739
- n = n.right;
740
- }
741
- return {
742
- str,
743
- nAttrs,
744
- };
745
- };
746
- const updateYText = (ytext, ptexts, mapping) => {
747
- mapping.set(ytext, ptexts);
748
- const { nAttrs, str } = ytextTrans(ytext);
749
- const content = ptexts.map((p) => ({
750
- insert: /** @type {any} */ (p).text,
751
- attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks)),
752
- }));
753
- const { insert, remove, index } = simpleDiff(str, content.map((c) => c.insert).join(''));
754
- ytext.delete(index, remove);
755
- ytext.insert(index, insert);
756
- ytext.applyDelta(content.map((c) => ({ retain: c.insert.length, attributes: c.attributes })));
757
- };
758
- const marksToAttributes = (marks) => {
759
- const pattrs = {};
760
- marks.forEach((mark) => {
761
- if (mark.type.name !== 'ychange') {
762
- pattrs[mark.type.name] = mark.attrs;
763
- }
764
- });
765
- return pattrs;
766
- };
767
- /**
768
- * Update a yDom node by syncing the current content of the prosemirror node.
769
- *
770
- * This is a y-prosemirror internal feature that you can use at your own risk.
771
- *
772
- * @private
773
- * @unstable
774
- *
775
- * @param {{transact: Function}} y
776
- * @param {Y.XmlFragment} yDomFragment
777
- * @param {any} pNode
778
- * @param {ProsemirrorMapping} mapping
779
- */
780
- export const updateYFragment = (y, yDomFragment, pNode, mapping) => {
781
- if (yDomFragment instanceof Y.XmlElement &&
782
- yDomFragment.nodeName !== pNode.type.name) {
783
- throw new Error('node name mismatch!');
784
- }
785
- mapping.set(yDomFragment, pNode);
786
- // update attributes
787
- if (yDomFragment instanceof Y.XmlElement) {
788
- const yDomAttrs = yDomFragment.getAttributes();
789
- const pAttrs = pNode.attrs;
790
- for (const key in pAttrs) {
791
- if (pAttrs[key] !== null) {
792
- if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
793
- yDomFragment.setAttribute(key, pAttrs[key]);
794
- }
795
- }
796
- else {
797
- yDomFragment.removeAttribute(key);
798
- }
799
- }
800
- // remove all keys that are no longer in pAttrs
801
- for (const key in yDomAttrs) {
802
- if (pAttrs[key] === undefined) {
803
- yDomFragment.removeAttribute(key);
804
- }
805
- }
806
- }
807
- // update children
808
- const pChildren = normalizePNodeContent(pNode);
809
- const pChildCnt = pChildren.length;
810
- const yChildren = yDomFragment.toArray();
811
- const yChildCnt = yChildren.length;
812
- const minCnt = math.min(pChildCnt, yChildCnt);
813
- let left = 0;
814
- let right = 0;
815
- // find number of matching elements from left
816
- for (; left < minCnt; left++) {
817
- const leftY = yChildren[left];
818
- const leftP = pChildren[left];
819
- if (!mappedIdentity(mapping.get(leftY), leftP)) {
820
- if (equalYTypePNode(leftY, leftP)) {
821
- // update mapping
822
- mapping.set(leftY, leftP);
823
- }
824
- else {
825
- break;
826
- }
827
- }
828
- }
829
- // find number of matching elements from right
830
- for (; right + left + 1 < minCnt; right++) {
831
- const rightY = yChildren[yChildCnt - right - 1];
832
- const rightP = pChildren[pChildCnt - right - 1];
833
- if (!mappedIdentity(mapping.get(rightY), rightP)) {
834
- if (equalYTypePNode(rightY, rightP)) {
835
- // update mapping
836
- mapping.set(rightY, rightP);
837
- }
838
- else {
839
- break;
840
- }
841
- }
842
- }
843
- y.transact(() => {
844
- // try to compare and update
845
- while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
846
- const leftY = yChildren[left];
847
- const leftP = pChildren[left];
848
- const rightY = yChildren[yChildCnt - right - 1];
849
- const rightP = pChildren[pChildCnt - right - 1];
850
- if (leftY instanceof Y.XmlText && leftP instanceof Array) {
851
- if (!equalYTextPText(leftY, leftP)) {
852
- updateYText(leftY, leftP, mapping);
853
- }
854
- left += 1;
855
- }
856
- else {
857
- let updateLeft = leftY instanceof Y.XmlElement &&
858
- matchNodeName(leftY, leftP);
859
- let updateRight = rightY instanceof Y.XmlElement &&
860
- matchNodeName(rightY, rightP);
861
- if (updateLeft && updateRight) {
862
- // decide which which element to update
863
- const equalityLeft = computeChildEqualityFactor(
864
- /** @type {Y.XmlElement} */ (leftY),
865
- /** @type {PModel.Node} */ (leftP), mapping);
866
- const equalityRight = computeChildEqualityFactor(
867
- /** @type {Y.XmlElement} */ (rightY),
868
- /** @type {PModel.Node} */ (rightP), mapping);
869
- if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) {
870
- updateRight = false;
871
- }
872
- else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) {
873
- updateLeft = false;
874
- }
875
- else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) {
876
- updateLeft = false;
877
- }
878
- else {
879
- updateRight = false;
880
- }
881
- }
882
- if (updateLeft) {
883
- updateYFragment(y,
884
- /** @type {Y.XmlFragment} */ (leftY),
885
- /** @type {PModel.Node} */ (leftP), mapping);
886
- left += 1;
887
- }
888
- else if (updateRight) {
889
- updateYFragment(y,
890
- /** @type {Y.XmlFragment} */ (rightY),
891
- /** @type {PModel.Node} */ (rightP), mapping);
892
- right += 1;
893
- }
894
- else {
895
- mapping.delete(yDomFragment.get(left));
896
- yDomFragment.delete(left, 1);
897
- yDomFragment.insert(left, [
898
- createTypeFromTextOrElementNode(leftP, mapping),
899
- ]);
900
- left += 1;
901
- }
902
- }
903
- }
904
- const yDelLen = yChildCnt - left - right;
905
- if (yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y.XmlText) {
906
- mapping.delete(yChildren[0]);
907
- // Edge case handling https://github.com/yjs/y-prosemirror/issues/108
908
- // Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
909
- yChildren[0].delete(0, yChildren[0].length);
910
- }
911
- else if (yDelLen > 0) {
912
- yDomFragment.slice(left, left + yDelLen).forEach((type) => mapping.delete(type));
913
- yDomFragment.delete(left, yDelLen);
914
- }
915
- if (left + right < pChildCnt) {
916
- const ins = [];
917
- for (let i = left; i < pChildCnt - right; i++) {
918
- ins.push(createTypeFromTextOrElementNode(pChildren[i], mapping));
919
- }
920
- yDomFragment.insert(left, ins);
921
- }
922
- }, ySyncPluginKey);
923
- };
924
- const matchNodeName = (yElement, pNode) => !(pNode instanceof Array) && yElement.nodeName === pNode.type.name;