@kerebron/extension-yjs 0.3.1 → 0.4.0
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.
- package/LICENSE +23 -0
- package/README.md +82 -0
- package/esm/editor/src/CoreEditor.d.ts +13 -4
- package/esm/editor/src/CoreEditor.d.ts.map +1 -1
- package/esm/editor/src/CoreEditor.js +64 -12
- package/esm/editor/src/Extension.d.ts +6 -1
- package/esm/editor/src/Extension.d.ts.map +1 -1
- package/esm/editor/src/Extension.js +21 -1
- package/esm/editor/src/ExtensionManager.d.ts +5 -6
- package/esm/editor/src/ExtensionManager.d.ts.map +1 -1
- package/esm/editor/src/ExtensionManager.js +43 -55
- package/esm/editor/src/Mark.d.ts +3 -0
- package/esm/editor/src/Mark.d.ts.map +1 -1
- package/esm/editor/src/Mark.js +11 -0
- package/esm/editor/src/Node.d.ts +5 -2
- package/esm/editor/src/Node.d.ts.map +1 -1
- package/esm/editor/src/Node.js +13 -2
- package/esm/editor/src/commands/CommandManager.d.ts +13 -6
- package/esm/editor/src/commands/CommandManager.d.ts.map +1 -1
- package/esm/editor/src/commands/CommandManager.js +59 -2
- package/esm/editor/src/commands/baseCommandFactories.d.ts +3 -0
- package/esm/editor/src/commands/baseCommandFactories.d.ts.map +1 -0
- package/esm/editor/src/commands/baseCommandFactories.js +836 -0
- package/esm/editor/src/commands/keyCommandFactories.d.ts +3 -0
- package/esm/editor/src/commands/keyCommandFactories.d.ts.map +1 -0
- package/esm/editor/src/commands/keyCommandFactories.js +10 -0
- package/esm/editor/src/commands/mod.d.ts +5 -53
- package/esm/editor/src/commands/mod.d.ts.map +1 -1
- package/esm/editor/src/commands/mod.js +14 -821
- package/esm/editor/src/commands/replaceCommandFactories.d.ts +3 -0
- package/esm/editor/src/commands/replaceCommandFactories.d.ts.map +1 -0
- package/esm/editor/src/commands/replaceCommandFactories.js +94 -0
- package/esm/editor/src/commands/types.d.ts +18 -0
- package/esm/editor/src/commands/types.d.ts.map +1 -0
- package/esm/editor/src/commands/types.js +1 -0
- package/esm/editor/src/mod.d.ts +2 -0
- package/esm/editor/src/mod.d.ts.map +1 -1
- package/esm/editor/src/mod.js +2 -0
- package/esm/editor/src/plugins/TrackSelecionPlugin.d.ts +6 -0
- package/esm/editor/src/plugins/TrackSelecionPlugin.d.ts.map +1 -0
- package/esm/editor/src/plugins/TrackSelecionPlugin.js +24 -0
- package/esm/editor/src/types.d.ts +20 -2
- package/esm/editor/src/types.d.ts.map +1 -1
- package/esm/editor/src/ui.d.ts +15 -0
- package/esm/editor/src/ui.d.ts.map +1 -0
- package/esm/editor/src/ui.js +16 -0
- package/esm/editor/src/utilities/SmartOutput.d.ts +9 -7
- package/esm/editor/src/utilities/SmartOutput.d.ts.map +1 -1
- package/esm/editor/src/utilities/SmartOutput.js +35 -20
- package/esm/extension-basic-editor/src/remote-selection/ExtensionRemoteSelection.d.ts +24 -0
- package/esm/extension-basic-editor/src/remote-selection/ExtensionRemoteSelection.d.ts.map +1 -0
- package/esm/extension-basic-editor/src/remote-selection/ExtensionRemoteSelection.js +35 -0
- package/esm/extension-basic-editor/src/remote-selection/remoteSelectionPlugin.d.ts +25 -0
- package/esm/extension-basic-editor/src/remote-selection/remoteSelectionPlugin.d.ts.map +1 -0
- package/esm/extension-basic-editor/src/remote-selection/remoteSelectionPlugin.js +96 -0
- package/esm/extension-yjs/src/ExtensionYjs.d.ts +9 -4
- package/esm/extension-yjs/src/ExtensionYjs.d.ts.map +1 -1
- package/esm/extension-yjs/src/ExtensionYjs.js +13 -7
- package/esm/extension-yjs/src/convertUtils.d.ts +59 -0
- package/esm/extension-yjs/src/convertUtils.d.ts.map +1 -0
- package/esm/extension-yjs/src/convertUtils.js +88 -0
- package/esm/extension-yjs/src/keys.d.ts +0 -4
- package/esm/extension-yjs/src/keys.d.ts.map +1 -1
- package/esm/extension-yjs/src/keys.js +0 -4
- package/esm/extension-yjs/src/lib.d.ts +15 -0
- package/esm/extension-yjs/src/lib.d.ts.map +1 -0
- package/esm/extension-yjs/src/lib.js +24 -103
- package/esm/extension-yjs/src/yPositionPlugin.d.ts +14 -0
- package/esm/extension-yjs/src/yPositionPlugin.d.ts.map +1 -0
- package/esm/extension-yjs/src/yPositionPlugin.js +121 -0
- package/esm/extension-yjs/src/ySyncPlugin.d.ts +37 -30
- package/esm/extension-yjs/src/ySyncPlugin.d.ts.map +1 -1
- package/esm/extension-yjs/src/ySyncPlugin.js +99 -77
- package/esm/extension-yjs/src/yUndoPlugin.d.ts.map +1 -1
- package/esm/extension-yjs/src/yUndoPlugin.js +18 -15
- package/esm/package.json +3 -0
- package/package.json +2 -2
- package/esm/extension-yjs/src/yCursorPlugin.d.ts +0 -29
- package/esm/extension-yjs/src/yCursorPlugin.d.ts.map +0 -1
- package/esm/extension-yjs/src/yCursorPlugin.js +0 -177
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import type { Schema } from 'prosemirror-model';
|
|
2
1
|
import type { Plugin } from 'prosemirror-state';
|
|
3
|
-
import
|
|
2
|
+
import * as awarenessProtocol from 'y-protocols/awareness';
|
|
3
|
+
import { Extension } from '../../editor/src/mod.js';
|
|
4
4
|
import type { CommandFactories, CommandShortcuts } from '../../editor/src/commands/mod.js';
|
|
5
|
+
export interface YjsProvider {
|
|
6
|
+
on(eventName: string, callback: (event: any) => void): void;
|
|
7
|
+
awareness: awarenessProtocol.Awareness;
|
|
8
|
+
}
|
|
5
9
|
export declare class ExtensionYjs extends Extension {
|
|
6
10
|
name: string;
|
|
7
|
-
|
|
11
|
+
conflicts: string[];
|
|
12
|
+
requires: string[];
|
|
8
13
|
getCommandFactories(): Partial<CommandFactories>;
|
|
9
14
|
getKeyboardShortcuts(): Partial<CommandShortcuts>;
|
|
10
|
-
getProseMirrorPlugins(
|
|
15
|
+
getProseMirrorPlugins(): Plugin[];
|
|
11
16
|
}
|
|
12
17
|
//# sourceMappingURL=ExtensionYjs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionYjs.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/ExtensionYjs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ExtensionYjs.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/ExtensionYjs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,kCAAkC,CAAC;AAM1C,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5D,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC;CACxC;AAED,qBAAa,YAAa,SAAQ,SAAS;IACzC,IAAI,SAAS;IAEJ,SAAS,WAAe;IACjC,QAAQ,WAAwB;IAGvB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAOhD,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAOjD,qBAAqB,IAAI,MAAM,EAAE;CAY3C"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Extension } from '../../editor/src/mod.js';
|
|
2
2
|
import { ySyncPlugin } from './ySyncPlugin.js';
|
|
3
|
-
import {
|
|
3
|
+
import { yPositionPlugin } from './yPositionPlugin.js';
|
|
4
4
|
import { redo, undo, yUndoPlugin } from './yUndoPlugin.js';
|
|
5
|
-
import { initProseMirrorDoc } from './
|
|
5
|
+
import { initProseMirrorDoc } from './convertUtils.js';
|
|
6
6
|
export class ExtensionYjs extends Extension {
|
|
7
7
|
constructor() {
|
|
8
8
|
super(...arguments);
|
|
@@ -12,11 +12,17 @@ export class ExtensionYjs extends Extension {
|
|
|
12
12
|
writable: true,
|
|
13
13
|
value: 'yjs'
|
|
14
14
|
});
|
|
15
|
-
Object.defineProperty(this, "
|
|
15
|
+
Object.defineProperty(this, "conflicts", {
|
|
16
16
|
enumerable: true,
|
|
17
17
|
configurable: true,
|
|
18
18
|
writable: true,
|
|
19
|
-
value:
|
|
19
|
+
value: ['history']
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "requires", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: ['remote-selection']
|
|
20
26
|
});
|
|
21
27
|
}
|
|
22
28
|
// declare type Command = (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) => boolean;
|
|
@@ -32,13 +38,13 @@ export class ExtensionYjs extends Extension {
|
|
|
32
38
|
'Mod-y': 'redo',
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
|
-
getProseMirrorPlugins(
|
|
41
|
+
getProseMirrorPlugins() {
|
|
36
42
|
const ydoc = this.config.ydoc;
|
|
37
43
|
const fragment = ydoc.getXmlFragment('prosemirror');
|
|
38
|
-
const { mapping } = initProseMirrorDoc(fragment, schema);
|
|
44
|
+
const { mapping } = initProseMirrorDoc(fragment, this.editor.schema);
|
|
39
45
|
return [
|
|
40
46
|
ySyncPlugin(fragment, { mapping }),
|
|
41
|
-
|
|
47
|
+
yPositionPlugin(this.config.provider.awareness, this.editor),
|
|
42
48
|
yUndoPlugin(),
|
|
43
49
|
];
|
|
44
50
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { Fragment, Node, type Schema } from 'prosemirror-model';
|
|
3
|
+
import { BindingMetadata } from './ySyncPlugin.js';
|
|
4
|
+
export declare const createEmptyMeta: () => BindingMetadata;
|
|
5
|
+
/**
|
|
6
|
+
* Utility function for converting an Y.Fragment to a ProseMirror fragment.
|
|
7
|
+
*/
|
|
8
|
+
export declare const yXmlFragmentToProseMirrorFragment: (yXmlFragment: Y.XmlFragment, schema: Schema) => Fragment;
|
|
9
|
+
/**
|
|
10
|
+
* Utility function for converting an Y.Fragment to a ProseMirror node.
|
|
11
|
+
*/
|
|
12
|
+
export declare const yXmlFragmentToProseMirrorRootNode: (yXmlFragment: Y.XmlFragment, schema: Schema) => Node;
|
|
13
|
+
/**
|
|
14
|
+
* The initial ProseMirror content should be supplied by Yjs. This function transforms a Y.Fragment
|
|
15
|
+
* to a ProseMirror Doc node and creates a mapping that is used by the sync plugin.
|
|
16
|
+
*
|
|
17
|
+
* @todo deprecate mapping property
|
|
18
|
+
*/
|
|
19
|
+
export declare const initProseMirrorDoc: (yXmlFragment: Y.XmlFragment, schema: Schema) => {
|
|
20
|
+
doc: Node;
|
|
21
|
+
meta: BindingMetadata;
|
|
22
|
+
mapping: Map<Y.AbstractType<any>, Node | Node[]>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Utility method to convert a Prosemirror Doc Node into a Y.Doc.
|
|
26
|
+
*
|
|
27
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
28
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
29
|
+
* collaboration has begun as all history will be lost
|
|
30
|
+
*/
|
|
31
|
+
export declare function prosemirrorToYDoc(doc: Node, xmlFragment?: string): Y.Doc;
|
|
32
|
+
/**
|
|
33
|
+
* Utility method to update an empty Y.XmlFragment with content from a Prosemirror Doc Node.
|
|
34
|
+
*
|
|
35
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
36
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
37
|
+
* collaboration has begun as all history will be lost
|
|
38
|
+
*
|
|
39
|
+
* Note: The Y.XmlFragment does not need to be part of a Y.Doc document at the time that this
|
|
40
|
+
* method is called, but it must be added before any other operations are performed on it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function prosemirrorToYXmlFragment(doc: Node, xmlFragment: Y.XmlFragment): Y.XmlFragment;
|
|
43
|
+
/**
|
|
44
|
+
* Utility method to convert Prosemirror compatible JSON into a Y.Doc.
|
|
45
|
+
*
|
|
46
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
47
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
48
|
+
* collaboration has begun as all history will be lost
|
|
49
|
+
*/
|
|
50
|
+
export declare function prosemirrorJSONToYDoc(schema: Schema, state: any, xmlFragment?: string): Y.Doc;
|
|
51
|
+
/**
|
|
52
|
+
* Utility method to convert Prosemirror compatible JSON to a Y.XmlFragment
|
|
53
|
+
*
|
|
54
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
55
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
56
|
+
* collaboration has begun as all history will be lost
|
|
57
|
+
*/
|
|
58
|
+
export declare function prosemirrorJSONToYXmlFragment(schema: Schema, state: any, xmlFragment: Y.XmlFragment): Y.XmlFragment;
|
|
59
|
+
//# sourceMappingURL=convertUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convertUtils.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/convertUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,eAAe,EAIhB,MAAM,kBAAkB,CAAC;AAE1B,eAAO,MAAM,eAAe,QAAO,eAGjC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iCAAiC,GAC5C,cAAc,CAAC,CAAC,WAAW,EAC3B,QAAQ,MAAM,aAUf,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iCAAiC,GAC5C,cAAc,CAAC,CAAC,WAAW,EAC3B,QAAQ,MAAM,SAKb,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,cAAc,CAAC,CAAC,WAAW,EAC3B,QAAQ,MAAM;;;;CAef,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,IAAI,EACT,WAAW,GAAE,MAAsB,GAClC,CAAC,CAAC,GAAG,CAUP;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,IAAI,EACT,WAAW,EAAE,CAAC,CAAC,WAAW,GACzB,CAAC,CAAC,WAAW,CAOf;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,GAAG,EACV,WAAW,GAAE,MAAsB,GAClC,CAAC,CAAC,GAAG,CAGP;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,GAAG,EACV,WAAW,EAAE,CAAC,CAAC,WAAW,GACzB,CAAC,CAAC,WAAW,CAGf"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { Fragment, Node } from 'prosemirror-model';
|
|
3
|
+
import { createNodeFromYElement, updateYFragment, } from './ySyncPlugin.js';
|
|
4
|
+
export const createEmptyMeta = () => ({
|
|
5
|
+
mapping: new Map(),
|
|
6
|
+
isOMark: new Map(),
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Utility function for converting an Y.Fragment to a ProseMirror fragment.
|
|
10
|
+
*/
|
|
11
|
+
export const yXmlFragmentToProseMirrorFragment = (yXmlFragment, schema) => {
|
|
12
|
+
const fragmentContent = yXmlFragment.toArray().map((t) => createNodeFromYElement(
|
|
13
|
+
/** @type {Y.XmlElement} */ (t), schema, createEmptyMeta())).filter((n) => n !== null);
|
|
14
|
+
return Fragment.fromArray(fragmentContent);
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Utility function for converting an Y.Fragment to a ProseMirror node.
|
|
18
|
+
*/
|
|
19
|
+
export const yXmlFragmentToProseMirrorRootNode = (yXmlFragment, schema) => schema.topNodeType.create(null, yXmlFragmentToProseMirrorFragment(yXmlFragment, schema));
|
|
20
|
+
/**
|
|
21
|
+
* The initial ProseMirror content should be supplied by Yjs. This function transforms a Y.Fragment
|
|
22
|
+
* to a ProseMirror Doc node and creates a mapping that is used by the sync plugin.
|
|
23
|
+
*
|
|
24
|
+
* @todo deprecate mapping property
|
|
25
|
+
*/
|
|
26
|
+
export const initProseMirrorDoc = (yXmlFragment, schema) => {
|
|
27
|
+
const meta = createEmptyMeta();
|
|
28
|
+
const fragmentContent = yXmlFragment.toArray().map((t) => createNodeFromYElement(t, schema, meta)).filter((n) => n !== null);
|
|
29
|
+
const doc = schema.topNodeType.create(null, Fragment.fromArray(fragmentContent));
|
|
30
|
+
return { doc, meta, mapping: meta.mapping };
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Utility method to convert a Prosemirror Doc Node into a Y.Doc.
|
|
34
|
+
*
|
|
35
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
36
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
37
|
+
* collaboration has begun as all history will be lost
|
|
38
|
+
*/
|
|
39
|
+
export function prosemirrorToYDoc(doc, xmlFragment = 'prosemirror') {
|
|
40
|
+
const ydoc = new Y.Doc();
|
|
41
|
+
const type =
|
|
42
|
+
/** @type {Y.XmlFragment} */ (ydoc.get(xmlFragment, Y.XmlFragment));
|
|
43
|
+
if (!type.doc) {
|
|
44
|
+
return ydoc;
|
|
45
|
+
}
|
|
46
|
+
prosemirrorToYXmlFragment(doc, type);
|
|
47
|
+
return type.doc;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Utility method to update an empty Y.XmlFragment with content from a Prosemirror Doc Node.
|
|
51
|
+
*
|
|
52
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
53
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
54
|
+
* collaboration has begun as all history will be lost
|
|
55
|
+
*
|
|
56
|
+
* Note: The Y.XmlFragment does not need to be part of a Y.Doc document at the time that this
|
|
57
|
+
* method is called, but it must be added before any other operations are performed on it.
|
|
58
|
+
*/
|
|
59
|
+
export function prosemirrorToYXmlFragment(doc, xmlFragment) {
|
|
60
|
+
const type = xmlFragment || new Y.XmlFragment();
|
|
61
|
+
const ydoc = type.doc
|
|
62
|
+
? type.doc
|
|
63
|
+
: { transact: (transaction) => transaction(undefined) };
|
|
64
|
+
updateYFragment(ydoc, type, doc, { mapping: new Map(), isOMark: new Map() });
|
|
65
|
+
return type;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Utility method to convert Prosemirror compatible JSON into a Y.Doc.
|
|
69
|
+
*
|
|
70
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
71
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
72
|
+
* collaboration has begun as all history will be lost
|
|
73
|
+
*/
|
|
74
|
+
export function prosemirrorJSONToYDoc(schema, state, xmlFragment = 'prosemirror') {
|
|
75
|
+
const doc = Node.fromJSON(schema, state);
|
|
76
|
+
return prosemirrorToYDoc(doc, xmlFragment);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Utility method to convert Prosemirror compatible JSON to a Y.XmlFragment
|
|
80
|
+
*
|
|
81
|
+
* This can be used when importing existing content to Y.Doc for the first time,
|
|
82
|
+
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
83
|
+
* collaboration has begun as all history will be lost
|
|
84
|
+
*/
|
|
85
|
+
export function prosemirrorJSONToYXmlFragment(schema, state, xmlFragment) {
|
|
86
|
+
const doc = Node.fromJSON(schema, state);
|
|
87
|
+
return prosemirrorToYXmlFragment(doc, xmlFragment);
|
|
88
|
+
}
|
|
@@ -8,8 +8,4 @@ export declare const ySyncPluginKey: PluginKey<any>;
|
|
|
8
8
|
* The unique prosemirror plugin key for undoPlugin
|
|
9
9
|
*/
|
|
10
10
|
export declare const yUndoPluginKey: PluginKey<UndoPluginState>;
|
|
11
|
-
/**
|
|
12
|
-
* The unique prosemirror plugin key for cursorPlugin
|
|
13
|
-
*/
|
|
14
|
-
export declare const yCursorPluginKey: PluginKey<any>;
|
|
15
11
|
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,cAAc,gBAA0B,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,CAAC,eAAe,CAErD,CAAC
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,cAAc,gBAA0B,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,CAAC,eAAe,CAErD,CAAC"}
|
|
@@ -7,7 +7,3 @@ export const ySyncPluginKey = new PluginKey('y-sync');
|
|
|
7
7
|
* The unique prosemirror plugin key for undoPlugin
|
|
8
8
|
*/
|
|
9
9
|
export const yUndoPluginKey = new PluginKey('y-undo');
|
|
10
|
-
/**
|
|
11
|
-
* The unique prosemirror plugin key for cursorPlugin
|
|
12
|
-
*/
|
|
13
|
-
export const yCursorPluginKey = new PluginKey('yjs-cursor');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { type EditorView } from 'prosemirror-view';
|
|
3
|
+
import { type Node } from 'prosemirror-model';
|
|
4
|
+
/**
|
|
5
|
+
* Either a node if type is YXmlElement or an Array of text nodes if YXmlText
|
|
6
|
+
*/
|
|
7
|
+
type ProsemirrorMapping = Map<Y.AbstractType<any>, Node>;
|
|
8
|
+
export declare const setMeta: (view: EditorView, key: any, value: any) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Transforms a Prosemirror based absolute position to a Yjs Cursor (relative position in the Yjs model).
|
|
11
|
+
*/
|
|
12
|
+
export declare const absolutePositionToRelativePosition: (pos: number, type: Y.XmlFragment, mapping: ProsemirrorMapping) => any;
|
|
13
|
+
export declare const relativePositionToAbsolutePosition: (yDoc: Y.Doc, documentType: Y.XmlFragment, relPos: any, mapping: ProsemirrorMapping) => null | number;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAI9C;;GAEG;AACH,KAAK,kBAAkB,GAAG,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AA2BzD,eAAO,MAAM,OAAO,GAAI,MAAM,UAAU,EAAE,QAAG,EAAE,UAAK,SAYnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kCAAkC,GAC7C,KAAK,MAAM,EACX,MAAM,CAAC,CAAC,WAAW,EACnB,SAAS,kBAAkB,KAC1B,GA+FF,CAAC;AAaF,eAAO,MAAM,kCAAkC,GAC7C,MAAM,CAAC,CAAC,GAAG,EACX,cAAc,CAAC,CAAC,WAAW,EAC3B,QAAQ,GAAG,EACX,SAAS,kBAAkB,KAC1B,IAAI,GAAG,MA2DT,CAAC"}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
|
-
import { Fragment, Node } from 'prosemirror-model';
|
|
3
|
-
import * as error from 'lib0/error';
|
|
4
|
-
import * as map from 'lib0/map';
|
|
5
|
-
import * as eventloop from 'lib0/eventloop';
|
|
6
|
-
import { createEmptyMeta, createNodeFromYElement, updateYFragment, } from './ySyncPlugin.js';
|
|
7
2
|
import { ySyncPluginKey } from './keys.js';
|
|
8
3
|
/**
|
|
9
4
|
* Is null if no timeout is in progress.
|
|
@@ -14,6 +9,9 @@ let viewsToUpdate = null;
|
|
|
14
9
|
const updateMetas = () => {
|
|
15
10
|
const ups = viewsToUpdate;
|
|
16
11
|
viewsToUpdate = null;
|
|
12
|
+
if (!ups) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
17
15
|
ups.forEach((metas, view) => {
|
|
18
16
|
const tr = view.state.tr;
|
|
19
17
|
const syncState = ySyncPluginKey.getState(view.state);
|
|
@@ -28,9 +26,14 @@ const updateMetas = () => {
|
|
|
28
26
|
export const setMeta = (view, key, value) => {
|
|
29
27
|
if (!viewsToUpdate) {
|
|
30
28
|
viewsToUpdate = new Map();
|
|
31
|
-
|
|
29
|
+
setTimeout(updateMetas, 0);
|
|
30
|
+
}
|
|
31
|
+
let subMap = viewsToUpdate.get(view);
|
|
32
|
+
if (subMap === undefined) {
|
|
33
|
+
subMap = new Map();
|
|
34
|
+
viewsToUpdate.set(view, subMap);
|
|
32
35
|
}
|
|
33
|
-
|
|
36
|
+
subMap.set(key, value);
|
|
34
37
|
};
|
|
35
38
|
/**
|
|
36
39
|
* Transforms a Prosemirror based absolute position to a Yjs Cursor (relative position in the Yjs model).
|
|
@@ -40,9 +43,6 @@ export const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
40
43
|
// if the type is later populated, we want to retain the 0 position (hence assoc=-1)
|
|
41
44
|
return Y.createRelativePositionFromTypeIndex(type, 0, type.length === 0 ? -1 : 0);
|
|
42
45
|
}
|
|
43
|
-
/**
|
|
44
|
-
* @type {any}
|
|
45
|
-
*/
|
|
46
46
|
let n = type._first === null
|
|
47
47
|
? null
|
|
48
48
|
: /** @type {Y.ContentType} */ (type._first.content).type;
|
|
@@ -94,7 +94,7 @@ export const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
94
94
|
return new Y.RelativePosition(n._item === null ? null : n._item.id, n._item === null ? Y.findRootTypeKey(n) : null, null);
|
|
95
95
|
}
|
|
96
96
|
do {
|
|
97
|
-
n =
|
|
97
|
+
n = n._item.parent;
|
|
98
98
|
pos--;
|
|
99
99
|
} while (n !== type && /** @type {Y.Item} */ (n._item).next === null);
|
|
100
100
|
// if n is null at this point, we have an unexpected case
|
|
@@ -108,7 +108,7 @@ export const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
if (n === null) {
|
|
111
|
-
throw
|
|
111
|
+
throw new Error('Unexpected case');
|
|
112
112
|
}
|
|
113
113
|
if (pos === 0 && n.constructor !== Y.XmlText && n !== type) { // TODO: set to <= 0
|
|
114
114
|
return createRelativePosition(n._item.parent, n._item);
|
|
@@ -127,8 +127,8 @@ const createRelativePosition = (type, item) => {
|
|
|
127
127
|
}
|
|
128
128
|
return new Y.RelativePosition(typeid, tname, item.id);
|
|
129
129
|
};
|
|
130
|
-
export const relativePositionToAbsolutePosition = (
|
|
131
|
-
const decodedPos = Y.createAbsolutePositionFromRelativePosition(relPos,
|
|
130
|
+
export const relativePositionToAbsolutePosition = (yDoc, documentType, relPos, mapping) => {
|
|
131
|
+
const decodedPos = Y.createAbsolutePositionFromRelativePosition(relPos, yDoc);
|
|
132
132
|
if (decodedPos === null ||
|
|
133
133
|
(decodedPos.type !== documentType &&
|
|
134
134
|
!Y.isParentOf(documentType, decodedPos.type._item))) {
|
|
@@ -136,7 +136,7 @@ export const relativePositionToAbsolutePosition = (y, documentType, relPos, mapp
|
|
|
136
136
|
}
|
|
137
137
|
let type = decodedPos.type;
|
|
138
138
|
let pos = 0;
|
|
139
|
-
if (type
|
|
139
|
+
if (type instanceof Y.XmlText) {
|
|
140
140
|
pos = decodedPos.index;
|
|
141
141
|
}
|
|
142
142
|
else if (type._item === null || !type._item.deleted) {
|
|
@@ -150,7 +150,8 @@ export const relativePositionToAbsolutePosition = (y, documentType, relPos, mapp
|
|
|
150
150
|
pos += t._length;
|
|
151
151
|
}
|
|
152
152
|
else {
|
|
153
|
-
|
|
153
|
+
const node = mapping.get(t);
|
|
154
|
+
pos += node?.nodeSize || 0;
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
n = n.right;
|
|
@@ -158,15 +159,16 @@ export const relativePositionToAbsolutePosition = (y, documentType, relPos, mapp
|
|
|
158
159
|
pos += 1; // increase because we go out of n
|
|
159
160
|
}
|
|
160
161
|
while (type !== documentType && type._item !== null) {
|
|
161
|
-
// @ts-ignore
|
|
162
162
|
const parent = type._item.parent;
|
|
163
|
-
|
|
163
|
+
if (parent instanceof Y.ID || parent === null) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
164
166
|
if (parent._item === null || !parent._item.deleted) {
|
|
165
167
|
pos += 1; // the start tag
|
|
166
168
|
let n = /** @type {Y.AbstractType} */ (parent)._first;
|
|
167
169
|
// now iterate until we found type
|
|
168
170
|
while (n !== null) {
|
|
169
|
-
const contentType =
|
|
171
|
+
const contentType = n.content.type;
|
|
170
172
|
if (contentType === type) {
|
|
171
173
|
break;
|
|
172
174
|
}
|
|
@@ -175,95 +177,14 @@ export const relativePositionToAbsolutePosition = (y, documentType, relPos, mapp
|
|
|
175
177
|
pos += contentType._length;
|
|
176
178
|
}
|
|
177
179
|
else {
|
|
178
|
-
|
|
180
|
+
const node = mapping.get(contentType);
|
|
181
|
+
pos += node?.nodeSize || 0;
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
n = n.right;
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
|
-
type =
|
|
187
|
+
type = parent;
|
|
185
188
|
}
|
|
186
189
|
return pos - 1; // we don't count the most outer tag, because it is a fragment
|
|
187
190
|
};
|
|
188
|
-
/**
|
|
189
|
-
* Utility function for converting an Y.Fragment to a ProseMirror fragment.
|
|
190
|
-
*/
|
|
191
|
-
export const yXmlFragmentToProseMirrorFragment = (yXmlFragment, schema) => {
|
|
192
|
-
const fragmentContent = yXmlFragment.toArray().map((t) => createNodeFromYElement(
|
|
193
|
-
/** @type {Y.XmlElement} */ (t), schema, createEmptyMeta())).filter((n) => n !== null);
|
|
194
|
-
return Fragment.fromArray(fragmentContent);
|
|
195
|
-
};
|
|
196
|
-
/**
|
|
197
|
-
* Utility function for converting an Y.Fragment to a ProseMirror node.
|
|
198
|
-
*/
|
|
199
|
-
export const yXmlFragmentToProseMirrorRootNode = (yXmlFragment, schema) => schema.topNodeType.create(null, yXmlFragmentToProseMirrorFragment(yXmlFragment, schema));
|
|
200
|
-
/**
|
|
201
|
-
* The initial ProseMirror content should be supplied by Yjs. This function transforms a Y.Fragment
|
|
202
|
-
* to a ProseMirror Doc node and creates a mapping that is used by the sync plugin.
|
|
203
|
-
*
|
|
204
|
-
* @todo deprecate mapping property
|
|
205
|
-
*/
|
|
206
|
-
export const initProseMirrorDoc = (yXmlFragment, schema) => {
|
|
207
|
-
const meta = createEmptyMeta();
|
|
208
|
-
const fragmentContent = yXmlFragment.toArray().map((t) => createNodeFromYElement(
|
|
209
|
-
/** @type {Y.XmlElement} */ (t), schema, meta)).filter((n) => n !== null);
|
|
210
|
-
const doc = schema.topNodeType.create(null, Fragment.fromArray(fragmentContent));
|
|
211
|
-
return { doc, meta, mapping: meta.mapping };
|
|
212
|
-
};
|
|
213
|
-
/**
|
|
214
|
-
* Utility method to convert a Prosemirror Doc Node into a Y.Doc.
|
|
215
|
-
*
|
|
216
|
-
* This can be used when importing existing content to Y.Doc for the first time,
|
|
217
|
-
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
218
|
-
* collaboration has begun as all history will be lost
|
|
219
|
-
*/
|
|
220
|
-
export function prosemirrorToYDoc(doc, xmlFragment = 'prosemirror') {
|
|
221
|
-
const ydoc = new Y.Doc();
|
|
222
|
-
const type =
|
|
223
|
-
/** @type {Y.XmlFragment} */ (ydoc.get(xmlFragment, Y.XmlFragment));
|
|
224
|
-
if (!type.doc) {
|
|
225
|
-
return ydoc;
|
|
226
|
-
}
|
|
227
|
-
prosemirrorToYXmlFragment(doc, type);
|
|
228
|
-
return type.doc;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Utility method to update an empty Y.XmlFragment with content from a Prosemirror Doc Node.
|
|
232
|
-
*
|
|
233
|
-
* This can be used when importing existing content to Y.Doc for the first time,
|
|
234
|
-
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
235
|
-
* collaboration has begun as all history will be lost
|
|
236
|
-
*
|
|
237
|
-
* Note: The Y.XmlFragment does not need to be part of a Y.Doc document at the time that this
|
|
238
|
-
* method is called, but it must be added before any other operations are performed on it.
|
|
239
|
-
*/
|
|
240
|
-
export function prosemirrorToYXmlFragment(doc, xmlFragment) {
|
|
241
|
-
const type = xmlFragment || new Y.XmlFragment();
|
|
242
|
-
const ydoc = type.doc
|
|
243
|
-
? type.doc
|
|
244
|
-
: { transact: (transaction) => transaction(undefined) };
|
|
245
|
-
updateYFragment(ydoc, type, doc, { mapping: new Map(), isOMark: new Map() });
|
|
246
|
-
return type;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Utility method to convert Prosemirror compatible JSON into a Y.Doc.
|
|
250
|
-
*
|
|
251
|
-
* This can be used when importing existing content to Y.Doc for the first time,
|
|
252
|
-
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
253
|
-
* collaboration has begun as all history will be lost
|
|
254
|
-
*/
|
|
255
|
-
export function prosemirrorJSONToYDoc(schema, state, xmlFragment = 'prosemirror') {
|
|
256
|
-
const doc = Node.fromJSON(schema, state);
|
|
257
|
-
return prosemirrorToYDoc(doc, xmlFragment);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Utility method to convert Prosemirror compatible JSON to a Y.XmlFragment
|
|
261
|
-
*
|
|
262
|
-
* This can be used when importing existing content to Y.Doc for the first time,
|
|
263
|
-
* note that this should not be used to rehydrate a Y.Doc from a database once
|
|
264
|
-
* collaboration has begun as all history will be lost
|
|
265
|
-
*/
|
|
266
|
-
export function prosemirrorJSONToYXmlFragment(schema, state, xmlFragment) {
|
|
267
|
-
const doc = Node.fromJSON(schema, state);
|
|
268
|
-
return prosemirrorToYXmlFragment(doc, xmlFragment);
|
|
269
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as awarenessProtocol from 'y-protocols/awareness';
|
|
2
|
+
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
3
|
+
import type { CoreEditor } from '../../editor/src/mod.js';
|
|
4
|
+
export declare const yPositionPluginKey: PluginKey<any>;
|
|
5
|
+
interface PositionPluginConfig {
|
|
6
|
+
getSelection?: (arg0: any) => any;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Default awareness state filter
|
|
10
|
+
*/
|
|
11
|
+
export declare const defaultAwarenessStateFilter: (currentClientId: number, userClientId: number, _user: any) => boolean;
|
|
12
|
+
export declare const yPositionPlugin: (awareness: awarenessProtocol.Awareness, editor: CoreEditor, { getSelection, }?: PositionPluginConfig, cursorStateField?: string) => Plugin<any>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=yPositionPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yPositionPlugin.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/yPositionPlugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAe,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAc1D,eAAO,MAAM,kBAAkB,gBAAgC,CAAC;AAYhE,UAAU,oBAAoB;IAC5B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;CACnC;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,GACtC,iBAAiB,MAAM,EACvB,cAAc,MAAM,EACpB,OAAO,GAAG,KACT,OAA2C,CAAC;AAE/C,eAAO,MAAM,eAAe,GAC1B,WAAW,iBAAiB,CAAC,SAAS,EACtC,QAAQ,UAAU,EAClB,oBAEG,oBAAyB,EAC5B,mBAAkB,MAAiB,gBAiLpC,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
3
|
+
import { remoteSelectionPluginKey } from '../../extension-basic-editor/src/remote-selection/ExtensionRemoteSelection.js';
|
|
4
|
+
import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, } from './lib.js';
|
|
5
|
+
import { ySyncPluginKey } from './keys.js';
|
|
6
|
+
export const yPositionPluginKey = new PluginKey('yjs-position');
|
|
7
|
+
/**
|
|
8
|
+
* Default awareness state filter
|
|
9
|
+
*/
|
|
10
|
+
export const defaultAwarenessStateFilter = (currentClientId, userClientId, _user) => currentClientId !== userClientId;
|
|
11
|
+
export const yPositionPlugin = (awareness, editor, { getSelection = (state) => state.selection, } = {}, cursorStateField = 'cursor') => {
|
|
12
|
+
return new Plugin({
|
|
13
|
+
key: yPositionPluginKey,
|
|
14
|
+
view: (view) => {
|
|
15
|
+
const extension = editor.getExtension('remote-selection');
|
|
16
|
+
const awarenessListener = ({ added, updated, removed }) => {
|
|
17
|
+
const clients = added.concat(updated).concat(removed);
|
|
18
|
+
if (clients.findIndex((id) => id !== awareness.doc.clientID) ===
|
|
19
|
+
-1) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (view.docView) {
|
|
23
|
+
setMeta(view, remoteSelectionPluginKey, {
|
|
24
|
+
remotePositionUpdated: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const remoteStates = [];
|
|
28
|
+
const ystate = ySyncPluginKey.getState(view.state);
|
|
29
|
+
const y = ystate.doc;
|
|
30
|
+
awareness.getStates().forEach((aw, clientId) => {
|
|
31
|
+
if (!defaultAwarenessStateFilter(y.clientID, clientId, aw)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!aw.cursor) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let anchor = relativePositionToAbsolutePosition(y, ystate.type, Y.createRelativePositionFromJSON(aw.cursor.anchor), ystate.binding.mapping);
|
|
38
|
+
let head = relativePositionToAbsolutePosition(y, ystate.type, Y.createRelativePositionFromJSON(aw.cursor.head), ystate.binding.mapping);
|
|
39
|
+
if (anchor !== null && head !== null) {
|
|
40
|
+
remoteStates.push({
|
|
41
|
+
clientId,
|
|
42
|
+
user: {
|
|
43
|
+
name: aw.user?.name,
|
|
44
|
+
color: aw.user?.color,
|
|
45
|
+
colorLight: aw.user?.colorLight,
|
|
46
|
+
},
|
|
47
|
+
cursor: {
|
|
48
|
+
anchor,
|
|
49
|
+
head,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
extension.setRemoteStates(remoteStates);
|
|
55
|
+
// view.dispatch({ annotations: [yRemoteSelectionsAnnotation.of([])] });
|
|
56
|
+
};
|
|
57
|
+
{
|
|
58
|
+
// if (
|
|
59
|
+
// ystate.snapshot != null || ystate.prevSnapshot != null ||
|
|
60
|
+
// ystate.binding.mapping.size === 0
|
|
61
|
+
// ) {
|
|
62
|
+
// // do not render cursors while snapshot is active
|
|
63
|
+
// return DecorationSet.create(state.doc, []);
|
|
64
|
+
// }
|
|
65
|
+
}
|
|
66
|
+
const updateAwareness = (selectionAnchor, selectionHead) => {
|
|
67
|
+
const ystate = ySyncPluginKey.getState(view.state);
|
|
68
|
+
const current = awareness.getLocalState() || {};
|
|
69
|
+
const anchor = absolutePositionToRelativePosition(selectionAnchor, ystate.type, ystate.binding.mapping);
|
|
70
|
+
const head = absolutePositionToRelativePosition(selectionHead, ystate.type, ystate.binding.mapping);
|
|
71
|
+
if (current.cursor == null ||
|
|
72
|
+
!Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.anchor), anchor) ||
|
|
73
|
+
!Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.head), head)) {
|
|
74
|
+
awareness.setLocalStateField(cursorStateField, {
|
|
75
|
+
anchor,
|
|
76
|
+
head,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const clearAwareness = () => {
|
|
81
|
+
const ystate = ySyncPluginKey.getState(view.state);
|
|
82
|
+
const current = awareness.getLocalState() || {};
|
|
83
|
+
if (current.cursor != null &&
|
|
84
|
+
relativePositionToAbsolutePosition(ystate.doc, ystate.type, Y.createRelativePositionFromJSON(current.cursor.anchor), ystate.binding.mapping) !== null) {
|
|
85
|
+
// delete cursor information if current cursor information is owned by this editor binding
|
|
86
|
+
awareness.setLocalStateField(cursorStateField, null);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const updateCursorInfo = () => {
|
|
90
|
+
if (view.hasFocus()) {
|
|
91
|
+
const selection = getSelection(view.state);
|
|
92
|
+
updateAwareness(selection.anchor, selection.head);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// clearAwareness();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const localPositionChangedListener = (event) => {
|
|
99
|
+
const { detail } = event;
|
|
100
|
+
updateAwareness(detail.anchor, detail.head);
|
|
101
|
+
};
|
|
102
|
+
editor.addEventListener('localPositionChanged', localPositionChangedListener);
|
|
103
|
+
awareness.on('change', awarenessListener);
|
|
104
|
+
view.dom.addEventListener('focusin', updateCursorInfo);
|
|
105
|
+
view.dom.addEventListener('focusout', updateCursorInfo);
|
|
106
|
+
return {
|
|
107
|
+
update: updateCursorInfo,
|
|
108
|
+
destroy: () => {
|
|
109
|
+
view.dom.removeEventListener('focusin', updateCursorInfo);
|
|
110
|
+
view.dom.removeEventListener('focusout', updateCursorInfo);
|
|
111
|
+
awareness.off('change', awarenessListener);
|
|
112
|
+
awareness.setLocalStateField(cursorStateField, null);
|
|
113
|
+
editor.removeEventListener('localPositionChanged', localPositionChangedListener);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
updateCursorInfo(state) {
|
|
118
|
+
throw new Error('TODO: merge with updateCursorInfo above');
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
};
|