@kerebron/extension-yjs 0.0.1

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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,42 @@
1
+ import * as awarenessProtocol from 'y-protocols/awareness';
2
+ import { DecorationAttrs } from 'prosemirror-view';
3
+ /**
4
+ * Default awareness state filter
5
+ *
6
+ * @param {number} currentClientId current client id
7
+ * @param {number} userClientId user client id
8
+ * @param {any} _user user data
9
+ * @return {boolean}
10
+ */
11
+ export declare const defaultAwarenessStateFilter: (currentClientId: number, userClientId: number, _user: any) => boolean;
12
+ /**
13
+ * Default generator for a cursor element
14
+ *
15
+ * @param {any} user user data
16
+ * @return {HTMLElement}
17
+ */
18
+ export declare const defaultCursorBuilder: (user: any) => any;
19
+ /**
20
+ * Default generator for the selection attributes
21
+ *
22
+ * @param {any} user user data
23
+ * @return {import('prosemirror-view').DecorationAttrs}
24
+ */
25
+ export declare const defaultSelectionBuilder: (user: any) => {
26
+ style: string;
27
+ class: string;
28
+ };
29
+ export declare const createDecorations: (state: any, awareness: awarenessProtocol.Awareness, awarenessFilter: (arg0: number, arg1: number, arg2: any) => boolean, createCursor: (user: {
30
+ name: string;
31
+ color: string;
32
+ }, clientId: number) => Element, createSelection: (user: {
33
+ name: string;
34
+ color: string;
35
+ }, clientId: number) => DecorationAttrs) => any;
36
+ export declare const yCursorPlugin: (awareness: awarenessProtocol.Awareness, { awarenessStateFilter, cursorBuilder, selectionBuilder, getSelection, }?: {
37
+ awarenessStateFilter?: (arg0: any, arg1: any, arg2: any) => boolean;
38
+ cursorBuilder?: (user: any, clientId: number) => HTMLElement;
39
+ selectionBuilder?: (user: any, clientId: number) => DecorationAttrs;
40
+ getSelection?: (arg0: any) => any;
41
+ }, cursorStateField?: string) => any;
42
+ //# sourceMappingURL=yCursorPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yCursorPlugin.d.ts","sourceRoot":"","sources":["../src/yCursorPlugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAI3D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAYnD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,oBACrB,MAAM,gBACT,MAAM,SACb,GAAG,KACT,OAA2C,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,oBAahC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;CAKnC,CAAC;AAIF,eAAO,MAAM,iBAAiB,UACrB,GAAG,aACC,iBAAiB,CAAC,SAAS,mBACrB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,gBACrD,CACZ,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EACrC,QAAQ,EAAE,MAAM,KACb,OAAO,mBACK,CACf,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EACrC,QAAQ,EAAE,MAAM,KACb,eAAe,KACnB,GA6DF,CAAC;AAEF,eAAO,MAAM,aAAa,cACb,iBAAiB,CAAC,SAAS,6EAMnC;IACD,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC;IACpE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC7D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,eAAe,CAAC;IACpE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;CACnC,qBACiB,MAAM,QAiHzB,CAAC"}
@@ -0,0 +1,169 @@
1
+ import * as Y from 'yjs';
2
+ import { Decoration, DecorationSet } from 'prosemirror-view'; // eslint-disable-line
3
+ import { Plugin } from 'prosemirror-state'; // eslint-disable-line
4
+ import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, } from 'y-prosemirror';
5
+ import { yCursorPluginKey, ySyncPluginKey } from 'y-prosemirror';
6
+ import * as math from 'lib0/math';
7
+ /**
8
+ * Default awareness state filter
9
+ *
10
+ * @param {number} currentClientId current client id
11
+ * @param {number} userClientId user client id
12
+ * @param {any} _user user data
13
+ * @return {boolean}
14
+ */
15
+ export const defaultAwarenessStateFilter = (currentClientId, userClientId, _user) => currentClientId !== userClientId;
16
+ /**
17
+ * Default generator for a cursor element
18
+ *
19
+ * @param {any} user user data
20
+ * @return {HTMLElement}
21
+ */
22
+ export const defaultCursorBuilder = (user) => {
23
+ const cursor = document.createElement('span');
24
+ cursor.classList.add('ProseMirror-yjs-cursor');
25
+ cursor.setAttribute('style', `border-color: ${user.color}`);
26
+ const userDiv = document.createElement('div');
27
+ userDiv.setAttribute('style', `background-color: ${user.color}`);
28
+ userDiv.insertBefore(document.createTextNode(user.name), null);
29
+ const nonbreakingSpace1 = document.createTextNode('\u2060');
30
+ const nonbreakingSpace2 = document.createTextNode('\u2060');
31
+ cursor.insertBefore(nonbreakingSpace1, null);
32
+ cursor.insertBefore(userDiv, null);
33
+ cursor.insertBefore(nonbreakingSpace2, null);
34
+ return cursor;
35
+ };
36
+ /**
37
+ * Default generator for the selection attributes
38
+ *
39
+ * @param {any} user user data
40
+ * @return {import('prosemirror-view').DecorationAttrs}
41
+ */
42
+ export const defaultSelectionBuilder = (user) => {
43
+ return {
44
+ style: `background-color: ${user.color}70`,
45
+ class: 'ProseMirror-yjs-selection',
46
+ };
47
+ };
48
+ const rxValidColor = /^#[0-9a-fA-F]{6}$/;
49
+ export const createDecorations = (state, awareness, awarenessFilter, createCursor, createSelection) => {
50
+ const ystate = ySyncPluginKey.getState(state);
51
+ const y = ystate.doc;
52
+ const decorations = [];
53
+ if (ystate.snapshot != null || ystate.prevSnapshot != null ||
54
+ ystate.binding.mapping.size === 0) {
55
+ // do not render cursors while snapshot is active
56
+ return DecorationSet.create(state.doc, []);
57
+ }
58
+ awareness.getStates().forEach((aw, clientId) => {
59
+ if (!awarenessFilter(y.clientID, clientId, aw)) {
60
+ return;
61
+ }
62
+ if (aw.cursor != null) {
63
+ const user = aw.user || {};
64
+ if (user.color == null) {
65
+ user.color = '#ffa500';
66
+ }
67
+ else if (!rxValidColor.test(user.color)) {
68
+ // We only support 6-digit RGB colors in y-prosemirror
69
+ console.warn('A user uses an unsupported color format', user);
70
+ }
71
+ if (user.name == null) {
72
+ user.name = `User: ${clientId}`;
73
+ }
74
+ let anchor = relativePositionToAbsolutePosition(y, ystate.type, Y.createRelativePositionFromJSON(aw.cursor.anchor), ystate.binding.mapping);
75
+ let head = relativePositionToAbsolutePosition(y, ystate.type, Y.createRelativePositionFromJSON(aw.cursor.head), ystate.binding.mapping);
76
+ if (anchor !== null && head !== null) {
77
+ const maxsize = math.max(state.doc.content.size - 1, 0);
78
+ anchor = math.min(anchor, maxsize);
79
+ head = math.min(head, maxsize);
80
+ decorations.push(Decoration.widget(head, () => createCursor(user, clientId), {
81
+ key: clientId + '',
82
+ side: 10,
83
+ }));
84
+ const from = math.min(anchor, head);
85
+ const to = math.max(anchor, head);
86
+ decorations.push(Decoration.inline(from, to, createSelection(user, clientId), {
87
+ inclusiveEnd: true,
88
+ inclusiveStart: false,
89
+ }));
90
+ }
91
+ }
92
+ });
93
+ return DecorationSet.create(state.doc, decorations);
94
+ };
95
+ export const yCursorPlugin = (awareness, { awarenessStateFilter = defaultAwarenessStateFilter, cursorBuilder = defaultCursorBuilder, selectionBuilder = defaultSelectionBuilder, getSelection = (state) => state.selection, } = {}, cursorStateField = 'cursor') => {
96
+ return new Plugin({
97
+ key: yCursorPluginKey,
98
+ state: {
99
+ init(_, state) {
100
+ return createDecorations(state, awareness, awarenessStateFilter, cursorBuilder, selectionBuilder);
101
+ },
102
+ apply(tr, prevState, _oldState, newState) {
103
+ const ystate = ySyncPluginKey.getState(newState);
104
+ const yCursorState = tr.getMeta(yCursorPluginKey);
105
+ if ((ystate && ystate.isChangeOrigin) ||
106
+ (yCursorState && yCursorState.awarenessUpdated)) {
107
+ return createDecorations(newState, awareness, awarenessStateFilter, cursorBuilder, selectionBuilder);
108
+ }
109
+ return prevState.map(tr.mapping, tr.doc);
110
+ },
111
+ },
112
+ props: {
113
+ decorations: (state) => {
114
+ return yCursorPluginKey.getState(state);
115
+ },
116
+ },
117
+ view: (view) => {
118
+ const awarenessListener = () => {
119
+ // @ts-ignore
120
+ if (view.docView) {
121
+ setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
122
+ }
123
+ };
124
+ const updateCursorInfo = () => {
125
+ const ystate = ySyncPluginKey.getState(view.state);
126
+ // @note We make implicit checks when checking for the cursor property
127
+ const current = awareness.getLocalState() || {};
128
+ const selection = getSelection(view.state);
129
+ if (view.hasFocus()) {
130
+ const selection = getSelection(view.state);
131
+ /**
132
+ * @type {Y.RelativePosition}
133
+ */
134
+ const anchor = absolutePositionToRelativePosition(selection.anchor, ystate.type, ystate.binding.mapping);
135
+ /**
136
+ * @type {Y.RelativePosition}
137
+ */
138
+ const head = absolutePositionToRelativePosition(selection.head, ystate.type, ystate.binding.mapping);
139
+ if (current.cursor == null ||
140
+ !Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.anchor), anchor) ||
141
+ !Y.compareRelativePositions(Y.createRelativePositionFromJSON(current.cursor.head), head)) {
142
+ awareness.setLocalStateField(cursorStateField, {
143
+ anchor,
144
+ head,
145
+ });
146
+ awareness.setLocalStateField('cm-cursor', null);
147
+ }
148
+ }
149
+ else if (current.cursor != null &&
150
+ relativePositionToAbsolutePosition(ystate.doc, ystate.type, Y.createRelativePositionFromJSON(current.cursor.anchor), ystate.binding.mapping) !== null) {
151
+ // delete cursor information if current cursor information is owned by this editor binding
152
+ awareness.setLocalStateField(cursorStateField, null);
153
+ }
154
+ };
155
+ awareness.on('change', awarenessListener);
156
+ view.dom.addEventListener('focusin', updateCursorInfo);
157
+ view.dom.addEventListener('focusout', updateCursorInfo);
158
+ return {
159
+ update: updateCursorInfo,
160
+ destroy: () => {
161
+ view.dom.removeEventListener('focusin', updateCursorInfo);
162
+ view.dom.removeEventListener('focusout', updateCursorInfo);
163
+ awareness.off('change', awarenessListener);
164
+ awareness.setLocalStateField(cursorStateField, null);
165
+ },
166
+ };
167
+ },
168
+ });
169
+ };
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@kerebron/extension-yjs",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "module": "./esm/ExtensionYjs.js",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./esm/ExtensionYjs.js"
9
+ }
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^20.9.0"
13
+ },
14
+ "_generatedBy": "dnt@dev"
15
+ }