@liveblocks/node-lexical 1.12.0-lexical3
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/README.md +19 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +233 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://liveblocks.io#gh-light-mode-only">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-light.svg" alt="Liveblocks" />
|
|
4
|
+
</a>
|
|
5
|
+
<a href="https://liveblocks.io#gh-dark-mode-only">
|
|
6
|
+
<img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-dark.svg" alt="Liveblocks" />
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
# `@liveblocks/node-lexical`
|
|
11
|
+
|
|
12
|
+
WIP
|
|
13
|
+
|
|
14
|
+
## License
|
|
15
|
+
|
|
16
|
+
Licensed under the Apache License 2.0, Copyright © 2021-present
|
|
17
|
+
[Liveblocks](https://liveblocks.io).
|
|
18
|
+
|
|
19
|
+
See [LICENSE](../../LICENSE) for more information.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Liveblocks } from '@liveblocks/node';
|
|
2
|
+
import { Klass, LexicalNode, LexicalNodeReplacement, EditorState, RootNode } from 'lexical';
|
|
3
|
+
export { $createParagraphNode, $createTextNode, $getRoot } from 'lexical';
|
|
4
|
+
|
|
5
|
+
declare function getEditorState(client: Liveblocks, roomId: string, nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>): Promise<EditorState>;
|
|
6
|
+
declare function modifyDocument(client: Liveblocks, roomId: string, nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>, modifyFn: (root: RootNode) => void): Promise<void>;
|
|
7
|
+
|
|
8
|
+
export { getEditorState, modifyDocument };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Liveblocks } from '@liveblocks/node';
|
|
2
|
+
import { Klass, LexicalNode, LexicalNodeReplacement, EditorState, RootNode } from 'lexical';
|
|
3
|
+
export { $createParagraphNode, $createTextNode, $getRoot } from 'lexical';
|
|
4
|
+
|
|
5
|
+
declare function getEditorState(client: Liveblocks, roomId: string, nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>): Promise<EditorState>;
|
|
6
|
+
declare function modifyDocument(client: Liveblocks, roomId: string, nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>, modifyFn: (root: RootNode) => void): Promise<void>;
|
|
7
|
+
|
|
8
|
+
export { getEditorState, modifyDocument };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/index.ts
|
|
2
|
+
var _core = require('@liveblocks/core');
|
|
3
|
+
var _lexical = require('lexical');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
var _yjs = require('yjs');
|
|
9
|
+
|
|
10
|
+
// src/headless.ts
|
|
11
|
+
var _headless = require('@lexical/headless');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
var _yjs3 = require('@lexical/yjs');
|
|
17
|
+
|
|
18
|
+
function withHeadlessCollaborationEditor(nodes, callback) {
|
|
19
|
+
const editor = _headless.createHeadlessEditor.call(void 0, {
|
|
20
|
+
nodes
|
|
21
|
+
});
|
|
22
|
+
const id = "root";
|
|
23
|
+
const doc = new (0, _yjs.Doc)();
|
|
24
|
+
const docMap = /* @__PURE__ */ new Map([[id, doc]]);
|
|
25
|
+
const provider = createNoOpProvider();
|
|
26
|
+
const binding = _yjs3.createBinding.call(void 0, editor, provider, id, doc, docMap);
|
|
27
|
+
const unsubscribe = registerCollaborationListeners(editor, provider, binding);
|
|
28
|
+
const res = callback(editor, binding, provider);
|
|
29
|
+
unsubscribe();
|
|
30
|
+
return res;
|
|
31
|
+
}
|
|
32
|
+
function registerCollaborationListeners(editor, provider, binding) {
|
|
33
|
+
const unsubscribeUpdateListener = editor.registerUpdateListener(
|
|
34
|
+
({
|
|
35
|
+
dirtyElements,
|
|
36
|
+
dirtyLeaves,
|
|
37
|
+
editorState,
|
|
38
|
+
normalizedNodes,
|
|
39
|
+
prevEditorState,
|
|
40
|
+
tags
|
|
41
|
+
}) => {
|
|
42
|
+
if (tags.has("skip-collab") === false) {
|
|
43
|
+
_yjs3.syncLexicalUpdateToYjs.call(void 0,
|
|
44
|
+
binding,
|
|
45
|
+
provider,
|
|
46
|
+
prevEditorState,
|
|
47
|
+
editorState,
|
|
48
|
+
dirtyElements,
|
|
49
|
+
dirtyLeaves,
|
|
50
|
+
normalizedNodes,
|
|
51
|
+
tags
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
const observer = (events, transaction) => {
|
|
57
|
+
if (transaction.origin !== binding) {
|
|
58
|
+
_yjs3.syncYjsChangesToLexical.call(void 0, binding, provider, events, false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
binding.root.getSharedType().observeDeep(observer);
|
|
62
|
+
return () => {
|
|
63
|
+
unsubscribeUpdateListener();
|
|
64
|
+
binding.root.getSharedType().unobserveDeep(observer);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createNoOpProvider() {
|
|
68
|
+
const emptyFunction = () => {
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
awareness: {
|
|
72
|
+
getLocalState: () => null,
|
|
73
|
+
getStates: () => /* @__PURE__ */ new Map(),
|
|
74
|
+
off: emptyFunction,
|
|
75
|
+
on: emptyFunction,
|
|
76
|
+
setLocalState: emptyFunction
|
|
77
|
+
},
|
|
78
|
+
connect: emptyFunction,
|
|
79
|
+
disconnect: emptyFunction,
|
|
80
|
+
off: emptyFunction,
|
|
81
|
+
on: emptyFunction
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/MentionNodeLite.ts
|
|
86
|
+
|
|
87
|
+
var MentionNode = class _MentionNode extends _lexical.DecoratorNode {
|
|
88
|
+
constructor(value, key) {
|
|
89
|
+
super(key);
|
|
90
|
+
this.__id = value;
|
|
91
|
+
}
|
|
92
|
+
static getType() {
|
|
93
|
+
return "lb-mention";
|
|
94
|
+
}
|
|
95
|
+
static clone(node) {
|
|
96
|
+
return new _MentionNode(node.__id);
|
|
97
|
+
}
|
|
98
|
+
static importJSON(serializedNode) {
|
|
99
|
+
const node = new _MentionNode(serializedNode.value);
|
|
100
|
+
return _lexical.$applyNodeReplacement.call(void 0, node);
|
|
101
|
+
}
|
|
102
|
+
exportJSON() {
|
|
103
|
+
return {
|
|
104
|
+
value: this.getTextContent(),
|
|
105
|
+
type: "lb-mention",
|
|
106
|
+
version: 1
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
getTextContent() {
|
|
110
|
+
const self = this.getLatest();
|
|
111
|
+
return self.__id;
|
|
112
|
+
}
|
|
113
|
+
decorate() {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/ThreadNodeLite.ts
|
|
119
|
+
|
|
120
|
+
var ThreadMarkNode = class _ThreadMarkNode extends _lexical.ElementNode {
|
|
121
|
+
constructor(ids, key) {
|
|
122
|
+
super(key);
|
|
123
|
+
this.__ids = ids || [];
|
|
124
|
+
}
|
|
125
|
+
// The ids of the threads that this mark is associated with
|
|
126
|
+
static getType() {
|
|
127
|
+
return "lb-thread-mark";
|
|
128
|
+
}
|
|
129
|
+
static clone(node) {
|
|
130
|
+
return new _ThreadMarkNode(Array.from(node.__ids), node.__key);
|
|
131
|
+
}
|
|
132
|
+
static importJSON(serializedNode) {
|
|
133
|
+
const node = _lexical.$applyNodeReplacement.call(void 0,
|
|
134
|
+
new _ThreadMarkNode(serializedNode.ids)
|
|
135
|
+
);
|
|
136
|
+
node.setFormat(serializedNode.format);
|
|
137
|
+
node.setIndent(serializedNode.indent);
|
|
138
|
+
node.setDirection(serializedNode.direction);
|
|
139
|
+
return node;
|
|
140
|
+
}
|
|
141
|
+
exportJSON() {
|
|
142
|
+
return {
|
|
143
|
+
...super.exportJSON(),
|
|
144
|
+
ids: this.getIDs(),
|
|
145
|
+
type: "lb-thread-mark",
|
|
146
|
+
version: 1
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
getIDs() {
|
|
150
|
+
const self = this.getLatest();
|
|
151
|
+
return self instanceof _ThreadMarkNode ? self.__ids : [];
|
|
152
|
+
}
|
|
153
|
+
canInsertTextBefore() {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
canInsertTextAfter() {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
canBeEmpty() {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
isInline() {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
extractWithChild(_, selection, destination) {
|
|
166
|
+
if (!_lexical.$isRangeSelection.call(void 0, selection) || destination === "html") {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const anchor = selection.anchor;
|
|
170
|
+
const focus = selection.focus;
|
|
171
|
+
const anchorNode = anchor.getNode();
|
|
172
|
+
const focusNode = focus.getNode();
|
|
173
|
+
const isBackward = selection.isBackward();
|
|
174
|
+
const selectionLength = isBackward ? anchor.offset - focus.offset : focus.offset - anchor.offset;
|
|
175
|
+
return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && this.getTextContent().length === selectionLength;
|
|
176
|
+
}
|
|
177
|
+
excludeFromCopy(destination) {
|
|
178
|
+
return destination !== "clone";
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/version.ts
|
|
183
|
+
var PKG_NAME = "@liveblocks/node-lexical";
|
|
184
|
+
var PKG_VERSION = "1.12.0-lexical2";
|
|
185
|
+
var PKG_FORMAT = "cjs";
|
|
186
|
+
|
|
187
|
+
// src/index.ts
|
|
188
|
+
|
|
189
|
+
_core.detectDupes.call(void 0, PKG_NAME, PKG_VERSION, PKG_FORMAT);
|
|
190
|
+
var LIVEBLOCKS_NODES = [ThreadMarkNode, MentionNode];
|
|
191
|
+
async function getEditorState(client, roomId, nodes) {
|
|
192
|
+
const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);
|
|
193
|
+
const update = new Uint8Array(doc);
|
|
194
|
+
return withHeadlessCollaborationEditor(
|
|
195
|
+
[...LIVEBLOCKS_NODES, ...nodes],
|
|
196
|
+
(editor, binding) => {
|
|
197
|
+
_yjs.applyUpdate.call(void 0, binding.doc, update);
|
|
198
|
+
editor.update(() => {
|
|
199
|
+
}, { discrete: true });
|
|
200
|
+
return editor.getEditorState();
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
async function modifyDocument(client, roomId, nodes, modifyFn) {
|
|
205
|
+
const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);
|
|
206
|
+
const update = new Uint8Array(doc);
|
|
207
|
+
await withHeadlessCollaborationEditor(
|
|
208
|
+
[...LIVEBLOCKS_NODES, ...nodes],
|
|
209
|
+
(editor, binding) => {
|
|
210
|
+
_yjs.applyUpdate.call(void 0, binding.doc, update);
|
|
211
|
+
editor.update(() => {
|
|
212
|
+
}, { discrete: true });
|
|
213
|
+
const beforeVector = _yjs.encodeStateVectorFromUpdate.call(void 0, update);
|
|
214
|
+
editor.update(
|
|
215
|
+
() => {
|
|
216
|
+
const root = _lexical.$getRoot.call(void 0, );
|
|
217
|
+
modifyFn(root);
|
|
218
|
+
},
|
|
219
|
+
{ discrete: true }
|
|
220
|
+
);
|
|
221
|
+
const afterUpdate = _yjs.encodeStateAsUpdate.call(void 0, binding.doc, beforeVector);
|
|
222
|
+
return client.sendYjsBinaryUpdate(roomId, afterUpdate);
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
exports.$createParagraphNode = _lexical.$createParagraphNode; exports.$createTextNode = _lexical.$createTextNode; exports.$getRoot = _lexical.$getRoot; exports.getEditorState = getEditorState; exports.modifyDocument = modifyDocument;
|
|
233
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/headless.ts","../src/MentionNodeLite.ts","../src/ThreadNodeLite.ts","../src/version.ts"],"names":["applyUpdate","$applyNodeReplacement","$getRoot"],"mappings":";AAAA,SAAS,mBAAmB;AAS5B,SAAS,gBAAgB;AACzB;AAAA,EACE,eAAAA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACdP,SAAS,4BAA4B;AAErC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,SAAS,aAAa,WAAW;AAa1B,SAAS,gCACd,OACA,UACG;AACH,QAAM,SAAS,qBAAqB;AAAA,IAClC;AAAA,EACF,CAAC;AAED,QAAM,KAAK;AACX,QAAM,MAAM,IAAI,IAAI;AACpB,QAAM,SAAS,oBAAI,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;AAClC,QAAM,WAAW,mBAAmB;AACpC,QAAM,UAAU,cAAc,QAAQ,UAAU,IAAI,KAAK,MAAM;AAE/D,QAAM,cAAc,+BAA+B,QAAQ,UAAU,OAAO;AAE5E,QAAM,MAAM,SAAS,QAAQ,SAAS,QAAQ;AAE9C,cAAY;AAEZ,SAAO;AACT;AAEA,SAAS,+BACP,QACA,UACA,SACY;AACZ,QAAM,4BAA4B,OAAO;AAAA,IACvC,CAAC;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAAM;AACJ,UAAI,KAAK,IAAI,aAAa,MAAM,OAAO;AACrC;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,CAAC,QAA4B,gBAA6B;AACzE,QAAI,YAAY,WAAW,SAAS;AAElC,8BAAwB,SAAS,UAAU,QAAQ,KAAK;AAAA,IAC1D;AAAA,EACF;AAEA,UAAQ,KAAK,cAAc,EAAE,YAAY,QAAQ;AAEjD,SAAO,MAAM;AACX,8BAA0B;AAC1B,YAAQ,KAAK,cAAc,EAAE,cAAc,QAAQ;AAAA,EACrD;AACF;AAEA,SAAS,qBAA+B;AACtC,QAAM,gBAAgB,MAAM;AAAA,EAAC;AAE7B,SAAO;AAAA,IACL,WAAW;AAAA,MACT,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM,oBAAI,IAAI;AAAA,MACzB,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,eAAe;AAAA,IACjB;AAAA,IACA,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,IAAI;AAAA,EACN;AACF;;;AChHA,SAAS,uBAAuB,qBAAqB;AAS9C,IAAM,cAAN,MAAM,qBAAoB,cAAoB;AAAA,EAGnD,YAAY,OAAe,KAAe;AACxC,UAAM,GAAG;AACT,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,OAAO,UAAkB;AACvB,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAM,MAAgC;AAC3C,WAAO,IAAI,aAAY,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,OAAO,WAAW,gBAAoD;AACpE,UAAM,OAAO,IAAI,aAAY,eAAe,KAAK;AACjD,WAAO,sBAAsB,IAAI;AAAA,EACnC;AAAA,EAEA,aAAoC;AAClC,WAAO;AAAA,MACL,OAAO,KAAK,eAAe;AAAA,MAC3B,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEA,iBAAyB;AACvB,UAAM,OAAO,KAAK,UAAU;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAiB;AACf,WAAO;AAAA,EACT;AACF;;;ACxCA,SAAS,yBAAAC,wBAAuB,mBAAmB,mBAAmB;AAS/D,IAAM,iBAAN,MAAM,wBAAuB,YAAY;AAAA,EAoC9C,YAAY,KAAoB,KAAe;AAC7C,UAAM,GAAG;AACT,SAAK,QAAQ,OAAO,CAAC;AAAA,EACvB;AAAA;AAAA,EAnCA,OAAO,UAAkB;AACvB,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAM,MAAsC;AACjD,WAAO,IAAI,gBAAe,MAAM,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK;AAAA,EAC9D;AAAA,EAEA,OAAO,WAAW,gBAA0D;AAC1E,UAAM,OAAOA;AAAA,MACX,IAAI,gBAAe,eAAe,GAAG;AAAA,IACvC;AACA,SAAK,UAAU,eAAe,MAAM;AACpC,SAAK,UAAU,eAAe,MAAM;AACpC,SAAK,aAAa,eAAe,SAAS;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,aAAuC;AACrC,WAAO;AAAA,MACL,GAAG,MAAM,WAAW;AAAA,MACpB,KAAK,KAAK,OAAO;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEA,SAAwB;AACtB,UAAM,OAAO,KAAK,UAAU;AAC5B,WAAO,gBAAgB,kBAAiB,KAAK,QAAQ,CAAC;AAAA,EACxD;AAAA,EAOA,sBAA6B;AAC3B,WAAO;AAAA,EACT;AAAA,EAEA,qBAA4B;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,aAAoB;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,WAAiB;AACf,WAAO;AAAA,EACT;AAAA,EAEA,iBACE,GACA,WACA,aACS;AACT,QAAI,CAAC,kBAAkB,SAAS,KAAK,gBAAgB,QAAQ;AAC3D,aAAO;AAAA,IACT;AACA,UAAM,SAAS,UAAU;AACzB,UAAM,QAAQ,UAAU;AACxB,UAAM,aAAa,OAAO,QAAQ;AAClC,UAAM,YAAY,MAAM,QAAQ;AAChC,UAAM,aAAa,UAAU,WAAW;AACxC,UAAM,kBAAkB,aACpB,OAAO,SAAS,MAAM,SACtB,MAAM,SAAS,OAAO;AAC1B,WACE,KAAK,WAAW,UAAU,KAC1B,KAAK,WAAW,SAAS,KACzB,KAAK,eAAe,EAAE,WAAW;AAAA,EAErC;AAAA,EAEA,gBAAgB,aAAwC;AACtD,WAAO,gBAAgB;AAAA,EACzB;AACF;;;AChGO,IAAM,WAAW;AACjB,IAAM,cAAiD;AACvD,IAAM,aAAgD;;;AJoB7D,SAAS,sBAAsB,iBAAiB,YAAAC,iBAAgB;AAJhE,YAAY,UAAU,aAAa,UAAU;AAE7C,IAAM,mBAAmB,CAAC,gBAAgB,WAAW;AAKrD,eAAsB,eACpB,QACA,QACA,OACsB;AACtB,QAAM,MAAM,MAAM,OAAO,6BAA6B,MAAM;AAC5D,QAAM,SAAS,IAAI,WAAW,GAAG;AACjC,SAAO;AAAA,IACL,CAAC,GAAG,kBAAkB,GAAG,KAAK;AAAA,IAC9B,CAAC,QAAQ,YAAY;AACnB,MAAAF,aAAY,QAAQ,KAAK,MAAM;AAC/B,aAAO,OAAO,MAAM;AAAA,MAAC,GAAG,EAAE,UAAU,KAAK,CAAC;AAC1C,aAAO,OAAO,eAAe;AAAA,IAC/B;AAAA,EACF;AACF;AAGA,eAAsB,eACpB,QACA,QACA,OACA,UACe;AACf,QAAM,MAAM,MAAM,OAAO,6BAA6B,MAAM;AAC5D,QAAM,SAAS,IAAI,WAAW,GAAG;AACjC,QAAM;AAAA,IACJ,CAAC,GAAG,kBAAkB,GAAG,KAAK;AAAA,IAC9B,CAAC,QAAQ,YAAY;AACnB,MAAAA,aAAY,QAAQ,KAAK,MAAM;AAC/B,aAAO,OAAO,MAAM;AAAA,MAAC,GAAG,EAAE,UAAU,KAAK,CAAC;AAC1C,YAAM,eAAe,4BAA4B,MAAM;AACvD,aAAO;AAAA,QACL,MAAM;AACJ,gBAAM,OAAO,SAAS;AACtB,mBAAS,IAAI;AAAA,QACf;AAAA,QACA,EAAE,UAAU,KAAK;AAAA,MACnB;AACA,YAAM,cAAc,oBAAoB,QAAQ,KAAK,YAAY;AACjE,aAAO,OAAO,oBAAoB,QAAQ,WAAW;AAAA,IACvD;AAAA,EACF;AACF","sourcesContent":["import { detectDupes } from \"@liveblocks/core\";\nimport type { Liveblocks } from \"@liveblocks/node\";\nimport type {\n EditorState,\n Klass,\n LexicalNode,\n LexicalNodeReplacement,\n RootNode,\n} from \"lexical\";\nimport { $getRoot } from \"lexical\";\nimport {\n applyUpdate,\n encodeStateAsUpdate,\n encodeStateVectorFromUpdate,\n} from \"yjs\";\n\nimport { withHeadlessCollaborationEditor } from \"./headless\";\nimport { MentionNode } from \"./MentionNodeLite\";\nimport { ThreadMarkNode } from \"./ThreadNodeLite\";\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\nconst LIVEBLOCKS_NODES = [ThreadMarkNode, MentionNode];\n\nexport { $createParagraphNode, $createTextNode, $getRoot } from \"lexical\";\n\n// get editor state\nexport async function getEditorState(\n client: Liveblocks,\n roomId: string,\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>\n): Promise<EditorState> {\n const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);\n const update = new Uint8Array(doc);\n return withHeadlessCollaborationEditor(\n [...LIVEBLOCKS_NODES, ...nodes],\n (editor, binding) => {\n applyUpdate(binding.doc, update);\n editor.update(() => {}, { discrete: true });\n return editor.getEditorState();\n }\n );\n}\n\n// modify document\nexport async function modifyDocument(\n client: Liveblocks,\n roomId: string,\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>,\n modifyFn: (root: RootNode) => void\n): Promise<void> {\n const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);\n const update = new Uint8Array(doc);\n await withHeadlessCollaborationEditor(\n [...LIVEBLOCKS_NODES, ...nodes],\n (editor, binding) => {\n applyUpdate(binding.doc, update);\n editor.update(() => {}, { discrete: true });\n const beforeVector = encodeStateVectorFromUpdate(update);\n editor.update(\n () => {\n const root = $getRoot();\n modifyFn(root);\n },\n { discrete: true }\n );\n const afterUpdate = encodeStateAsUpdate(binding.doc, beforeVector);\n return client.sendYjsBinaryUpdate(roomId, afterUpdate);\n }\n );\n}\n","import { createHeadlessEditor } from \"@lexical/headless\";\nimport type { Binding, Provider } from \"@lexical/yjs\";\nimport {\n createBinding,\n syncLexicalUpdateToYjs,\n syncYjsChangesToLexical,\n} from \"@lexical/yjs\";\nimport type {\n Klass,\n LexicalEditor,\n LexicalNode,\n LexicalNodeReplacement,\n SerializedEditorState,\n SerializedLexicalNode,\n} from \"lexical\";\nimport type { Transaction, YEvent } from \"yjs\";\nimport { applyUpdate, Doc } from \"yjs\";\n\nexport function headlessConvertYDocStateToLexicalJSON(\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>,\n yDocState: Uint8Array\n): SerializedEditorState<SerializedLexicalNode> {\n return withHeadlessCollaborationEditor(nodes, (editor, binding) => {\n applyUpdate(binding.doc, yDocState, { isUpdateRemote: true });\n editor.update(() => {}, { discrete: true });\n\n return editor.getEditorState().toJSON();\n });\n}\nexport function withHeadlessCollaborationEditor<T>(\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>,\n callback: (editor: LexicalEditor, binding: Binding, provider: Provider) => T\n): T {\n const editor = createHeadlessEditor({\n nodes,\n });\n\n const id = \"root\";\n const doc = new Doc();\n const docMap = new Map([[id, doc]]);\n const provider = createNoOpProvider();\n const binding = createBinding(editor, provider, id, doc, docMap);\n\n const unsubscribe = registerCollaborationListeners(editor, provider, binding);\n\n const res = callback(editor, binding, provider);\n\n unsubscribe();\n\n return res;\n}\n\nfunction registerCollaborationListeners(\n editor: LexicalEditor,\n provider: Provider,\n binding: Binding\n): () => void {\n const unsubscribeUpdateListener = editor.registerUpdateListener(\n ({\n dirtyElements,\n dirtyLeaves,\n editorState,\n normalizedNodes,\n prevEditorState,\n tags,\n }) => {\n if (tags.has(\"skip-collab\") === false) {\n syncLexicalUpdateToYjs(\n binding,\n provider,\n prevEditorState,\n editorState,\n dirtyElements,\n dirtyLeaves,\n normalizedNodes,\n tags\n );\n }\n }\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const observer = (events: Array<YEvent<any>>, transaction: Transaction) => {\n if (transaction.origin !== binding) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n syncYjsChangesToLexical(binding, provider, events, false);\n }\n };\n\n binding.root.getSharedType().observeDeep(observer);\n\n return () => {\n unsubscribeUpdateListener();\n binding.root.getSharedType().unobserveDeep(observer);\n };\n}\n\nfunction createNoOpProvider(): Provider {\n const emptyFunction = () => {};\n\n return {\n awareness: {\n getLocalState: () => null,\n getStates: () => new Map(),\n off: emptyFunction,\n on: emptyFunction,\n setLocalState: emptyFunction,\n },\n connect: emptyFunction,\n disconnect: emptyFunction,\n off: emptyFunction,\n on: emptyFunction,\n };\n}\n","import type { NodeKey, SerializedLexicalNode, Spread } from \"lexical\";\nimport { $applyNodeReplacement, DecoratorNode } from \"lexical\";\n\nexport type SerializedMentionNode = Spread<\n {\n value: string;\n },\n SerializedLexicalNode\n>;\n\nexport class MentionNode extends DecoratorNode<null> {\n __id: string;\n\n constructor(value: string, key?: NodeKey) {\n super(key);\n this.__id = value;\n }\n\n static getType(): string {\n return \"lb-mention\";\n }\n\n static clone(node: MentionNode): MentionNode {\n return new MentionNode(node.__id);\n }\n\n static importJSON(serializedNode: SerializedMentionNode): MentionNode {\n const node = new MentionNode(serializedNode.value);\n return $applyNodeReplacement(node);\n }\n\n exportJSON(): SerializedMentionNode {\n return {\n value: this.getTextContent(),\n type: \"lb-mention\",\n version: 1,\n };\n }\n\n getTextContent(): string {\n const self = this.getLatest();\n return self.__id;\n }\n\n decorate(): null {\n return null;\n }\n}\n","import type {\n BaseSelection,\n LexicalNode,\n NodeKey,\n SerializedElementNode,\n Spread,\n} from \"lexical\";\nimport { $applyNodeReplacement, $isRangeSelection, ElementNode } from \"lexical\";\n\nexport type SerializedThreadMarkNode = Spread<\n {\n ids: Array<string>;\n },\n SerializedElementNode\n>;\n\nexport class ThreadMarkNode extends ElementNode {\n /** @internal */\n __ids: Array<string>; // The ids of the threads that this mark is associated with\n\n static getType(): string {\n return \"lb-thread-mark\";\n }\n\n static clone(node: ThreadMarkNode): ThreadMarkNode {\n return new ThreadMarkNode(Array.from(node.__ids), node.__key);\n }\n\n static importJSON(serializedNode: SerializedThreadMarkNode): ThreadMarkNode {\n const node = $applyNodeReplacement<ThreadMarkNode>(\n new ThreadMarkNode(serializedNode.ids)\n );\n node.setFormat(serializedNode.format);\n node.setIndent(serializedNode.indent);\n node.setDirection(serializedNode.direction);\n return node;\n }\n\n exportJSON(): SerializedThreadMarkNode {\n return {\n ...super.exportJSON(),\n ids: this.getIDs(),\n type: \"lb-thread-mark\",\n version: 1,\n };\n }\n\n getIDs(): Array<string> {\n const self = this.getLatest();\n return self instanceof ThreadMarkNode ? self.__ids : [];\n }\n\n constructor(ids: Array<string>, key?: NodeKey) {\n super(key);\n this.__ids = ids || [];\n }\n\n canInsertTextBefore(): false {\n return false;\n }\n\n canInsertTextAfter(): false {\n return false;\n }\n\n canBeEmpty(): false {\n return false;\n }\n\n isInline(): true {\n return true;\n }\n\n extractWithChild(\n _: LexicalNode,\n selection: BaseSelection,\n destination: \"clone\" | \"html\"\n ): boolean {\n if (!$isRangeSelection(selection) || destination === \"html\") {\n return false;\n }\n const anchor = selection.anchor;\n const focus = selection.focus;\n const anchorNode = anchor.getNode();\n const focusNode = focus.getNode();\n const isBackward = selection.isBackward();\n const selectionLength = isBackward\n ? anchor.offset - focus.offset\n : focus.offset - anchor.offset;\n return (\n this.isParentOf(anchorNode) &&\n this.isParentOf(focusNode) &&\n this.getTextContent().length === selectionLength\n );\n }\n\n excludeFromCopy(destination: \"clone\" | \"html\"): boolean {\n return destination !== \"clone\";\n }\n}\n","declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/node-lexical\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { detectDupes } from "@liveblocks/core";
|
|
3
|
+
import { $getRoot } from "lexical";
|
|
4
|
+
import {
|
|
5
|
+
applyUpdate as applyUpdate2,
|
|
6
|
+
encodeStateAsUpdate,
|
|
7
|
+
encodeStateVectorFromUpdate
|
|
8
|
+
} from "yjs";
|
|
9
|
+
|
|
10
|
+
// src/headless.ts
|
|
11
|
+
import { createHeadlessEditor } from "@lexical/headless";
|
|
12
|
+
import {
|
|
13
|
+
createBinding,
|
|
14
|
+
syncLexicalUpdateToYjs,
|
|
15
|
+
syncYjsChangesToLexical
|
|
16
|
+
} from "@lexical/yjs";
|
|
17
|
+
import { applyUpdate, Doc } from "yjs";
|
|
18
|
+
function withHeadlessCollaborationEditor(nodes, callback) {
|
|
19
|
+
const editor = createHeadlessEditor({
|
|
20
|
+
nodes
|
|
21
|
+
});
|
|
22
|
+
const id = "root";
|
|
23
|
+
const doc = new Doc();
|
|
24
|
+
const docMap = /* @__PURE__ */ new Map([[id, doc]]);
|
|
25
|
+
const provider = createNoOpProvider();
|
|
26
|
+
const binding = createBinding(editor, provider, id, doc, docMap);
|
|
27
|
+
const unsubscribe = registerCollaborationListeners(editor, provider, binding);
|
|
28
|
+
const res = callback(editor, binding, provider);
|
|
29
|
+
unsubscribe();
|
|
30
|
+
return res;
|
|
31
|
+
}
|
|
32
|
+
function registerCollaborationListeners(editor, provider, binding) {
|
|
33
|
+
const unsubscribeUpdateListener = editor.registerUpdateListener(
|
|
34
|
+
({
|
|
35
|
+
dirtyElements,
|
|
36
|
+
dirtyLeaves,
|
|
37
|
+
editorState,
|
|
38
|
+
normalizedNodes,
|
|
39
|
+
prevEditorState,
|
|
40
|
+
tags
|
|
41
|
+
}) => {
|
|
42
|
+
if (tags.has("skip-collab") === false) {
|
|
43
|
+
syncLexicalUpdateToYjs(
|
|
44
|
+
binding,
|
|
45
|
+
provider,
|
|
46
|
+
prevEditorState,
|
|
47
|
+
editorState,
|
|
48
|
+
dirtyElements,
|
|
49
|
+
dirtyLeaves,
|
|
50
|
+
normalizedNodes,
|
|
51
|
+
tags
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
const observer = (events, transaction) => {
|
|
57
|
+
if (transaction.origin !== binding) {
|
|
58
|
+
syncYjsChangesToLexical(binding, provider, events, false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
binding.root.getSharedType().observeDeep(observer);
|
|
62
|
+
return () => {
|
|
63
|
+
unsubscribeUpdateListener();
|
|
64
|
+
binding.root.getSharedType().unobserveDeep(observer);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createNoOpProvider() {
|
|
68
|
+
const emptyFunction = () => {
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
awareness: {
|
|
72
|
+
getLocalState: () => null,
|
|
73
|
+
getStates: () => /* @__PURE__ */ new Map(),
|
|
74
|
+
off: emptyFunction,
|
|
75
|
+
on: emptyFunction,
|
|
76
|
+
setLocalState: emptyFunction
|
|
77
|
+
},
|
|
78
|
+
connect: emptyFunction,
|
|
79
|
+
disconnect: emptyFunction,
|
|
80
|
+
off: emptyFunction,
|
|
81
|
+
on: emptyFunction
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/MentionNodeLite.ts
|
|
86
|
+
import { $applyNodeReplacement, DecoratorNode } from "lexical";
|
|
87
|
+
var MentionNode = class _MentionNode extends DecoratorNode {
|
|
88
|
+
constructor(value, key) {
|
|
89
|
+
super(key);
|
|
90
|
+
this.__id = value;
|
|
91
|
+
}
|
|
92
|
+
static getType() {
|
|
93
|
+
return "lb-mention";
|
|
94
|
+
}
|
|
95
|
+
static clone(node) {
|
|
96
|
+
return new _MentionNode(node.__id);
|
|
97
|
+
}
|
|
98
|
+
static importJSON(serializedNode) {
|
|
99
|
+
const node = new _MentionNode(serializedNode.value);
|
|
100
|
+
return $applyNodeReplacement(node);
|
|
101
|
+
}
|
|
102
|
+
exportJSON() {
|
|
103
|
+
return {
|
|
104
|
+
value: this.getTextContent(),
|
|
105
|
+
type: "lb-mention",
|
|
106
|
+
version: 1
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
getTextContent() {
|
|
110
|
+
const self = this.getLatest();
|
|
111
|
+
return self.__id;
|
|
112
|
+
}
|
|
113
|
+
decorate() {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/ThreadNodeLite.ts
|
|
119
|
+
import { $applyNodeReplacement as $applyNodeReplacement2, $isRangeSelection, ElementNode } from "lexical";
|
|
120
|
+
var ThreadMarkNode = class _ThreadMarkNode extends ElementNode {
|
|
121
|
+
constructor(ids, key) {
|
|
122
|
+
super(key);
|
|
123
|
+
this.__ids = ids || [];
|
|
124
|
+
}
|
|
125
|
+
// The ids of the threads that this mark is associated with
|
|
126
|
+
static getType() {
|
|
127
|
+
return "lb-thread-mark";
|
|
128
|
+
}
|
|
129
|
+
static clone(node) {
|
|
130
|
+
return new _ThreadMarkNode(Array.from(node.__ids), node.__key);
|
|
131
|
+
}
|
|
132
|
+
static importJSON(serializedNode) {
|
|
133
|
+
const node = $applyNodeReplacement2(
|
|
134
|
+
new _ThreadMarkNode(serializedNode.ids)
|
|
135
|
+
);
|
|
136
|
+
node.setFormat(serializedNode.format);
|
|
137
|
+
node.setIndent(serializedNode.indent);
|
|
138
|
+
node.setDirection(serializedNode.direction);
|
|
139
|
+
return node;
|
|
140
|
+
}
|
|
141
|
+
exportJSON() {
|
|
142
|
+
return {
|
|
143
|
+
...super.exportJSON(),
|
|
144
|
+
ids: this.getIDs(),
|
|
145
|
+
type: "lb-thread-mark",
|
|
146
|
+
version: 1
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
getIDs() {
|
|
150
|
+
const self = this.getLatest();
|
|
151
|
+
return self instanceof _ThreadMarkNode ? self.__ids : [];
|
|
152
|
+
}
|
|
153
|
+
canInsertTextBefore() {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
canInsertTextAfter() {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
canBeEmpty() {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
isInline() {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
extractWithChild(_, selection, destination) {
|
|
166
|
+
if (!$isRangeSelection(selection) || destination === "html") {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const anchor = selection.anchor;
|
|
170
|
+
const focus = selection.focus;
|
|
171
|
+
const anchorNode = anchor.getNode();
|
|
172
|
+
const focusNode = focus.getNode();
|
|
173
|
+
const isBackward = selection.isBackward();
|
|
174
|
+
const selectionLength = isBackward ? anchor.offset - focus.offset : focus.offset - anchor.offset;
|
|
175
|
+
return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && this.getTextContent().length === selectionLength;
|
|
176
|
+
}
|
|
177
|
+
excludeFromCopy(destination) {
|
|
178
|
+
return destination !== "clone";
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/version.ts
|
|
183
|
+
var PKG_NAME = "@liveblocks/node-lexical";
|
|
184
|
+
var PKG_VERSION = "1.12.0-lexical2";
|
|
185
|
+
var PKG_FORMAT = "esm";
|
|
186
|
+
|
|
187
|
+
// src/index.ts
|
|
188
|
+
import { $createParagraphNode, $createTextNode, $getRoot as $getRoot2 } from "lexical";
|
|
189
|
+
detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
|
|
190
|
+
var LIVEBLOCKS_NODES = [ThreadMarkNode, MentionNode];
|
|
191
|
+
async function getEditorState(client, roomId, nodes) {
|
|
192
|
+
const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);
|
|
193
|
+
const update = new Uint8Array(doc);
|
|
194
|
+
return withHeadlessCollaborationEditor(
|
|
195
|
+
[...LIVEBLOCKS_NODES, ...nodes],
|
|
196
|
+
(editor, binding) => {
|
|
197
|
+
applyUpdate2(binding.doc, update);
|
|
198
|
+
editor.update(() => {
|
|
199
|
+
}, { discrete: true });
|
|
200
|
+
return editor.getEditorState();
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
async function modifyDocument(client, roomId, nodes, modifyFn) {
|
|
205
|
+
const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);
|
|
206
|
+
const update = new Uint8Array(doc);
|
|
207
|
+
await withHeadlessCollaborationEditor(
|
|
208
|
+
[...LIVEBLOCKS_NODES, ...nodes],
|
|
209
|
+
(editor, binding) => {
|
|
210
|
+
applyUpdate2(binding.doc, update);
|
|
211
|
+
editor.update(() => {
|
|
212
|
+
}, { discrete: true });
|
|
213
|
+
const beforeVector = encodeStateVectorFromUpdate(update);
|
|
214
|
+
editor.update(
|
|
215
|
+
() => {
|
|
216
|
+
const root = $getRoot();
|
|
217
|
+
modifyFn(root);
|
|
218
|
+
},
|
|
219
|
+
{ discrete: true }
|
|
220
|
+
);
|
|
221
|
+
const afterUpdate = encodeStateAsUpdate(binding.doc, beforeVector);
|
|
222
|
+
return client.sendYjsBinaryUpdate(roomId, afterUpdate);
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
$createParagraphNode,
|
|
228
|
+
$createTextNode,
|
|
229
|
+
$getRoot2 as $getRoot,
|
|
230
|
+
getEditorState,
|
|
231
|
+
modifyDocument
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/headless.ts","../src/MentionNodeLite.ts","../src/ThreadNodeLite.ts","../src/version.ts"],"sourcesContent":["import { detectDupes } from \"@liveblocks/core\";\nimport type { Liveblocks } from \"@liveblocks/node\";\nimport type {\n EditorState,\n Klass,\n LexicalNode,\n LexicalNodeReplacement,\n RootNode,\n} from \"lexical\";\nimport { $getRoot } from \"lexical\";\nimport {\n applyUpdate,\n encodeStateAsUpdate,\n encodeStateVectorFromUpdate,\n} from \"yjs\";\n\nimport { withHeadlessCollaborationEditor } from \"./headless\";\nimport { MentionNode } from \"./MentionNodeLite\";\nimport { ThreadMarkNode } from \"./ThreadNodeLite\";\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\nconst LIVEBLOCKS_NODES = [ThreadMarkNode, MentionNode];\n\nexport { $createParagraphNode, $createTextNode, $getRoot } from \"lexical\";\n\n// get editor state\nexport async function getEditorState(\n client: Liveblocks,\n roomId: string,\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>\n): Promise<EditorState> {\n const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);\n const update = new Uint8Array(doc);\n return withHeadlessCollaborationEditor(\n [...LIVEBLOCKS_NODES, ...nodes],\n (editor, binding) => {\n applyUpdate(binding.doc, update);\n editor.update(() => {}, { discrete: true });\n return editor.getEditorState();\n }\n );\n}\n\n// modify document\nexport async function modifyDocument(\n client: Liveblocks,\n roomId: string,\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>,\n modifyFn: (root: RootNode) => void\n): Promise<void> {\n const doc = await client.getYjsDocumentAsBinaryUpdate(roomId);\n const update = new Uint8Array(doc);\n await withHeadlessCollaborationEditor(\n [...LIVEBLOCKS_NODES, ...nodes],\n (editor, binding) => {\n applyUpdate(binding.doc, update);\n editor.update(() => {}, { discrete: true });\n const beforeVector = encodeStateVectorFromUpdate(update);\n editor.update(\n () => {\n const root = $getRoot();\n modifyFn(root);\n },\n { discrete: true }\n );\n const afterUpdate = encodeStateAsUpdate(binding.doc, beforeVector);\n return client.sendYjsBinaryUpdate(roomId, afterUpdate);\n }\n );\n}\n","import { createHeadlessEditor } from \"@lexical/headless\";\nimport type { Binding, Provider } from \"@lexical/yjs\";\nimport {\n createBinding,\n syncLexicalUpdateToYjs,\n syncYjsChangesToLexical,\n} from \"@lexical/yjs\";\nimport type {\n Klass,\n LexicalEditor,\n LexicalNode,\n LexicalNodeReplacement,\n SerializedEditorState,\n SerializedLexicalNode,\n} from \"lexical\";\nimport type { Transaction, YEvent } from \"yjs\";\nimport { applyUpdate, Doc } from \"yjs\";\n\nexport function headlessConvertYDocStateToLexicalJSON(\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>,\n yDocState: Uint8Array\n): SerializedEditorState<SerializedLexicalNode> {\n return withHeadlessCollaborationEditor(nodes, (editor, binding) => {\n applyUpdate(binding.doc, yDocState, { isUpdateRemote: true });\n editor.update(() => {}, { discrete: true });\n\n return editor.getEditorState().toJSON();\n });\n}\nexport function withHeadlessCollaborationEditor<T>(\n nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>,\n callback: (editor: LexicalEditor, binding: Binding, provider: Provider) => T\n): T {\n const editor = createHeadlessEditor({\n nodes,\n });\n\n const id = \"root\";\n const doc = new Doc();\n const docMap = new Map([[id, doc]]);\n const provider = createNoOpProvider();\n const binding = createBinding(editor, provider, id, doc, docMap);\n\n const unsubscribe = registerCollaborationListeners(editor, provider, binding);\n\n const res = callback(editor, binding, provider);\n\n unsubscribe();\n\n return res;\n}\n\nfunction registerCollaborationListeners(\n editor: LexicalEditor,\n provider: Provider,\n binding: Binding\n): () => void {\n const unsubscribeUpdateListener = editor.registerUpdateListener(\n ({\n dirtyElements,\n dirtyLeaves,\n editorState,\n normalizedNodes,\n prevEditorState,\n tags,\n }) => {\n if (tags.has(\"skip-collab\") === false) {\n syncLexicalUpdateToYjs(\n binding,\n provider,\n prevEditorState,\n editorState,\n dirtyElements,\n dirtyLeaves,\n normalizedNodes,\n tags\n );\n }\n }\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const observer = (events: Array<YEvent<any>>, transaction: Transaction) => {\n if (transaction.origin !== binding) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n syncYjsChangesToLexical(binding, provider, events, false);\n }\n };\n\n binding.root.getSharedType().observeDeep(observer);\n\n return () => {\n unsubscribeUpdateListener();\n binding.root.getSharedType().unobserveDeep(observer);\n };\n}\n\nfunction createNoOpProvider(): Provider {\n const emptyFunction = () => {};\n\n return {\n awareness: {\n getLocalState: () => null,\n getStates: () => new Map(),\n off: emptyFunction,\n on: emptyFunction,\n setLocalState: emptyFunction,\n },\n connect: emptyFunction,\n disconnect: emptyFunction,\n off: emptyFunction,\n on: emptyFunction,\n };\n}\n","import type { NodeKey, SerializedLexicalNode, Spread } from \"lexical\";\nimport { $applyNodeReplacement, DecoratorNode } from \"lexical\";\n\nexport type SerializedMentionNode = Spread<\n {\n value: string;\n },\n SerializedLexicalNode\n>;\n\nexport class MentionNode extends DecoratorNode<null> {\n __id: string;\n\n constructor(value: string, key?: NodeKey) {\n super(key);\n this.__id = value;\n }\n\n static getType(): string {\n return \"lb-mention\";\n }\n\n static clone(node: MentionNode): MentionNode {\n return new MentionNode(node.__id);\n }\n\n static importJSON(serializedNode: SerializedMentionNode): MentionNode {\n const node = new MentionNode(serializedNode.value);\n return $applyNodeReplacement(node);\n }\n\n exportJSON(): SerializedMentionNode {\n return {\n value: this.getTextContent(),\n type: \"lb-mention\",\n version: 1,\n };\n }\n\n getTextContent(): string {\n const self = this.getLatest();\n return self.__id;\n }\n\n decorate(): null {\n return null;\n }\n}\n","import type {\n BaseSelection,\n LexicalNode,\n NodeKey,\n SerializedElementNode,\n Spread,\n} from \"lexical\";\nimport { $applyNodeReplacement, $isRangeSelection, ElementNode } from \"lexical\";\n\nexport type SerializedThreadMarkNode = Spread<\n {\n ids: Array<string>;\n },\n SerializedElementNode\n>;\n\nexport class ThreadMarkNode extends ElementNode {\n /** @internal */\n __ids: Array<string>; // The ids of the threads that this mark is associated with\n\n static getType(): string {\n return \"lb-thread-mark\";\n }\n\n static clone(node: ThreadMarkNode): ThreadMarkNode {\n return new ThreadMarkNode(Array.from(node.__ids), node.__key);\n }\n\n static importJSON(serializedNode: SerializedThreadMarkNode): ThreadMarkNode {\n const node = $applyNodeReplacement<ThreadMarkNode>(\n new ThreadMarkNode(serializedNode.ids)\n );\n node.setFormat(serializedNode.format);\n node.setIndent(serializedNode.indent);\n node.setDirection(serializedNode.direction);\n return node;\n }\n\n exportJSON(): SerializedThreadMarkNode {\n return {\n ...super.exportJSON(),\n ids: this.getIDs(),\n type: \"lb-thread-mark\",\n version: 1,\n };\n }\n\n getIDs(): Array<string> {\n const self = this.getLatest();\n return self instanceof ThreadMarkNode ? self.__ids : [];\n }\n\n constructor(ids: Array<string>, key?: NodeKey) {\n super(key);\n this.__ids = ids || [];\n }\n\n canInsertTextBefore(): false {\n return false;\n }\n\n canInsertTextAfter(): false {\n return false;\n }\n\n canBeEmpty(): false {\n return false;\n }\n\n isInline(): true {\n return true;\n }\n\n extractWithChild(\n _: LexicalNode,\n selection: BaseSelection,\n destination: \"clone\" | \"html\"\n ): boolean {\n if (!$isRangeSelection(selection) || destination === \"html\") {\n return false;\n }\n const anchor = selection.anchor;\n const focus = selection.focus;\n const anchorNode = anchor.getNode();\n const focusNode = focus.getNode();\n const isBackward = selection.isBackward();\n const selectionLength = isBackward\n ? anchor.offset - focus.offset\n : focus.offset - anchor.offset;\n return (\n this.isParentOf(anchorNode) &&\n this.isParentOf(focusNode) &&\n this.getTextContent().length === selectionLength\n );\n }\n\n excludeFromCopy(destination: \"clone\" | \"html\"): boolean {\n return destination !== \"clone\";\n }\n}\n","declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/node-lexical\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"],"mappings":"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","names":["applyUpdate","$applyNodeReplacement","$getRoot","applyUpdate"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liveblocks/node-lexical",
|
|
3
|
+
"version": "1.12.0-lexical3",
|
|
4
|
+
"description": "A server-side utility that lets you modify lexical documents hosted in Liveblocks.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"module": "./dist/index.mjs",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/**",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"format": "(eslint --fix src/ || true) && prettier --write src/",
|
|
29
|
+
"lint": "eslint src/",
|
|
30
|
+
"lint:package": "publint --strict && attw --pack",
|
|
31
|
+
"test": "jest --silent --verbose --color=always",
|
|
32
|
+
"test:types": "tsd",
|
|
33
|
+
"test:watch": "jest --silent --verbose --color=always --watch"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@liveblocks/core": "1.12.0-lexical2",
|
|
37
|
+
"@liveblocks/node": "1.12.0-lexical2",
|
|
38
|
+
"yjs": "^13.6.15"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@lexical/headless": "0.14.5",
|
|
42
|
+
"@lexical/yjs": "0.14.5",
|
|
43
|
+
"lexical": "0.14.5"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@liveblocks/eslint-config": "*",
|
|
47
|
+
"@liveblocks/jest-config": "*",
|
|
48
|
+
"@types/node": "^20.7.1"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/liveblocks/liveblocks/issues"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/liveblocks/liveblocks.git",
|
|
56
|
+
"directory": "packages/liveblocks-node"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://liveblocks.io",
|
|
59
|
+
"keywords": [
|
|
60
|
+
"node",
|
|
61
|
+
"liveblocks",
|
|
62
|
+
"real-time",
|
|
63
|
+
"toolkit",
|
|
64
|
+
"multiplayer",
|
|
65
|
+
"websockets",
|
|
66
|
+
"collaboration",
|
|
67
|
+
"collaborative",
|
|
68
|
+
"presence",
|
|
69
|
+
"crdts",
|
|
70
|
+
"synchronize",
|
|
71
|
+
"rooms",
|
|
72
|
+
"documents",
|
|
73
|
+
"conflict resolution"
|
|
74
|
+
]
|
|
75
|
+
}
|