@veltdev/lexical-velt-comments 1.0.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/cjs/index.js +2 -0
- package/cjs/index.js.map +1 -0
- package/cjs/types/comment-node.d.ts +23 -0
- package/cjs/types/constants.d.ts +6 -0
- package/cjs/types/editor.d.ts +384 -0
- package/cjs/types/index.d.ts +24 -0
- package/cjs/types/plugin.d.ts +14 -0
- package/cjs/types/serializer.d.ts +9 -0
- package/cjs/types/store.d.ts +39 -0
- package/cjs/types/types.d.ts +40 -0
- package/cjs/types/velt.d.ts +21 -0
- package/esm/index.js +2 -0
- package/esm/index.js.map +1 -0
- package/esm/types/comment-node.d.ts +23 -0
- package/esm/types/constants.d.ts +6 -0
- package/esm/types/editor.d.ts +384 -0
- package/esm/types/index.d.ts +24 -0
- package/esm/types/plugin.d.ts +14 -0
- package/esm/types/serializer.d.ts +9 -0
- package/esm/types/store.d.ts +39 -0
- package/esm/types/types.d.ts +40 -0
- package/esm/types/velt.d.ts +21 -0
- package/index.d.ts +58 -0
- package/package.json +44 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { LexicalEditor, LexicalNode, RangeSelection, TextNode } from 'lexical';
|
|
2
|
+
import { CommentNode } from './comment-node';
|
|
3
|
+
import type { Plugin } from './plugin';
|
|
4
|
+
import { AnnotationData } from './types';
|
|
5
|
+
interface TextMatch {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
startNode?: LexicalNode;
|
|
9
|
+
endNode?: LexicalNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Interface for tracking annotation data during processing
|
|
13
|
+
*/
|
|
14
|
+
interface AnnotationToCheck {
|
|
15
|
+
id: string;
|
|
16
|
+
multiThreadAnnotationId?: string;
|
|
17
|
+
annotation: AnnotationData;
|
|
18
|
+
nodes: {
|
|
19
|
+
node: {
|
|
20
|
+
text?: string;
|
|
21
|
+
nodeSize?: number;
|
|
22
|
+
};
|
|
23
|
+
pos: number;
|
|
24
|
+
}[];
|
|
25
|
+
originalText: string;
|
|
26
|
+
originalOccurrence: number;
|
|
27
|
+
targetTextNodeId: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Interface for tracking annotation changes during a transaction
|
|
31
|
+
*/
|
|
32
|
+
interface AnnotationChange {
|
|
33
|
+
annotationId: string;
|
|
34
|
+
multiThreadAnnotationId?: string;
|
|
35
|
+
originalText: string;
|
|
36
|
+
currentText: string;
|
|
37
|
+
originalOccurrence: number;
|
|
38
|
+
currentOccurrence: number;
|
|
39
|
+
originalTargetTextNodeId: string;
|
|
40
|
+
newTargetTextNodeId: string;
|
|
41
|
+
annotation: any;
|
|
42
|
+
contentChanged: boolean;
|
|
43
|
+
occurrenceChanged: boolean;
|
|
44
|
+
targetTextNodeIdChanged: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare class EditorService {
|
|
47
|
+
plugin: Plugin;
|
|
48
|
+
private editorUpdateListenerRemover?;
|
|
49
|
+
constructor(plugin: Plugin);
|
|
50
|
+
static getEditorId: (editor: LexicalEditor) => string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Initialize the transaction listener (equivalent to TipTap's onTransaction)
|
|
53
|
+
* Call this method when setting up the plugin
|
|
54
|
+
*/
|
|
55
|
+
initializeTransactionListener(editor: LexicalEditor): void;
|
|
56
|
+
/**
|
|
57
|
+
* Clean up the transaction listener
|
|
58
|
+
*/
|
|
59
|
+
destroyTransactionListener(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Check if the document content has actually changed
|
|
62
|
+
*/
|
|
63
|
+
private hasDocumentChanged;
|
|
64
|
+
/**
|
|
65
|
+
* Process editor changes
|
|
66
|
+
*/
|
|
67
|
+
onTransaction(editor: LexicalEditor): void;
|
|
68
|
+
/**
|
|
69
|
+
* Extract text from a CommentNode while preserving paragraph structure.
|
|
70
|
+
* This method ensures that text extracted from existing comment nodes maintains
|
|
71
|
+
* the same newline characters that were present during initial selection.
|
|
72
|
+
*
|
|
73
|
+
* @param commentNode - The CommentNode to extract text from
|
|
74
|
+
* @returns Text content with preserved paragraph breaks (newlines)
|
|
75
|
+
*/
|
|
76
|
+
private getCommentNodeTextWithStructure;
|
|
77
|
+
/**
|
|
78
|
+
* Collect all annotations present in the document for processing
|
|
79
|
+
*/
|
|
80
|
+
collectDocumentAnnotations(editor: LexicalEditor): Map<string, AnnotationToCheck>;
|
|
81
|
+
/**
|
|
82
|
+
* Process annotation changes
|
|
83
|
+
*/
|
|
84
|
+
processAnnotationChanges(editor: LexicalEditor, data: AnnotationToCheck): AnnotationChange | null;
|
|
85
|
+
/**
|
|
86
|
+
* Detect content changes while preserving paragraph structure.
|
|
87
|
+
* When multiple CommentNodes represent different paragraphs of the same annotation,
|
|
88
|
+
* we need to join them with newlines to match the original text format.
|
|
89
|
+
*/
|
|
90
|
+
detectContentChanges(nodes: AnnotationToCheck['nodes'], originalText: string): {
|
|
91
|
+
currentText: string;
|
|
92
|
+
contentChanged: boolean;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Detect container changes
|
|
96
|
+
*/
|
|
97
|
+
detectContainerChanges(editor: LexicalEditor, params: {
|
|
98
|
+
annotationId?: string;
|
|
99
|
+
currentText: string;
|
|
100
|
+
targetTextNodeId: string;
|
|
101
|
+
nodes: AnnotationToCheck['nodes'];
|
|
102
|
+
}): {
|
|
103
|
+
targetTextNodeIdChanged: boolean;
|
|
104
|
+
newTargetTextNodeId: string;
|
|
105
|
+
searchResults: TextMatch[];
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Process container changes
|
|
109
|
+
*/
|
|
110
|
+
processContainerChanges(editor: LexicalEditor, params: {
|
|
111
|
+
annotationId?: string;
|
|
112
|
+
currentText: string;
|
|
113
|
+
targetTextNodeId: string;
|
|
114
|
+
nodes: {
|
|
115
|
+
node: {
|
|
116
|
+
text?: string;
|
|
117
|
+
nodeSize?: number;
|
|
118
|
+
};
|
|
119
|
+
pos: number;
|
|
120
|
+
}[];
|
|
121
|
+
}): {
|
|
122
|
+
changed: boolean;
|
|
123
|
+
newId: string;
|
|
124
|
+
searchResults: TextMatch[];
|
|
125
|
+
} | null;
|
|
126
|
+
/**
|
|
127
|
+
* Find new container for moved content
|
|
128
|
+
*/
|
|
129
|
+
findNewContainer(editor: LexicalEditor, currentText: string): HTMLElement | null;
|
|
130
|
+
/**
|
|
131
|
+
* Calculate new occurrence
|
|
132
|
+
*/
|
|
133
|
+
calculateNewOccurrence(editor: LexicalEditor, params: {
|
|
134
|
+
searchResults: TextMatch[];
|
|
135
|
+
nodes: {
|
|
136
|
+
node: {
|
|
137
|
+
text?: string;
|
|
138
|
+
nodeSize?: number;
|
|
139
|
+
};
|
|
140
|
+
pos: number;
|
|
141
|
+
}[];
|
|
142
|
+
contentChanged: boolean;
|
|
143
|
+
currentText: string;
|
|
144
|
+
targetTextNodeId: string | null;
|
|
145
|
+
originalOccurrence: number;
|
|
146
|
+
}): number;
|
|
147
|
+
/**
|
|
148
|
+
* Create annotation change object
|
|
149
|
+
*/
|
|
150
|
+
createAnnotationChange(data: AnnotationToCheck, changes: {
|
|
151
|
+
currentText: string;
|
|
152
|
+
contentChanged: boolean;
|
|
153
|
+
currentOccurrence: number;
|
|
154
|
+
occurrenceChanged: boolean;
|
|
155
|
+
targetTextNodeIdChanged: boolean;
|
|
156
|
+
newTargetTextNodeId: string;
|
|
157
|
+
}): AnnotationChange;
|
|
158
|
+
/**
|
|
159
|
+
* Update Velt comments
|
|
160
|
+
*/
|
|
161
|
+
updateVeltComments(commentElement: {
|
|
162
|
+
updateContext: (id: string, context: unknown) => void;
|
|
163
|
+
}, changes: Map<string, AnnotationChange>, editor: LexicalEditor): Promise<void>;
|
|
164
|
+
getSelectedText(editor: LexicalEditor): string;
|
|
165
|
+
/**
|
|
166
|
+
* Find occurrence index of selected text in Lexical editor.
|
|
167
|
+
* This method determines which occurrence of a text pattern the current selection represents,
|
|
168
|
+
* which is crucial for multi-paragraph text selections that span across different paragraphs.
|
|
169
|
+
*
|
|
170
|
+
* @param editor - The Lexical editor instance
|
|
171
|
+
* @param selectedText - The text that is currently selected (may include newlines for multi-paragraph selections)
|
|
172
|
+
* @param targetContainer - Optional container element to limit search scope
|
|
173
|
+
* @returns The 1-indexed occurrence number of the selected text
|
|
174
|
+
*/
|
|
175
|
+
findOccurrenceIndex(editor: LexicalEditor, selectedText: string, targetContainer: HTMLElement | null): number;
|
|
176
|
+
/**
|
|
177
|
+
* Get the text range of current selection, accounting for paragraph breaks.
|
|
178
|
+
* This method maps Lexical selection positions to text offsets in the combined text
|
|
179
|
+
* representation that includes newline characters between paragraphs.
|
|
180
|
+
*
|
|
181
|
+
* @param editor - The Lexical editor instance
|
|
182
|
+
* @param selection - The current range selection
|
|
183
|
+
* @returns Object with start and end positions in the combined text
|
|
184
|
+
*/
|
|
185
|
+
getSelectionTextRange(editor: LexicalEditor, selection: RangeSelection): {
|
|
186
|
+
start: number;
|
|
187
|
+
end: number;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Collect all text content including paragraph breaks.
|
|
191
|
+
* This method creates a combined text representation of the entire document,
|
|
192
|
+
* inserting newline characters between paragraphs to maintain text structure
|
|
193
|
+
* for accurate multi-paragraph text matching.
|
|
194
|
+
*
|
|
195
|
+
* @param root - The root Lexical node to traverse
|
|
196
|
+
* @returns Object containing:
|
|
197
|
+
* - combinedText: Complete text with newlines between paragraphs
|
|
198
|
+
* - nodeMap: Array mapping each text node and paragraph break to its position
|
|
199
|
+
*/
|
|
200
|
+
collectTextContent(root: LexicalNode): {
|
|
201
|
+
combinedText: string;
|
|
202
|
+
nodeMap: {
|
|
203
|
+
node: TextNode | null;
|
|
204
|
+
start: number;
|
|
205
|
+
end: number;
|
|
206
|
+
isParagraphBreak?: boolean;
|
|
207
|
+
}[];
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Find all text occurrences in the entire Lexical editor.
|
|
211
|
+
* This method supports multi-paragraph text search by using the combined text
|
|
212
|
+
* representation that includes newline characters between paragraphs.
|
|
213
|
+
*
|
|
214
|
+
* @param editor - The Lexical editor instance
|
|
215
|
+
* @param searchText - The text to search for (may include newlines for multi-paragraph searches)
|
|
216
|
+
* @returns Array of TextMatch objects with start and end positions
|
|
217
|
+
*/
|
|
218
|
+
findTextInEditor(editor: LexicalEditor, searchText: string): TextMatch[];
|
|
219
|
+
/**
|
|
220
|
+
* Find text occurrences within a specific DOM container (Updated to handle paragraph breaks)
|
|
221
|
+
*/
|
|
222
|
+
findTextInContainer(editor: LexicalEditor, searchText: string, containerId: string): TextMatch[];
|
|
223
|
+
/**
|
|
224
|
+
* Get text content for a specific container
|
|
225
|
+
*/
|
|
226
|
+
getContainerTextContent(containerElement: HTMLElement, editor: LexicalEditor, nodeMap: {
|
|
227
|
+
node: TextNode | null;
|
|
228
|
+
start: number;
|
|
229
|
+
end: number;
|
|
230
|
+
isParagraphBreak?: boolean;
|
|
231
|
+
}[], combinedText: string): {
|
|
232
|
+
text: string;
|
|
233
|
+
startOffset: number;
|
|
234
|
+
} | null;
|
|
235
|
+
/**
|
|
236
|
+
* Filter text nodes that are within a specific DOM container
|
|
237
|
+
*/
|
|
238
|
+
filterNodesInContainer(textNodes: any[], containerElement: HTMLElement, editor: LexicalEditor): any[];
|
|
239
|
+
/**
|
|
240
|
+
* Collect all text nodes with their cumulative text offsets (helper method)
|
|
241
|
+
*/
|
|
242
|
+
collectTextNodes(root: LexicalNode): {
|
|
243
|
+
node: TextNode;
|
|
244
|
+
text: string;
|
|
245
|
+
textOffset: number;
|
|
246
|
+
}[];
|
|
247
|
+
/**
|
|
248
|
+
* Get text nodes within a specific character range (Updated to handle paragraph breaks)
|
|
249
|
+
*/
|
|
250
|
+
getTextNodesInRange(root: LexicalNode, fromPos: number, toPos: number, excludeAnnotationId?: string): {
|
|
251
|
+
node: TextNode;
|
|
252
|
+
startOffset: number;
|
|
253
|
+
endOffset: number;
|
|
254
|
+
isPartial: boolean;
|
|
255
|
+
}[];
|
|
256
|
+
/**
|
|
257
|
+
* Check if a text range is already wrapped (Updated to handle paragraph breaks)
|
|
258
|
+
*/
|
|
259
|
+
isRangeAlreadyWrapped(root: LexicalNode, fromPos: number, toPos: number, annotationId: string): boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Collect text nodes within a specific range
|
|
262
|
+
*/
|
|
263
|
+
collectTextNodesInRange(root: LexicalNode, range: {
|
|
264
|
+
start: number;
|
|
265
|
+
end: number;
|
|
266
|
+
}): {
|
|
267
|
+
node: TextNode;
|
|
268
|
+
text: string;
|
|
269
|
+
start: number;
|
|
270
|
+
end: number;
|
|
271
|
+
}[];
|
|
272
|
+
/**
|
|
273
|
+
* Get the text range for a specific DOM container
|
|
274
|
+
*/
|
|
275
|
+
getContainerRange(editor: LexicalEditor, containerElement: HTMLElement): {
|
|
276
|
+
start: number;
|
|
277
|
+
end: number;
|
|
278
|
+
} | null;
|
|
279
|
+
/**
|
|
280
|
+
* KMP Search implementation
|
|
281
|
+
*/
|
|
282
|
+
kmpSearch(text: string, pattern: string, startPos?: number, maxOccurrences?: number): number[];
|
|
283
|
+
/**
|
|
284
|
+
* Compute KMP failure function
|
|
285
|
+
*/
|
|
286
|
+
computeKMPTable(pattern: string): number[];
|
|
287
|
+
findParentContainerWithId(selection: RangeSelection, editor: LexicalEditor): HTMLElement | null;
|
|
288
|
+
/**
|
|
289
|
+
* Highlights text in the editor with a Velt comment annotation.
|
|
290
|
+
* This method supports multi-paragraph text highlighting by preserving paragraph structure
|
|
291
|
+
* and creating separate comment nodes for each paragraph when necessary.
|
|
292
|
+
*
|
|
293
|
+
* @param editor - The Lexical editor instance
|
|
294
|
+
* @param textToFind - The text to highlight (may include newlines for multi-paragraph selections)
|
|
295
|
+
* @param commentOptions - Configuration options for the comment annotation
|
|
296
|
+
*/
|
|
297
|
+
highlightTextWithVeltComment(editor: LexicalEditor, textToFind: string, commentOptions: {
|
|
298
|
+
annotationId?: string;
|
|
299
|
+
multiThreadAnnotationId?: string;
|
|
300
|
+
occurrence?: number;
|
|
301
|
+
targetTextNodeId?: string;
|
|
302
|
+
originalAnnotation?: any;
|
|
303
|
+
}): void;
|
|
304
|
+
/**
|
|
305
|
+
* Find text within a specific DOM element
|
|
306
|
+
*/
|
|
307
|
+
findTextInDomElement(editor: LexicalEditor, text: string, domElementId: string, desiredOccurrence?: number): TextMatch[];
|
|
308
|
+
/**
|
|
309
|
+
* Find text in the entire document with support for multi-paragraph searches.
|
|
310
|
+
* This method is used by the highlighting system to locate text that needs to be wrapped
|
|
311
|
+
* with comment annotations, including text that spans across multiple paragraphs.
|
|
312
|
+
*
|
|
313
|
+
* @param editor - The Lexical editor instance
|
|
314
|
+
* @param text - The text to search for (may include newlines for multi-paragraph searches)
|
|
315
|
+
* @param desiredOccurrence - Optional limit on the number of occurrences to find
|
|
316
|
+
* @returns Array of TextMatch objects with start and end positions
|
|
317
|
+
*/
|
|
318
|
+
findTextInDocument(editor: LexicalEditor, text: string, desiredOccurrence?: number): TextMatch[];
|
|
319
|
+
/**
|
|
320
|
+
* Creates a Velt comment highlight at the specified range.
|
|
321
|
+
* This method intelligently handles different scenarios including single text nodes,
|
|
322
|
+
* partial text nodes, and multi-paragraph selections while preserving document structure.
|
|
323
|
+
*
|
|
324
|
+
* @param editor - The Lexical editor instance
|
|
325
|
+
* @param annotationId - The annotation ID for the comment
|
|
326
|
+
* @param multiThreadAnnotationId - Optional multi-thread annotation ID
|
|
327
|
+
* @param fromPos - Start position in the combined text representation
|
|
328
|
+
* @param toPos - End position in the combined text representation
|
|
329
|
+
*/
|
|
330
|
+
setVeltComment(editor: LexicalEditor, annotationId: string, multiThreadAnnotationId: string | undefined, fromPos: number, toPos: number): void;
|
|
331
|
+
/**
|
|
332
|
+
* Wrap a partial text node (when selection is within the node) - PRESERVING FORMATTING
|
|
333
|
+
*/
|
|
334
|
+
wrapPartialTextNode(textNode: TextNode, startOffset: number, endOffset: number, commentNode: CommentNode): void;
|
|
335
|
+
/**
|
|
336
|
+
* Wrap an entire text node - PRESERVING FORMATTING
|
|
337
|
+
*/
|
|
338
|
+
wrapEntireTextNode(textNode: TextNode, commentNode: CommentNode): void;
|
|
339
|
+
/**
|
|
340
|
+
* Wrap multiple text nodes while preserving formatting and paragraph structure.
|
|
341
|
+
* This method groups text nodes by their paragraph parent and creates separate
|
|
342
|
+
* comment nodes for each paragraph to maintain document structure integrity.
|
|
343
|
+
*
|
|
344
|
+
* @param textNodesToWrap - Array of text nodes with their offset information
|
|
345
|
+
* @param commentNode - The base comment node to use (additional nodes will be created for other paragraphs)
|
|
346
|
+
*/
|
|
347
|
+
wrapMultipleTextNodesWithFormatting(textNodesToWrap: {
|
|
348
|
+
node: TextNode;
|
|
349
|
+
startOffset: number;
|
|
350
|
+
endOffset: number;
|
|
351
|
+
isPartial: boolean;
|
|
352
|
+
}[], commentNode: CommentNode): void;
|
|
353
|
+
/**
|
|
354
|
+
* Find the paragraph parent of a text node.
|
|
355
|
+
* Traverses up the node hierarchy to find the paragraph element that contains this text node.
|
|
356
|
+
*
|
|
357
|
+
* @param node - The text node to find the paragraph parent for
|
|
358
|
+
* @returns The paragraph node or null if not found
|
|
359
|
+
*/
|
|
360
|
+
findParagraphParent(node: TextNode): LexicalNode | null;
|
|
361
|
+
/**
|
|
362
|
+
* Wrap multiple text nodes within the same paragraph.
|
|
363
|
+
* This method handles complex scenarios where multiple text nodes need to be wrapped
|
|
364
|
+
* while maintaining their original formatting and order within the paragraph.
|
|
365
|
+
*
|
|
366
|
+
* @param nodesInParagraph - Array of text nodes within the same paragraph
|
|
367
|
+
* @param commentNode - The comment node to wrap the text nodes with
|
|
368
|
+
*/
|
|
369
|
+
wrapMultipleNodesInSameParagraph(nodesInParagraph: {
|
|
370
|
+
node: TextNode;
|
|
371
|
+
startOffset: number;
|
|
372
|
+
endOffset: number;
|
|
373
|
+
isPartial: boolean;
|
|
374
|
+
}[], commentNode: CommentNode): void;
|
|
375
|
+
/**
|
|
376
|
+
* Removes a Velt comment from the Lexical document
|
|
377
|
+
* @param editor - The Lexical editor instance
|
|
378
|
+
* @param annotationId - The ID of the annotation to remove
|
|
379
|
+
* @returns boolean - True if the annotation was successfully removed, false otherwise
|
|
380
|
+
* @throws Error if the stored data is invalid
|
|
381
|
+
*/
|
|
382
|
+
removeVeltCommentFromEditor(editor: LexicalEditor, annotationId: string): boolean;
|
|
383
|
+
}
|
|
384
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CommentAnnotation } from '@veltdev/types';
|
|
2
|
+
import { LexicalEditor } from 'lexical';
|
|
3
|
+
import { LexicalVeltCommentConfig } from "./types";
|
|
4
|
+
export * from './comment-node';
|
|
5
|
+
export * from './serializer';
|
|
6
|
+
export declare const triggerAddComment: (editor: LexicalEditor, config?: LexicalVeltCommentConfig) => Promise<void>;
|
|
7
|
+
export interface AddCommentRequest {
|
|
8
|
+
editorId?: string;
|
|
9
|
+
editor: LexicalEditor;
|
|
10
|
+
context?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a comment annotation for the currently selected text in the editor
|
|
14
|
+
* This function handles the entire flow from getting the selected text to creating
|
|
15
|
+
* the actual comment through the Velt service
|
|
16
|
+
*/
|
|
17
|
+
export declare const addComment: ({ editorId, editor, context: clientContext, }: AddCommentRequest) => Promise<void>;
|
|
18
|
+
export declare function highlightComments(editor: LexicalEditor, commentAnnotations: CommentAnnotation[], editorId?: string): void;
|
|
19
|
+
export interface RenderCommentsRequest {
|
|
20
|
+
editor: LexicalEditor;
|
|
21
|
+
editorId?: string;
|
|
22
|
+
commentAnnotations?: CommentAnnotation[];
|
|
23
|
+
}
|
|
24
|
+
export declare const renderComments: ({ editor, editorId, commentAnnotations }: RenderCommentsRequest) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LexicalEditor } from 'lexical';
|
|
2
|
+
import { EditorService } from './editor';
|
|
3
|
+
import { StoreService } from './store';
|
|
4
|
+
export declare class Plugin {
|
|
5
|
+
editorId: string;
|
|
6
|
+
editor: LexicalEditor | undefined;
|
|
7
|
+
editorService: EditorService;
|
|
8
|
+
storeService: StoreService;
|
|
9
|
+
constructor(editorId: string, editor?: LexicalEditor);
|
|
10
|
+
static getInstance(config?: {
|
|
11
|
+
editorId?: string | null;
|
|
12
|
+
editor?: LexicalEditor;
|
|
13
|
+
}): Plugin;
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LexicalEditor } from "lexical";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced export function with multiple strategies
|
|
4
|
+
*/
|
|
5
|
+
export declare const exportCleanEditorContent: (editor: LexicalEditor) => string;
|
|
6
|
+
/**
|
|
7
|
+
* Deserializes editor state from clean JSON
|
|
8
|
+
*/
|
|
9
|
+
export declare function deserializeCleanState(editor: LexicalEditor, jsonString: string): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CommentAnnotation } from '@veltdev/types';
|
|
2
|
+
import { LexicalEditor } from 'lexical';
|
|
3
|
+
import type { Plugin } from './plugin';
|
|
4
|
+
import type { AnnotationData, PluginReference } from './types';
|
|
5
|
+
type GlobalStore = {
|
|
6
|
+
editor?: LexicalEditor | null;
|
|
7
|
+
comments?: CommentAnnotation[];
|
|
8
|
+
filteredComments?: CommentAnnotation[];
|
|
9
|
+
selectedCommentsMap?: Map<string, boolean>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Interface for annotation store entry
|
|
13
|
+
*/
|
|
14
|
+
interface AnnotationStoreEntry {
|
|
15
|
+
annotation: AnnotationData;
|
|
16
|
+
pluginReference: PluginReference;
|
|
17
|
+
}
|
|
18
|
+
export declare class StoreService {
|
|
19
|
+
static storeInstanceMap: Map<string, StoreService>;
|
|
20
|
+
plugin: Plugin;
|
|
21
|
+
isSubscribed: boolean;
|
|
22
|
+
globalStore: GlobalStore;
|
|
23
|
+
globalAnnotationStore: Map<string, AnnotationStoreEntry>;
|
|
24
|
+
/**
|
|
25
|
+
* Helper to get and set annotations with debug messages
|
|
26
|
+
*/
|
|
27
|
+
annotationStore: {
|
|
28
|
+
get: (id: string) => AnnotationStoreEntry | undefined;
|
|
29
|
+
set: (id: string, data: AnnotationStoreEntry) => void;
|
|
30
|
+
getAll: () => Map<string, AnnotationStoreEntry>;
|
|
31
|
+
clear: () => void;
|
|
32
|
+
remove: (id: string) => void;
|
|
33
|
+
hasAnnotationTextChanged: (annotationId: string, currentAnnotation: AnnotationData) => boolean;
|
|
34
|
+
};
|
|
35
|
+
constructor(plugin: Plugin);
|
|
36
|
+
updateGlobalStore: (store: GlobalStore) => void;
|
|
37
|
+
highlightCommentsFromGlobalStore: () => void;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type CommentAnnotationContext = {
|
|
2
|
+
textEditorConfig?: {
|
|
3
|
+
text: string;
|
|
4
|
+
occurrence: number;
|
|
5
|
+
editorId?: string;
|
|
6
|
+
targetTextNodeId?: string;
|
|
7
|
+
};
|
|
8
|
+
} & Record<string, unknown>;
|
|
9
|
+
export interface LexicalVeltCommentConfig {
|
|
10
|
+
context?: any;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Interface for annotation data
|
|
14
|
+
*/
|
|
15
|
+
export interface AnnotationData {
|
|
16
|
+
annotationId?: string;
|
|
17
|
+
multiThreadAnnotationId?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
context?: AnnotationContext;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Interface for annotation context
|
|
23
|
+
*/
|
|
24
|
+
export interface AnnotationContext {
|
|
25
|
+
textEditorConfig?: {
|
|
26
|
+
text?: string;
|
|
27
|
+
occurrence?: number;
|
|
28
|
+
targetTextNodeId?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Interface for plugin reference in annotation store
|
|
33
|
+
*/
|
|
34
|
+
export interface PluginReference {
|
|
35
|
+
node: HTMLElement;
|
|
36
|
+
position: {
|
|
37
|
+
from: number;
|
|
38
|
+
to: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Location } from '@veltdev/types';
|
|
2
|
+
import type { CommentAnnotationContext } from './types';
|
|
3
|
+
export declare class VeltService {
|
|
4
|
+
static selectedCommentsMap: Map<string, boolean>;
|
|
5
|
+
static subscribers: Map<string, (selectedCommentsMap: Map<string, boolean>) => void>;
|
|
6
|
+
static isSubscribed: boolean;
|
|
7
|
+
static getCommentElement: () => import("@veltdev/types").CommentElement | null;
|
|
8
|
+
static addManualComment: ({ context, location, }: {
|
|
9
|
+
context: CommentAnnotationContext;
|
|
10
|
+
location?: Location | undefined;
|
|
11
|
+
}) => Promise<any>;
|
|
12
|
+
static updateContext: ({ annotationId, context, }: {
|
|
13
|
+
annotationId: string;
|
|
14
|
+
context: CommentAnnotationContext;
|
|
15
|
+
}) => Promise<any> | undefined;
|
|
16
|
+
static subscribeToSelectedCommentsMap: (id: string, subscriber: (selectedCommentsMap: Map<string, boolean>) => void) => void;
|
|
17
|
+
static unsubscribeFromSelectedCommentsMap: (id: string) => void;
|
|
18
|
+
static catchError: (message: string, otherProperties?: any) => void;
|
|
19
|
+
static subscribeToSelectedAnnotationsMapSingleton: () => Map<any, any> | undefined;
|
|
20
|
+
private static updateSelectedCommentsMap;
|
|
21
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { CommentAnnotation } from '@veltdev/types';
|
|
2
|
+
import { ElementNode, NodeKey, EditorConfig, LexicalNode, Spread, SerializedElementNode, LexicalEditor } from 'lexical';
|
|
3
|
+
|
|
4
|
+
interface LexicalVeltCommentConfig {
|
|
5
|
+
context?: any;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type SerializedCommentNode = Spread<{
|
|
9
|
+
annotationId?: string;
|
|
10
|
+
multiThreadAnnotationId?: string;
|
|
11
|
+
type: 'comment';
|
|
12
|
+
version: 1;
|
|
13
|
+
}, SerializedElementNode>;
|
|
14
|
+
declare class CommentNode extends ElementNode {
|
|
15
|
+
__annotationId?: string;
|
|
16
|
+
__multiThreadAnnotationId?: string;
|
|
17
|
+
static getType(): string;
|
|
18
|
+
static clone(node: CommentNode): CommentNode;
|
|
19
|
+
constructor(annotationId?: string, multiThreadAnnotationId?: string, key?: NodeKey);
|
|
20
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
21
|
+
updateDOM(prevNode: CommentNode, dom: HTMLElement, config: EditorConfig): boolean;
|
|
22
|
+
static importJSON(serializedNode: SerializedCommentNode): CommentNode;
|
|
23
|
+
exportJSON(): SerializedCommentNode;
|
|
24
|
+
isInline(): boolean;
|
|
25
|
+
}
|
|
26
|
+
declare function $createCommentNode(annotationId?: string, multiThreadAnnotationId?: string): CommentNode;
|
|
27
|
+
declare function $isCommentNode(node: LexicalNode | null | undefined): node is CommentNode;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Enhanced export function with multiple strategies
|
|
31
|
+
*/
|
|
32
|
+
declare const exportCleanEditorContent: (editor: LexicalEditor) => string;
|
|
33
|
+
/**
|
|
34
|
+
* Deserializes editor state from clean JSON
|
|
35
|
+
*/
|
|
36
|
+
declare function deserializeCleanState(editor: LexicalEditor, jsonString: string): void;
|
|
37
|
+
|
|
38
|
+
declare const triggerAddComment: (editor: LexicalEditor, config?: LexicalVeltCommentConfig) => Promise<void>;
|
|
39
|
+
interface AddCommentRequest {
|
|
40
|
+
editorId?: string;
|
|
41
|
+
editor: LexicalEditor;
|
|
42
|
+
context?: unknown;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a comment annotation for the currently selected text in the editor
|
|
46
|
+
* This function handles the entire flow from getting the selected text to creating
|
|
47
|
+
* the actual comment through the Velt service
|
|
48
|
+
*/
|
|
49
|
+
declare const addComment: ({ editorId, editor, context: clientContext, }: AddCommentRequest) => Promise<void>;
|
|
50
|
+
declare function highlightComments(editor: LexicalEditor, commentAnnotations: CommentAnnotation[], editorId?: string): void;
|
|
51
|
+
interface RenderCommentsRequest {
|
|
52
|
+
editor: LexicalEditor;
|
|
53
|
+
editorId?: string;
|
|
54
|
+
commentAnnotations?: CommentAnnotation[];
|
|
55
|
+
}
|
|
56
|
+
declare const renderComments: ({ editor, editorId, commentAnnotations }: RenderCommentsRequest) => void;
|
|
57
|
+
|
|
58
|
+
export { $createCommentNode, $isCommentNode, AddCommentRequest, CommentNode, RenderCommentsRequest, addComment, deserializeCleanState, exportCleanEditorContent, highlightComments, renderComments, triggerAddComment };
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veltdev/lexical-velt-comments",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lexical Extension to add Google Docs-style overlay comments to your Lexical editor. Works with the Velt Collaboration SDK.",
|
|
5
|
+
"homepage": "https://velt.dev",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"lexical",
|
|
8
|
+
"react",
|
|
9
|
+
"velt",
|
|
10
|
+
"real-time",
|
|
11
|
+
"realtime",
|
|
12
|
+
"toolkit",
|
|
13
|
+
"multiplayer",
|
|
14
|
+
"websockets",
|
|
15
|
+
"collaboration",
|
|
16
|
+
"collaborative",
|
|
17
|
+
"presence",
|
|
18
|
+
"rooms",
|
|
19
|
+
"documents",
|
|
20
|
+
"conflict resolution",
|
|
21
|
+
"crdts",
|
|
22
|
+
"comment",
|
|
23
|
+
"comments",
|
|
24
|
+
"recording",
|
|
25
|
+
"screen recording",
|
|
26
|
+
"cursors",
|
|
27
|
+
"notifications",
|
|
28
|
+
"cord",
|
|
29
|
+
"liveblocks"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
33
|
+
"version:update": "node ../update-npm-package.mjs",
|
|
34
|
+
"publish:extension": "npm publish --access public"
|
|
35
|
+
},
|
|
36
|
+
"author": "Velt",
|
|
37
|
+
"license": "Proprietary",
|
|
38
|
+
"main": "cjs/index.js",
|
|
39
|
+
"module": "esm/index.js",
|
|
40
|
+
"types": "index.d.ts",
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"lexical": "*"
|
|
43
|
+
}
|
|
44
|
+
}
|