automerge-lexical 0.1.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/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+
45
+ ```
46
+
47
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
48
+
49
+ ```js
50
+ // eslint.config.js
51
+ import reactX from 'eslint-plugin-react-x'
52
+ import reactDom from 'eslint-plugin-react-dom'
53
+
54
+ export default defineConfig([
55
+ globalIgnores(['dist']),
56
+ {
57
+ files: ['**/*.{ts,tsx}'],
58
+ extends: [
59
+ // Other configs...
60
+ // Enable lint rules for React
61
+ reactX.configs['recommended-typescript'],
62
+ // Enable lint rules for React DOM
63
+ reactDom.configs.recommended,
64
+ ],
65
+ languageOptions: {
66
+ parserOptions: {
67
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
68
+ tsconfigRootDir: import.meta.dirname,
69
+ },
70
+ // other options...
71
+ },
72
+ },
73
+ ])
74
+
75
+ ```
@@ -0,0 +1,10 @@
1
+ import { UpdateSpansConfig } from '@automerge/automerge';
2
+ import { DocHandle } from '@automerge/automerge-repo';
3
+ import { LexicalEditor } from 'lexical';
4
+ import { default as React } from 'react';
5
+ interface AutomergeChangePluginProps {
6
+ registerSync(editor: LexicalEditor, handle: DocHandle<any>, updateSpansConfig?: UpdateSpansConfig): () => void;
7
+ handle: DocHandle<any>;
8
+ }
9
+ export declare const AutomergeChangePlugin: React.FC<AutomergeChangePluginProps>;
10
+ export {};
@@ -0,0 +1,28 @@
1
+ import { InitialConfigType } from '@lexical/react/LexicalComposer';
2
+ import { LexicalEditor, LexicalNode } from 'lexical';
3
+ import { AutomergeUrl } from '@automerge/react';
4
+ import { LexicalSchemaAdapter } from './types';
5
+ /**
6
+ * Creates an Automerge configuration for a Lexical editor.
7
+ *
8
+ * @param editorConfig - Lexical editor configuration.
9
+ * @param path - Path to the rich text object in the document.
10
+ * @param docUrl - URL of the Automerge document.
11
+ * @param schema - Schema used to map Lexical nodes to Automerge.
12
+ * @returns A configuration object for the editor.
13
+ */
14
+ export declare function automergeConfig<T>(editorConfig: Omit<InitialConfigType, 'editorState'>, path: string[], docUrl: AutomergeUrl, schema?: LexicalSchemaAdapter): {
15
+ initialConfig: {
16
+ nodes: (import('lexical').KlassConstructor<typeof LexicalNode> | import('lexical').LexicalNodeReplacement)[];
17
+ editorState: (editor: LexicalEditor) => void;
18
+ html?: import('lexical').HTMLConfig;
19
+ theme?: import('lexical').EditorThemeClasses;
20
+ namespace: string;
21
+ onError: (error: Error, editor: LexicalEditor) => void;
22
+ onWarn?: (error: Error, editor: LexicalEditor) => void;
23
+ editable?: boolean;
24
+ };
25
+ doc: import('@automerge/automerge').Doc<T>;
26
+ changeDoc: (changeFn: import('@automerge/automerge').ChangeFn<T>, options?: import('@automerge/automerge').ChangeOptions<T> | undefined) => void;
27
+ registerSync: (editor: LexicalEditor, handle: import('@automerge/automerge-repo').DocHandle<T>, updateSpansConfig?: import('@automerge/automerge').UpdateSpansConfig) => () => void;
28
+ };
@@ -0,0 +1,51 @@
1
+ import { ElementNode, LexicalEditor, LexicalNode } from 'lexical';
2
+ import { DocHandle } from '@automerge/automerge-repo';
3
+ import { LexicalSchemaAdapter } from './types';
4
+ import * as am from '@automerge/automerge';
5
+ export declare class AutomergeLexical<T> {
6
+ private readonly schema;
7
+ path: string[];
8
+ private isApplyingRemote;
9
+ private isApplyingLocal;
10
+ private readonly automergeListTypeToLexicalType;
11
+ constructor(schema: LexicalSchemaAdapter, path: string[]);
12
+ getBlockMappingByLexicalType(lexicalType: string): import('./types').LexicalBlockMapping | undefined;
13
+ getBlockFactoryCreate(automergeType: string): ((attrs: Record<string, am.MaterializeValue>) => ElementNode) | undefined;
14
+ buildEditorStateFromAutomerge(_editor: LexicalEditor, doc: am.Doc<T> | undefined): void;
15
+ /**
16
+ * Initializes the lexical editor using the automerge doc
17
+ *
18
+ * @param editor - The Lexical editor instance.
19
+ * @param doc - The Automerge document handle.
20
+ * @param pathToTextField - Path to the rich text object in the document.
21
+ * @param tag - Used to identify the origin of the update (eg: 'automerge-init', 'automerge-remote')
22
+ */
23
+ initLexicalFromAutomerge(editor: LexicalEditor, doc: am.Doc<T>, pathToTextField: am.Prop[], tag?: string): void;
24
+ blockTypeVal(value: unknown): string;
25
+ appendText(block: ElementNode, text: string, marks: am.MarkSet): void;
26
+ /**
27
+ * Converts automerge spans to lexical nodes
28
+ *
29
+ * @param spans - Automerge spans to be converted.
30
+ * @returns Lexical nodes.
31
+ */
32
+ automergeSpansToLexicalNodes(spans: am.Span[]): LexicalNode[];
33
+ /**
34
+ * Converts lexical nodes to automerge spans
35
+ *
36
+ * @param nodes - Lexical nodes to be converted.
37
+ * @returns Automerge spans.
38
+ */
39
+ lexicalNodesToAmSpans(nodes: LexicalNode[]): am.Span[];
40
+ private appendInlineSpans;
41
+ private inlineNodeToTextAndMarks;
42
+ /**
43
+ * Registers two way synchronization between lexical and automerge
44
+ *
45
+ * @param editor - The Lexical editor instance.
46
+ * @param handle - The Automerge document handle.
47
+ * @param updateSpansConfig - Optional configuration passed to `am.updateSpans()`.
48
+ * @returns A cleanup function that unregisters all listeners.
49
+ */
50
+ registerSync(editor: LexicalEditor, handle: DocHandle<T>, updateSpansConfig?: am.UpdateSpansConfig): () => void;
51
+ }
@@ -0,0 +1,368 @@
1
+ import { useDocument as e } from "@automerge/react";
2
+ import { $createParagraphNode as t, $createTextNode as n, $getRoot as r, ElementNode as i, ParagraphNode as a, TextNode as o } from "lexical";
3
+ import * as s from "@automerge/automerge";
4
+ import { $createListItemNode as c, $createListNode as l, ListItemNode as u, ListNode as d } from "@lexical/list";
5
+ import { $createLinkNode as f, LinkNode as p } from "@lexical/link";
6
+ import { useLayoutEffect as m, useMemo as h } from "react";
7
+ import { $createHeadingNode as g, $createQuoteNode as _, HeadingNode as v, QuoteNode as y } from "@lexical/rich-text";
8
+ import { $createCodeNode as b, CodeNode as x } from "@lexical/code";
9
+ import { useLexicalComposerContext as S } from "@lexical/react/LexicalComposerContext";
10
+ //#region src/nodes/UnknownBlockNode.ts
11
+ var C = class e extends i {
12
+ __automergeType;
13
+ __automergeAttrs;
14
+ static getType() {
15
+ return "automerge-unknown-block";
16
+ }
17
+ static clone(t) {
18
+ return new e(t.__automergeType, t.__automergeAttrs, t.__key);
19
+ }
20
+ constructor(e, t = {}, n) {
21
+ super(n), this.__automergeType = e, this.__automergeAttrs = t;
22
+ }
23
+ getAutomergeType() {
24
+ return this.getLatest().__automergeType;
25
+ }
26
+ getAutomergeAttrs() {
27
+ return this.getLatest().__automergeAttrs;
28
+ }
29
+ createDOM() {
30
+ let e = document.createElement("div");
31
+ e.setAttribute("data-automerge-unknown-block", this.__automergeType), e.style.border = "1px dashed var(--am-unknown-block-border, #999)", e.style.borderRadius = "4px", e.style.padding = "8px", e.style.margin = "4px 0", e.style.position = "relative";
32
+ let t = document.createElement("div");
33
+ return t.textContent = `Unsupported block: ${this.__automergeType}`, t.style.fontSize = "11px", t.style.opacity = "0.6", t.style.userSelect = "none", t.contentEditable = "false", e.appendChild(t), e;
34
+ }
35
+ updateDOM() {
36
+ return !1;
37
+ }
38
+ exportJSON() {
39
+ return {
40
+ ...super.exportJSON(),
41
+ type: "automerge-unknown-block",
42
+ version: 1,
43
+ automergeType: this.__automergeType,
44
+ automergeAttrs: this.__automergeAttrs
45
+ };
46
+ }
47
+ static importJSON(e) {
48
+ return w(e.automergeType, e.automergeAttrs).updateFromJSON(e);
49
+ }
50
+ exportDOM() {
51
+ return { element: this.createDOM() };
52
+ }
53
+ static importDOM() {
54
+ return null;
55
+ }
56
+ isInline() {
57
+ return !1;
58
+ }
59
+ };
60
+ function w(e, t = {}) {
61
+ return new C(e, t);
62
+ }
63
+ function T(e) {
64
+ return e instanceof C;
65
+ }
66
+ //#endregion
67
+ //#region src/AutomergeLexical.tsx
68
+ var E = class {
69
+ schema;
70
+ path;
71
+ isApplyingRemote = !1;
72
+ isApplyingLocal = !1;
73
+ automergeListTypeToLexicalType = /* @__PURE__ */ new Map();
74
+ constructor(e, t) {
75
+ this.schema = e, this.path = t;
76
+ for (let [t, n] of Object.entries(e.listItemTypes)) this.automergeListTypeToLexicalType.set(n, t);
77
+ }
78
+ getBlockMappingByLexicalType(e) {
79
+ return this.schema.blocks.find((t) => t.lexicalType === e);
80
+ }
81
+ getBlockFactoryCreate(e) {
82
+ let t = this.schema.blocks.find((t) => t.automergeType === e);
83
+ if (t) return t.create;
84
+ }
85
+ buildEditorStateFromAutomerge(e, t) {
86
+ r().clear(), t && this.initLexicalFromAutomerge(e, t, this.path);
87
+ }
88
+ initLexicalFromAutomerge(e, t, n, i = "automerge-init") {
89
+ let a = s.spans(t, n);
90
+ e.update(() => {
91
+ let e = r();
92
+ e.clear();
93
+ let t = this.automergeSpansToLexicalNodes(a);
94
+ for (let n of t) e.append(n);
95
+ }, { tag: i });
96
+ }
97
+ blockTypeVal(e) {
98
+ return s.isImmutableString(e) ? e.val : typeof e == "string" ? e : "paragraph";
99
+ }
100
+ appendText(e, t, r) {
101
+ if (t.length === 0) return;
102
+ let i = n(t);
103
+ for (let { automergeMarkName: e, apply: t } of this.schema.marks) if (e in r) {
104
+ let n = r[e];
105
+ i = t(i, n);
106
+ }
107
+ e.append(i);
108
+ }
109
+ automergeSpansToLexicalNodes(e) {
110
+ if (e.length === 0) return [t()];
111
+ let n = [], r = null, i = null, a = null;
112
+ for (let o of e) if (o.type === "block") {
113
+ let e = this.blockTypeVal(o.value.type), t = o.value.attrs || {};
114
+ if (Object.keys(this.schema.listItemTypes).includes(e)) {
115
+ (i == null || a !== e) && (i = l(this.schema.listItemTypes[e]), a = e, n.push(i));
116
+ let t = c();
117
+ i.append(t), r = t;
118
+ continue;
119
+ }
120
+ i = null, a = null;
121
+ let s = this.getBlockFactoryCreate(e);
122
+ r = s ? s(t) : w(e, t), n.push(r);
123
+ } else r ?? (r = t(), n.push(r)), this.appendText(r, o.value, o.marks || {});
124
+ return n;
125
+ }
126
+ lexicalNodesToAmSpans(e) {
127
+ let t = [];
128
+ for (let n of e) {
129
+ if (n instanceof d) {
130
+ let e = this.automergeListTypeToLexicalType.get(n.getListType());
131
+ if (e == null) {
132
+ console.warn(`unknown lexical list type "${n.getListType()}", skipping list`);
133
+ continue;
134
+ }
135
+ for (let r of n.getChildren()) r instanceof u && (t.push({
136
+ type: "block",
137
+ value: {
138
+ type: e,
139
+ attrs: {}
140
+ }
141
+ }), this.appendInlineSpans(t, r));
142
+ continue;
143
+ }
144
+ if (T(n)) {
145
+ t.push({
146
+ type: "block",
147
+ value: {
148
+ type: n.getAutomergeType(),
149
+ attrs: n.getAutomergeAttrs()
150
+ }
151
+ }), this.appendInlineSpans(t, n);
152
+ continue;
153
+ }
154
+ if (n instanceof i) {
155
+ let e = this.getBlockMappingByLexicalType(n.getType());
156
+ if (e == null) {
157
+ console.warn(`unknown lexical node type "${n.getType()}", dropping (no reverse mapping)`);
158
+ continue;
159
+ }
160
+ let r = e.toAutomergeAttrs ? e.toAutomergeAttrs(n) : {};
161
+ t.push({
162
+ type: "block",
163
+ value: {
164
+ type: e.automergeType,
165
+ attrs: r
166
+ }
167
+ }), this.appendInlineSpans(t, n);
168
+ continue;
169
+ }
170
+ }
171
+ return t;
172
+ }
173
+ appendInlineSpans(e, t) {
174
+ for (let n of t.getChildren()) {
175
+ let { text: t, marks: r } = this.inlineNodeToTextAndMarks(n);
176
+ if (t.length === 0) continue;
177
+ let i = {
178
+ type: "text",
179
+ value: t
180
+ };
181
+ Object.keys(r).length > 0 && (i.marks = r), e.push(i);
182
+ }
183
+ }
184
+ inlineNodeToTextAndMarks(e) {
185
+ let t = {}, n = e;
186
+ if (n instanceof p) {
187
+ let e = this.schema.marks.find((e) => e.automergeMarkName === "link");
188
+ if (e) {
189
+ let r = e?.toAutomergeValue(n);
190
+ if (r) return t.link = r, {
191
+ text: n.getTextContent(),
192
+ marks: t
193
+ };
194
+ }
195
+ }
196
+ if (!(n instanceof o)) return {
197
+ text: "",
198
+ marks: t
199
+ };
200
+ for (let e of this.schema.marks) {
201
+ if (e.automergeMarkName === "link") continue;
202
+ let r = e.toAutomergeValue(n);
203
+ r && (t[e.automergeMarkName] = r);
204
+ }
205
+ return {
206
+ text: n.getTextContent(),
207
+ marks: t
208
+ };
209
+ }
210
+ registerSync(e, t, n) {
211
+ let i = ({ dirtyElements: e, dirtyLeaves: i, tags: a, editorState: o }) => {
212
+ this.isApplyingRemote || a.has("automerge-init") || a.has("automerge-remote") || e.size === 0 && i.size === 0 || o.read(() => {
213
+ let e = this.lexicalNodesToAmSpans(r().getChildren());
214
+ this.isApplyingLocal = !0;
215
+ try {
216
+ t.change((t) => {
217
+ s.updateSpans(t, this.path, e, n);
218
+ });
219
+ } finally {
220
+ this.isApplyingLocal = !1;
221
+ }
222
+ });
223
+ }, a = ({ doc: t }) => {
224
+ if (!this.isApplyingLocal) {
225
+ this.isApplyingRemote = !0;
226
+ try {
227
+ this.initLexicalFromAutomerge(e, t, this.path, "automerge-remote");
228
+ } finally {
229
+ this.isApplyingRemote = !1;
230
+ }
231
+ }
232
+ }, o = e.registerUpdateListener(i);
233
+ return t.on("change", a), () => {
234
+ o(), t.off("change", a);
235
+ };
236
+ }
237
+ };
238
+ //#endregion
239
+ //#region src/schema/richTextSchema.ts
240
+ function D(e) {
241
+ if (typeof e == "string") try {
242
+ let t = JSON.parse(e);
243
+ return {
244
+ href: t.href || "",
245
+ title: t.title || ""
246
+ };
247
+ } catch {}
248
+ return {
249
+ href: "",
250
+ title: ""
251
+ };
252
+ }
253
+ var O = {
254
+ requiredNodes: [
255
+ v,
256
+ a,
257
+ y,
258
+ d,
259
+ u,
260
+ p,
261
+ x
262
+ ],
263
+ blocks: [
264
+ {
265
+ automergeType: "paragraph",
266
+ lexicalType: "paragraph",
267
+ create: () => t()
268
+ },
269
+ {
270
+ automergeType: "heading",
271
+ lexicalType: "heading",
272
+ create: (e) => {
273
+ let t = Number(e.level) || 1;
274
+ return g(`h${Math.min(Math.max(t, 1), 6)}`);
275
+ },
276
+ toAutomergeAttrs: (e) => ({ level: Number(e.getTag().slice(1)) })
277
+ },
278
+ {
279
+ automergeType: "blockquote",
280
+ lexicalType: "quote",
281
+ create: () => _()
282
+ },
283
+ {
284
+ automergeType: "code-block",
285
+ lexicalType: "code",
286
+ create: (e) => {
287
+ let t = e.language;
288
+ return typeof t == "string" ? b(t) : b();
289
+ },
290
+ toAutomergeAttrs: (e) => {
291
+ let t = e.getLanguage();
292
+ return { ...typeof t == "string" && { language: t } };
293
+ }
294
+ }
295
+ ],
296
+ marks: [
297
+ {
298
+ automergeMarkName: "bold",
299
+ lexicalMarkName: "bold",
300
+ apply: (e, t) => (t && e instanceof o && e.toggleFormat("bold"), e),
301
+ toAutomergeValue: (e) => e instanceof p ? !1 : !!e.hasFormat("bold")
302
+ },
303
+ {
304
+ automergeMarkName: "italic",
305
+ lexicalMarkName: "italic",
306
+ apply: (e, t) => (t && e instanceof o && e.toggleFormat("italic"), e),
307
+ toAutomergeValue: (e) => e instanceof p ? !1 : !!e.hasFormat("italic")
308
+ },
309
+ {
310
+ automergeMarkName: "underline",
311
+ lexicalMarkName: "underline",
312
+ apply: (e, t) => (t && e instanceof o && e.toggleFormat("underline"), e),
313
+ toAutomergeValue: (e) => e instanceof p ? !1 : !!e.hasFormat("underline")
314
+ },
315
+ {
316
+ automergeMarkName: "link",
317
+ lexicalMarkName: "link",
318
+ apply: (e, t) => {
319
+ let { href: n, title: r } = D(t), i = f(n, r ? { title: r } : void 0);
320
+ return i.append(e), i;
321
+ },
322
+ toAutomergeValue: (e) => e instanceof p ? JSON.stringify({
323
+ href: e.getURL(),
324
+ title: e.getTitle?.() ?? ""
325
+ }) : ""
326
+ }
327
+ ],
328
+ listItemTypes: {
329
+ "ordered-list-item": "number",
330
+ "unordered-list-item": "bullet"
331
+ }
332
+ };
333
+ //#endregion
334
+ //#region src/AutomergeConfig.tsx
335
+ function k(t, n, r, i = O) {
336
+ let [a, o] = e(r, { suspense: !0 }), { amLexical: s, registerSync: c } = h(() => {
337
+ let e = new E(i, n);
338
+ return {
339
+ amLexical: e,
340
+ registerSync: e.registerSync.bind(e)
341
+ };
342
+ }, [
343
+ i,
344
+ n,
345
+ r
346
+ ]), l = [...i.requiredNodes, C], u = [...l, ...(t.nodes ?? []).filter((e) => !l.includes(e))];
347
+ return {
348
+ initialConfig: {
349
+ ...t,
350
+ nodes: u,
351
+ editorState: (e) => s.buildEditorStateFromAutomerge(e, a)
352
+ },
353
+ doc: a,
354
+ changeDoc: o,
355
+ registerSync: c
356
+ };
357
+ }
358
+ //#endregion
359
+ //#region src/AutomergeChangePlugin.tsx
360
+ var A = ({ registerSync: e, handle: t }) => {
361
+ let [n] = S();
362
+ return m(() => {
363
+ let r = e(n, t);
364
+ return () => r();
365
+ }, [n]), null;
366
+ };
367
+ //#endregion
368
+ export { A as AutomergeChangePlugin, E as AutomergeLexical, k as automergeConfig };
@@ -0,0 +1,4 @@
1
+ export { automergeConfig } from './AutomergeConfig';
2
+ export { AutomergeLexical } from './AutomergeLexical';
3
+ export type { LexicalSchemaAdapter } from './types';
4
+ export { AutomergeChangePlugin } from './AutomergeChangePlugin';
@@ -0,0 +1,24 @@
1
+ import { ElementNode, DOMConversionMap, DOMExportOutput, LexicalNode, NodeKey, SerializedElementNode, Spread } from 'lexical';
2
+ import type * as am from '@automerge/automerge';
3
+ export type SerializedUnknownBlockNode = Spread<{
4
+ automergeType: string;
5
+ automergeAttrs: Record<string, am.MaterializeValue>;
6
+ }, SerializedElementNode>;
7
+ export declare class UnknownBlockNode extends ElementNode {
8
+ __automergeType: string;
9
+ __automergeAttrs: Record<string, am.MaterializeValue>;
10
+ static getType(): string;
11
+ static clone(node: UnknownBlockNode): UnknownBlockNode;
12
+ constructor(automergeType: string, automergeAttrs?: Record<string, am.MaterializeValue>, key?: NodeKey);
13
+ getAutomergeType(): string;
14
+ getAutomergeAttrs(): Record<string, am.MaterializeValue>;
15
+ createDOM(): HTMLElement;
16
+ updateDOM(): boolean;
17
+ exportJSON(): SerializedUnknownBlockNode;
18
+ static importJSON(serializedNode: SerializedUnknownBlockNode): UnknownBlockNode;
19
+ exportDOM(): DOMExportOutput;
20
+ static importDOM(): DOMConversionMap | null;
21
+ isInline(): boolean;
22
+ }
23
+ export declare function $createUnknownBlockNode(automergeType: string, automergeAttrs?: Record<string, am.MaterializeValue>): UnknownBlockNode;
24
+ export declare function $isUnknownBlockNode(node: LexicalNode | null | undefined): node is UnknownBlockNode;
@@ -0,0 +1,2 @@
1
+ import { LexicalSchemaAdapter } from '../types';
2
+ export declare const richTextSchema: LexicalSchemaAdapter;
@@ -0,0 +1,22 @@
1
+ import { ElementNode, Klass, LexicalNode, TextNode } from 'lexical';
2
+ import { ListType } from '@lexical/list';
3
+ import { LinkNode } from '@lexical/link';
4
+ import * as am from '@automerge/automerge';
5
+ export interface LexicalBlockMapping {
6
+ automergeType: string;
7
+ lexicalType: string;
8
+ create(attrs: Record<string, am.MaterializeValue>): ElementNode;
9
+ toAutomergeAttrs?(node: ElementNode): Record<string, am.MaterializeValue>;
10
+ }
11
+ export interface LexicalMarkMapping {
12
+ automergeMarkName: string;
13
+ lexicalMarkName: string;
14
+ apply(textNode: LexicalNode, value: am.ScalarValue | string | undefined): LexicalNode;
15
+ toAutomergeValue(node: TextNode | LinkNode): am.MarkValue;
16
+ }
17
+ export interface LexicalSchemaAdapter {
18
+ blocks: LexicalBlockMapping[];
19
+ listItemTypes: Record<string, ListType>;
20
+ marks: LexicalMarkMapping[];
21
+ requiredNodes: Klass<LexicalNode>[];
22
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "automerge-lexical",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "./dist/automerge-lexical-plugin.es.js",
7
+ "module": "./dist/automerge-lexical-plugin.es.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/automerge-lexical-plugin.es.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "dev": "vite",
20
+ "build": "tsc -b && vite build",
21
+ "lint": "eslint .",
22
+ "preview": "vite preview",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "devDependencies": {
27
+ "@automerge/automerge": "^3.3.2",
28
+ "@automerge/automerge-repo": "^2.5.6",
29
+ "@automerge/react": "^2.5.6",
30
+ "@eslint/js": "^10.0.1",
31
+ "@lexical/code": "^0.48.0",
32
+ "@lexical/headless": "^0.48.0",
33
+ "@lexical/link": "^0.48.0",
34
+ "@lexical/list": "^0.48.0",
35
+ "@lexical/react": "^0.48.0",
36
+ "@lexical/rich-text": "^0.48.0",
37
+ "@types/node": "^24.13.2",
38
+ "@types/react": "^19.2.17",
39
+ "@types/react-dom": "^19.2.3",
40
+ "@vitejs/plugin-react": "^6.0.3",
41
+ "eslint": "^10.6.0",
42
+ "eslint-plugin-react-hooks": "^7.1.1",
43
+ "eslint-plugin-react-refresh": "^0.5.3",
44
+ "globals": "^17.7.0",
45
+ "jsdom": "^29.1.1",
46
+ "lexical": "^0.48.0",
47
+ "react": "^19.2.7",
48
+ "react-dom": "^19.2.7",
49
+ "typescript": "~6.0.2",
50
+ "typescript-eslint": "^8.62.0",
51
+ "vite": "^8.1.1",
52
+ "vite-plugin-dts": "^5.0.3",
53
+ "vitest": "^4.1.10"
54
+ },
55
+ "peerDependencies": {
56
+ "@automerge/automerge": "^3.3.2",
57
+ "@automerge/automerge-repo": "^2.5.6",
58
+ "@automerge/react": "^2.5.6",
59
+ "@lexical/link": "^0.47.0",
60
+ "@lexical/list": "^0.47.0",
61
+ "@lexical/react": "^0.47.0",
62
+ "@lexical/code": "^0.47.0",
63
+ "@lexical/rich-text": "^0.47.0",
64
+ "lexical": "^0.47.0",
65
+ "react": "^18.2.0 || ^19.0.0",
66
+ "react-dom": "^18.2.0 || ^19.0.0"
67
+ }
68
+ }