@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/esm/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ElementNode as t,$getRoot as e,$isTextNode as n,$isElementNode as r,$getSelection as o,$isRangeSelection as i,$createTextNode as a,createEditor as c}from"lexical";class s extends t{static getType(){return"comment"}static clone(t){return new s(t.__annotationId,t.__multiThreadAnnotationId,t.__key)}constructor(t,e,n){super(n),this.__annotationId=t,this.__multiThreadAnnotationId=e}createDOM(t){const e=document.createElement("velt-comment-text");return this.__annotationId&&e.setAttribute("annotation-id",this.__annotationId),this.__multiThreadAnnotationId&&e.setAttribute("multi-thread-annotation-id",this.__multiThreadAnnotationId),e}updateDOM(t,e,n){return!1}static importJSON(t){return d(t.annotationId,t.multiThreadAnnotationId)}exportJSON(){return{...super.exportJSON(),annotationId:this.__annotationId,multiThreadAnnotationId:this.__multiThreadAnnotationId,type:"comment",version:1}}isInline(){return!0}}function d(t,e){return new s(t,e)}function l(t){return t instanceof s}var g;class h{}g=h,h.selectedCommentsMap=new Map,h.subscribers=new Map,h.isSubscribed=!1,h.getCommentElement=()=>{try{const t=window.Velt;return t?.getCommentElement?t.getCommentElement():null}catch(t){return g.catchError("Error getting comment element:",t),null}},h.addManualComment=({context:t,location:e})=>{try{const n=g.getCommentElement();return n?n.addManualComment({context:t,location:e}):Promise.resolve(null)}catch(t){return g.catchError("Error adding manual comment:",t),Promise.resolve(null)}},h.updateContext=({annotationId:t,context:e})=>{try{const n=g.getCommentElement();return n?n.updateContext(t,e):Promise.resolve(null)}catch(t){g.catchError("Error updating context:",t)}},h.subscribeToSelectedCommentsMap=(t,e)=>{try{g.subscribeToSelectedAnnotationsMapSingleton(),g.subscribers.set(t,e)}catch(t){g.catchError("Error subscribing to selected comments map:",t)}},h.unsubscribeFromSelectedCommentsMap=t=>{try{g.subscribers.delete(t)}catch(t){g.catchError("Error unsubscribing from selected comments map:",t)}},h.catchError=(t,e)=>{try{let n={};e&&"object"==typeof e&&(n={...n,...e}),t&&(n.message=t),n.sdkVersion=window.Velt?.version}catch(t){}},h.subscribeToSelectedAnnotationsMapSingleton=()=>{try{if(g.isSubscribed)return;const t=g.getCommentElement();t&&"function"==typeof t?.getSelectedComments&&(t?.getSelectedComments()?.subscribe(t=>{const e=new Map;t?.forEach(t=>{t?.annotationId&&e.set(t?.annotationId,!0)});((t,e)=>{if(t.size!==e.size)return!1;for(const[n,r]of t)if(e.get(n)!==r)return!1;return!0})(e,g.selectedCommentsMap||new Map)||g.updateSelectedCommentsMap(e)}),g.isSubscribed=!0)}catch(t){return new Map}},h.updateSelectedCommentsMap=t=>{try{g.selectedCommentsMap=t,g.subscribers.forEach(t=>{"function"==typeof t&&t(g.selectedCommentsMap)})}catch(t){g.catchError("Error updating selected comments map:",t)}};class u{constructor(t){this.plugin=t}initializeTransactionListener(t){try{this.editorUpdateListenerRemover&&this.editorUpdateListenerRemover(),this.editorUpdateListenerRemover=t.registerUpdateListener(({editorState:e,prevEditorState:n})=>{try{if(e===n||!this.hasDocumentChanged(e,n))return;this.onTransaction(t)}catch(t){}})}catch(t){h.catchError("Error initializing transaction listener:",t)}}destroyTransactionListener(){try{this.editorUpdateListenerRemover&&(this.editorUpdateListenerRemover(),this.editorUpdateListenerRemover=void 0)}catch(t){h.catchError("Error destroying transaction listener:",t)}}hasDocumentChanged(t,n){try{return t.read(()=>{const t=e().getTextContent();return n.read(()=>{const n=e().getTextContent();return t!==n})})}catch(t){return!0}}onTransaction(t){try{const e=new Map,n=this.collectDocumentAnnotations(t);for(const[r,o]of Array.from(n.entries())){const n=this.processAnnotationChanges(t,o);n&&e.set(r,n)}if(e.size>0){const n=h.getCommentElement();if(!n)return;this.updateVeltComments(n,e,t)}}catch(t){h.catchError("Error in onTransaction:",t)}}getCommentNodeTextWithStructure(t){try{let e="";const o=(t,i=0)=>{if(n(t))e+=t.getTextContent();else if(r(t)){const n=t.getChildren(),a=t.getType();for(let t=0;t<n.length;t++)o(n[t],i+1);if("paragraph"===a&&i>=0){const n=t.getParent();if(n&&r(n)){const r=n.getChildren();r.indexOf(t)<r.length-1&&(e+="\n")}}}};return o(t),e}catch(e){return t.getTextContent()}}collectDocumentAnnotations(t){try{const o=new Map,i=this;return t.getEditorState().read(()=>{const t=e();let a=0;const c=t=>{if(l(t)){const e=t.__annotationId;if(!e)return;const n=i.plugin.storeService.annotationStore.get(e);if(!n)return;const r=i.getCommentNodeTextWithStructure(t),c=r.length,s=o.get(e),d={node:{text:r,nodeSize:c},pos:a};s?s.nodes.push(d):o.set(e,{id:e,multiThreadAnnotationId:t.__multiThreadAnnotationId,annotation:n.annotation,nodes:[d],originalText:n.annotation.context?.textEditorConfig?.text||"",originalOccurrence:n.annotation.context?.textEditorConfig?.occurrence||1,targetTextNodeId:n.annotation.context?.textEditorConfig?.targetTextNodeId||""}),a+=c}else if(n(t))a+=t.getTextContent().length;else if(r(t)){const e=t.getChildren();for(const t of e)c(t)}};c(t)}),o}catch(t){return h.catchError("Error collecting document annotations:",t),new Map}}processAnnotationChanges(t,e){try{if(!t||!e)throw new Error("Invalid input: editor and data are required");if(!e.originalText||!e.annotation)return null;if(!Array.isArray(e.nodes)||0===e.nodes.length)return null;const{currentText:n,contentChanged:r}=this.detectContentChanges(e.nodes,e.originalText),{targetTextNodeIdChanged:o,newTargetTextNodeId:i,searchResults:a}=this.detectContainerChanges(t,{annotationId:e.id,currentText:n,targetTextNodeId:e.targetTextNodeId,nodes:e.nodes}),c=this.calculateNewOccurrence(t,{searchResults:a,nodes:e.nodes,contentChanged:r,currentText:n,targetTextNodeId:o?i:e.targetTextNodeId,originalOccurrence:e.originalOccurrence}),s=c!==e.originalOccurrence;return r||s||o?this.createAnnotationChange(e,{currentText:n,contentChanged:r,currentOccurrence:c,occurrenceChanged:s,targetTextNodeIdChanged:o,newTargetTextNodeId:i}):null}catch(t){return h.catchError("Error processing annotation changes:",t),null}}detectContentChanges(t,e){const n=[...t].sort((t,e)=>t.pos-e.pos),r=n.length>1&&e.includes("\n")?n.map(t=>t.node.text||"").join("\n"):n.map(t=>t.node.text||"").join("");return{currentText:r,contentChanged:r!==e}}detectContainerChanges(t,e){const{annotationId:n,currentText:r,targetTextNodeId:o,nodes:i}=e;let a=!1,c=o,s=[];const d=this.processContainerChanges(t,{annotationId:n,currentText:r,targetTextNodeId:o,nodes:i});return d?(a=d.changed,c=d.newId,s=d.searchResults):s=o?this.findTextInDomElement(t,r,o,void 0):this.findTextInDocument(t,r,void 0),{targetTextNodeIdChanged:a,newTargetTextNodeId:c,searchResults:s}}processContainerChanges(t,o){const{annotationId:i,currentText:a,targetTextNodeId:c}=o;if(!c)return null;let s=null;if(t.getEditorState().read(()=>{const t=e();let o=0;!function t(e){if(l(e)&&e.__annotationId===i)s=o;else if(n(e))o+=e.getTextContent().length;else if(r(e)){const n=e.getChildren();for(const e of n)if(t(e),null!==s)return}}(t)}),null!==s){const e=t.getRootElement();if(!e)return null;if(e.querySelector(`#${c}`)){if(0===this.findTextInDomElement(t,a,c,void 0).length){const e=this.findNewContainer(t,a);if(e&&e.id!==c){const n=this.findTextInDomElement(t,a,e.id,void 0);return{changed:!0,newId:e.id,searchResults:n}}}}}return null}findNewContainer(t,e){const n=t.getRootElement();if(!n)return null;const r=n.querySelectorAll("[id]");for(const t of Array.from(r))if(t.textContent?.includes(e))return t;return null}calculateNewOccurrence(t,e){if(0===e.searchResults.length)return e.originalOccurrence;const n=e.nodes[0],r=e.nodes[e.nodes.length-1],o=n.pos,i=r.pos+(r.node.nodeSize||0);for(let t=0;t<e.searchResults.length;t++){const n=e.searchResults[t];if(n.start<=o&&n.end>=o||n.start<=i&&n.end>=i||o<=n.start&&i>=n.end)return t+1}return e.originalOccurrence}createAnnotationChange(t,e){const{currentText:n,contentChanged:r,currentOccurrence:o,occurrenceChanged:i,targetTextNodeIdChanged:a,newTargetTextNodeId:c}=e;return{annotationId:t.id,multiThreadAnnotationId:t.multiThreadAnnotationId,originalText:t.originalText,currentText:r?n:t.originalText,originalOccurrence:t.originalOccurrence,currentOccurrence:o,originalTargetTextNodeId:t.targetTextNodeId,newTargetTextNodeId:a?c:t.targetTextNodeId,annotation:t.annotation,contentChanged:r,occurrenceChanged:i,targetTextNodeIdChanged:a}}async updateVeltComments(t,e,n){try{for(const[t,r]of Array.from(e.entries()))try{if(!r.annotation.context?.textEditorConfig)continue;const e=JSON.parse(JSON.stringify(r.annotation.context.textEditorConfig));r.contentChanged&&(e.text=r.currentText),r.occurrenceChanged&&(e.occurrence=r.currentOccurrence),r.targetTextNodeIdChanged&&(e.targetTextNodeId=r.newTargetTextNodeId);const o={...r.annotation.context,textEditorConfig:e};this.plugin.storeService.annotationStore.remove(t),this.removeVeltCommentFromEditor(n,t),await new Promise(t=>setTimeout(t,100)),h.updateContext({annotationId:t,context:o})}catch(t){h.catchError("Error updating individual Velt comment:",t)}}catch(t){h.catchError("Error updating Velt comments:",t)}}getSelectedText(t){return t.getEditorState().read(()=>{const t=o();return i(t)?t.getTextContent():""})}findOccurrenceIndex(t,e,n){try{if(!e.trim())return 1;let r=[],a=0,c=0;t.getEditorState().read(()=>{const s=o();if(i(s)){const o=this.getSelectionTextRange(t,s);a=o.start,c=o.end,r=n&&n.id?this.findTextInContainer(t,e,n.id):this.findTextInEditor(t,e)}});for(let t=0;t<r.length;t++)if(r[t].start===a&&r[t].end===c)return t+1;return 1}catch(t){return h.catchError("Error finding occurrence index:",t),1}}getSelectionTextRange(t,n){const r=e(),{nodeMap:o}=this.collectTextContent(r),i=n.anchor.getNode(),a=n.focus.getNode(),c=n.anchor.offset,s=n.focus.offset;let d=0,l=0;for(const t of o){if(t.isParagraphBreak||!t.node)continue;const e=t.node.getKey();e===i.getKey()&&(d=t.start+c),e===a.getKey()&&(l=t.start+s)}return{start:Math.min(d,l),end:Math.max(d,l)}}collectTextContent(t){try{const e=[];let o="",i=0;const a=(t,c=0)=>{if(n(t)){const n=t.getTextContent(),r=i,a=i+n.length;e.push({node:t,start:r,end:a}),o+=n,i+=n.length}else if(r(t)){const n=t.getChildren(),s=t.getType();for(let t=0;t<n.length;t++)a(n[t],c+1);if("paragraph"===s&&1===c){const n=t.getParent();if(n&&r(n)){const r=n.getChildren();if(r.indexOf(t)<r.length-1){const t=i,n=i+1;e.push({node:null,start:t,end:n,isParagraphBreak:!0}),o+="\n",i+=1}}}}};return a(t),{combinedText:o,nodeMap:e}}catch(t){return h.catchError("Error collecting text content:",t),{combinedText:"",nodeMap:[]}}}findTextInEditor(t,n){try{const r=[];return t.getEditorState().read(()=>{const t=e(),{combinedText:o}=this.collectTextContent(t),i=this.kmpSearch(o,n);for(const t of i)r.push({start:t,end:t+n.length})}),r}catch(t){return h.catchError("Error finding text in editor:",t),[]}}findTextInContainer(t,n,r){try{const o=[],i=document.getElementById(r);return i?(t.getEditorState().read(()=>{const r=e(),{combinedText:a,nodeMap:c}=this.collectTextContent(r),s=this.getContainerTextContent(i,t,c,a);if(!s)return;const d=this.kmpSearch(s.text,n);for(const t of d)o.push({start:s.startOffset+t,end:s.startOffset+t+n.length})}),o):o}catch(t){return h.catchError("Error finding text in container:",t),[]}}getContainerTextContent(t,e,n,r){try{let o=-1,i=-1;for(let r=0;r<n.length;r++){const a=n[r];if(!a.isParagraphBreak&&a.node)try{const n=a.node.getKey(),r=e.getElementByKey(n);r&&t.contains(r)&&(-1===o&&(o=a.start),i=a.end)}catch(t){continue}}if(-1===o||-1===i)return null;return{text:r.substring(o,i),startOffset:o}}catch(t){return null}}filterNodesInContainer(t,e,n){try{const r=[];for(const o of t)try{const t=o.node.getKey(),i=n.getElementByKey(t);i&&e.contains(i)&&r.push(o)}catch(t){continue}return r}catch(t){return[]}}collectTextNodes(t){try{const e=[];let o=0;const i=t=>{if(n(t)){const n=t.getTextContent();e.push({node:t,text:n,textOffset:o}),o+=n.length}else if(r(t)){const e=t.getChildren();for(const t of e)i(t)}};return i(t),e}catch(t){return[]}}getTextNodesInRange(t,e,n,r){try{const o=[],{combinedText:i,nodeMap:a}=this.collectTextContent(t);for(const t of a){if(t.isParagraphBreak||!t.node)continue;const i=t.node.getParent();if((!l(i)||!r||i.__annotationId!==r)&&(t.end>e&&t.start<n)){const r=Math.max(0,e-t.start),i=Math.min(t.node.getTextContent().length,n-t.start),a=r>0||i<t.node.getTextContent().length;o.push({node:t.node,startOffset:r,endOffset:i,isPartial:a})}}return o}catch(t){return h.catchError("Error getting text nodes in range:",t),[]}}isRangeAlreadyWrapped(t,e,o,i){try{let a=0,c=!1;const s=t=>{if(l(t)){if(t.__annotationId===i){const n=t.getTextContent(),r=a,i=a+n.length;if(r<=e&&i>=o)return c=!0,!0}const n=t.getTextContent();a+=n.length}else if(n(t)){const e=t.getTextContent();a+=e.length}else if(r(t)){const e=t.getChildren();for(const t of e)if(s(t))return!0;if("paragraph"===t.getType()){const e=t.getParent();if(e&&r(e)){const n=e.getChildren();n.indexOf(t)<n.length-1&&(a+=1)}}}return!1};return s(t),c}catch(t){return!0}}collectTextNodesInRange(t,e){try{const o=[];let i=0;const a=t=>{const c=i;if(n(t)){const n=t.getTextContent(),r=i+n.length;r>=e.start&&c<=e.end&&o.push({node:t,text:n,start:i,end:r}),i=r}else if(r(t)){const e=t.getChildren();for(const t of e)a(t)}};return a(t),o}catch(t){return[]}}getContainerRange(t,e){try{const n=t.getRootElement();if(!n||!n.contains(e))return null;const r=e.textContent||"",o=(n.textContent||"").indexOf(r);return-1===o?null:{start:o,end:o+r.length}}catch(t){return h.catchError("Error getting container range:",t),null}}kmpSearch(t,e,n=0,r){try{const o=[];if(!e||!t||e.length>t.length)return o;const i=this.computeKMPTable(e);let a=0,c=0;for(;a<t.length;)if(e[c]===t[a]&&(a++,c++),c===e.length){if(o.push(n+a-c),c=i[c-1],r&&o.length>=r)break}else a<t.length&&e[c]!==t[a]&&(0!==c?c=i[c-1]:a++);return o}catch(t){return[]}}computeKMPTable(t){try{const e=new Array(t.length).fill(0);let n=0,r=1;for(;r<t.length;)t[r]===t[n]?(n++,e[r]=n,r++):0!==n?n=e[n-1]:(e[r]=0,r++);return e}catch(t){return[]}}findParentContainerWithId(t,e){try{const n=t.anchor.getNode(),r=e.getElementByKey(n.getKey());if(!r)return null;let o=r;for(;o&&o!==document.body;){if(o.id)return o;o=o.parentElement}return null}catch(t){return null}}highlightTextWithVeltComment(t,n,r){try{const{targetTextNodeId:o}=r,i=r?.occurrence||1;if(r.annotationId){const e=this.plugin.storeService.annotationStore.get(r.annotationId);if(e){const n=e.annotation.context?.textEditorConfig,o=r.originalAnnotation?.context?.textEditorConfig;n&&o&&(n.text!==o.text||n.occurrence!==o.occurrence||n.targetTextNodeId!==o.targetTextNodeId)&&this.removeVeltCommentFromEditor(t,r.annotationId)}}if(!n||0===n.length)return;if(o){const t=document.getElementById(o);if(t){const e=t.closest("[data-velt-location-id]")?.getAttribute("data-velt-location-id");if(e!==r.originalAnnotation?.location?.id)return}}let a=[];if(a=o?this.findTextInDomElement(t,n,o,i):this.findTextInDocument(t,n,i),0===a.length)return;const c=a[Math.min(i,a.length)-1];if(c&&r.annotationId){if(t.getEditorState().read(()=>{const t=e();return this.isRangeAlreadyWrapped(t,c.start,c.end,r.annotationId)}))return;this.plugin.storeService.annotationStore.set(r.annotationId,{annotation:r.originalAnnotation||{annotationId:r.annotationId,multiThreadAnnotationId:r.multiThreadAnnotationId,text:n},pluginReference:{node:document.createElement("velt-comment-text"),position:{from:c.start,to:c.end}}}),this.setVeltComment(t,r.annotationId,r.multiThreadAnnotationId,c.start,c.end)}}catch(t){h.catchError("Error highlighting text with Velt comment:",t)}}findTextInDomElement(t,n,r,o){try{const i=[],a=document.getElementById(r);return a?(t.getEditorState().read(()=>{const r=e(),c=this.filterNodesInContainer(this.collectTextNodes(r),a,t);if(0===c.length)return;const s=c[0].textOffset,d=c.map(t=>t.text).join(""),l=this.kmpSearch(d,n,0,o);for(const t of l)i.push({start:s+t,end:s+t+n.length})}),i):i}catch(t){return[]}}findTextInDocument(t,n,r){try{const o=[];return t.getEditorState().read(()=>{const t=e(),{combinedText:i}=this.collectTextContent(t),a=this.kmpSearch(i,n,0,r);for(const t of a)o.push({start:t,end:t+n.length})}),o}catch(t){return h.catchError("Error finding text in document:",t),[]}}setVeltComment(t,n,r,o,i){try{t.update(()=>{const t=e();t.selectStart();const a=this.getTextNodesInRange(t,o,i,n);if(0===a.length)return;const c=d(n,r);if(1===a.length){const{node:t,startOffset:e,endOffset:n,isPartial:r}=a[0];r?this.wrapPartialTextNode(t,e,n,c):this.wrapEntireTextNode(t,c)}else this.wrapMultipleTextNodesWithFormatting(a,c)})}catch(t){h.catchError("Error setting Velt comment:",t)}}wrapPartialTextNode(t,e,n,r){try{const o=t.getWritable(),i=o.getTextContent(),c=i.substring(0,e),s=i.substring(e,n),d=i.substring(n),l=a(s);if(l.setFormat(o.getFormat()),l.setStyle(o.getStyle()),r.append(l),c){const t=a(c);t.setFormat(o.getFormat()),t.setStyle(o.getStyle()),o.insertBefore(t)}if(o.insertBefore(r),d){const t=a(d);t.setFormat(o.getFormat()),t.setStyle(o.getStyle()),o.insertBefore(t)}o.remove()}catch(t){}}wrapEntireTextNode(t,e){try{const n=t.getWritable();e.append(n);const r=n.getParent();if(r){const t=r.getWritable(),o=n.getIndexWithinParent();t.splice(o,1,[e])}}catch(t){}}wrapMultipleTextNodesWithFormatting(t,e){try{const n=new Map;for(const e of t){const t=this.findParagraphParent(e.node),r=t?t.getKey():"no-paragraph";n.has(r)||n.set(r,[]),n.get(r).push(e)}let r=!0;for(const[,t]of n.entries()){const n=r?e:d(e.__annotationId,e.__multiThreadAnnotationId);if(1===t.length){const e=t[0],{node:r,startOffset:o,endOffset:i,isPartial:a}=e;a?this.wrapPartialTextNode(r,o,i,n):this.wrapEntireTextNode(r,n)}else this.wrapMultipleNodesInSameParagraph(t,n);r=!1}}catch(t){h.catchError("Error in wrapMultipleTextNodesWithFormatting:",t)}}findParagraphParent(t){let e=t.getParent();for(;e;){if(r(e)&&"paragraph"===e.getType())return e;e=e.getParent()}return null}wrapMultipleNodesInSameParagraph(t,e){try{const n=[...t].sort((t,e)=>t.node.getIndexWithinParent()-e.node.getIndexWithinParent()),r=n[0],o=n[n.length-1],i=r.node.getWritable(),c=i.getParent();if(!c)return;const s=c.getWritable();let d=i.getIndexWithinParent();if(r.isPartial&&r.startOffset>0){const t=r.node.getTextContent().substring(0,r.startOffset),n=r.node.getTextContent().substring(r.startOffset),o=a(t);o.setFormat(i.getFormat()),o.setStyle(i.getStyle());const c=a(n);c.setFormat(i.getFormat()),c.setStyle(i.getStyle()),e.append(c),s.splice(d,1,[o,e])}else e.append(i),s.splice(d,1,[e]);for(let t=1;t<n.length-1;t++){const r=n[t].node.getWritable();e.append(r)}if(n.length>1){const t=o.node.getWritable();if(o.isPartial&&o.endOffset<o.node.getTextContent().length){const n=o.node.getTextContent().substring(0,o.endOffset),r=o.node.getTextContent().substring(o.endOffset),i=a(n);i.setFormat(t.getFormat()),i.setStyle(t.getStyle());const c=a(r);c.setFormat(t.getFormat()),c.setStyle(t.getStyle()),e.append(i),t.replace(c)}else e.append(t)}}catch(t){h.catchError("Error in wrapMultipleNodesInSameParagraph:",t)}}removeVeltCommentFromEditor(t,n){try{if(!t||!n)throw new Error("Invalid input: editor and annotationId are required");let o=!1;return t.update(()=>{const t=e(),i=[];!function t(e){if(l(e))e.__annotationId===n&&i.push(e);else if(r(e)){const n=e.getChildren();for(const e of n)t(e)}}(t);for(const t of i)try{const e=t.getWritable(),n=e.getChildren();if(n.length>0){const t=n[0];e.replace(t);let r=t;for(let t=1;t<n.length;t++)r.insertAfter(n[t]),r=n[t]}else{const t=e.getTextContent();if(t){const n=a(t);e.replace(n)}else e.remove()}o=!0}catch(t){h.catchError("Error removing individual comment node:",t)}}),o&&this.plugin.storeService.annotationStore.remove(n),o}catch(t){return!1}}}u.getEditorId=t=>{try{return t&&t._config?t._config.namespace:null}catch(t){return h.catchError("Error finding editor ID:",t),null}};class f{constructor(t){this.isSubscribed=!1,this.globalStore={editor:null,comments:[],filteredComments:[],selectedCommentsMap:new Map},this.globalAnnotationStore=new Map,this.annotationStore={get:t=>this.globalAnnotationStore.get(t),set:(t,e)=>{this.globalAnnotationStore.set(t,e)},getAll:()=>new Map(this.globalAnnotationStore),clear:()=>{this.globalAnnotationStore.clear()},remove:t=>{this.globalAnnotationStore.delete(t)},hasAnnotationTextChanged:(t,e)=>{try{const n=this.globalAnnotationStore.get(t);if(!n||!e)return!1;const r=n.annotation.text,o=e.context?.textEditorConfig?.text;return r!==o}catch(t){return!1}}},this.updateGlobalStore=t=>{try{const e=t?.comments||this.globalStore?.comments||[],n=t?.selectedCommentsMap||this.globalStore?.selectedCommentsMap||new Map,r=this.globalStore?.filteredComments||[],o=e.filter(t=>"terminal"!==t?.status?.type||n?.get(t?.annotationId)),i=new Set(o.map(t=>t?.annotationId));r.map(t=>t?.annotationId).filter(t=>t&&!i.has(t)).forEach(t=>{this.globalStore?.editor&&t&&this.plugin.editorService.removeVeltCommentFromEditor(this.globalStore?.editor,t)}),this.globalStore={...this.globalStore,...t,filteredComments:o},this.highlightCommentsFromGlobalStore()}catch(t){h.catchError("Error updating global store:",t)}},this.highlightCommentsFromGlobalStore=()=>{try{const{editor:t,filteredComments:e}=this.globalStore;if(!t||!e)return;for(const n of e){const e=n?.context?.textEditorConfig?.text,r=n?.context?.textEditorConfig?.occurrence;e&&this.plugin.editorService.highlightTextWithVeltComment(t,e,{annotationId:n?.annotationId,multiThreadAnnotationId:n?.multiThreadAnnotationId,occurrence:r,targetTextNodeId:n?.context?.textEditorConfig?.targetTextNodeId,originalAnnotation:n})}}catch(t){h.catchError("Error highlighting comments from global store:",t)}},this.plugin=t;try{h.subscribeToSelectedCommentsMap(this.plugin.editorId,t=>{try{this.updateGlobalStore({selectedCommentsMap:t})}catch(t){h.catchError("Error updating global store:",t)}})}catch(t){h.catchError("Error subscribing to selected comments map:",t)}}}f.storeInstanceMap=new Map;const m=new Map;class p{constructor(t,e){this.editorId=t,this.editor=e,this.editorService=new u(this),e&&this.editorService.initializeTransactionListener(e),this.storeService=new f(this)}static getInstance(t){let{editorId:e,editor:n}=t||{};return e||(e="default"),m.has(e)||m.set(e,new p(e,n)),m.get(e)}}function x(t){const n=function(t){const e=t._createEditorArgs,n={namespace:`shadow-editor-${Date.now()}`,nodes:e?.nodes||[],theme:{},onError:t=>{},editable:!1};return c(n)}(t);try{const o=t.getEditorState();n.setEditorState(o),n.update(()=>{!function(t){const e=[];function n(t){if(l(t))e.push(t);else if(r(t)){t.getChildren().forEach(n)}}n(t),e.forEach(t=>{try{const e=t.getParent();if(!e)return;const n=t.getChildren(),r=t.getIndexWithinParent();t.remove(),n.length>0&&e.splice(r,0,n)}catch(t){}}),t.markDirty()}(e())},{discrete:!0});const i=n.getEditorState().toJSON();return JSON.stringify(i)}catch(e){return function(t){return t.getEditorState().read(()=>{const e=C(T(t.getEditorState().toJSON()));return JSON.stringify(e)})}(t)}}function T(t){if(!t||"object"!=typeof t)return t;if(Array.isArray(t)){const e=[];for(const n of t)if(n&&"comment"===n.type){if(n.children&&Array.isArray(n.children))for(const t of n.children)e.push(T(t))}else e.push(T(n));return e}const e={...t};if("comment"===e.type)return e.children&&Array.isArray(e.children)?e.children.map(t=>T(t)):null;for(const[t,n]of Object.entries(e))("children"===t&&Array.isArray(n)||"object"==typeof n&&null!==n)&&(e[t]=T(n));return e}function C(t){if(!t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>C(t));const e={...t};if(e.children&&Array.isArray(e.children)){const t=e.children.map(t=>C(t));e.children=function(t){if(!Array.isArray(t)||t.length<=1)return t;const e=[];let n=0;for(;n<t.length;){const r=t[n];if(r&&"text"===r.type){let o=r.text||"",i=n+1;for(;i<t.length;){const e=t[i];if(!e||"text"!==e.type||!y(r,e))break;o+=e.text||"",i++}e.push({...r,text:o}),n=i}else e.push(r),n++}return e}(t)}for(const[t,n]of Object.entries(e))"children"!==t&&"object"==typeof n&&null!==n&&(e[t]=C(n));return e}function y(t,e){return t.format===e.format&&t.style===e.style&&t.mode===e.mode&&t.detail===e.detail}const E=t=>{try{return x(t)}catch(n){return t.getEditorState().read(()=>{const t=e();return JSON.stringify({root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:t.getTextContent(),type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}})})}};function I(t,n){try{const e=JSON.parse(n);t.setEditorState(t.parseEditorState(e))}catch(n){t.update(()=>{e().clear()})}}const S=async(t,e)=>{try{return b({editor:t,context:e?.context})}catch(t){h.catchError("Error triggering add comment",t)}},b=async({editorId:t,editor:e,context:n})=>{try{const r=h.getCommentElement();if(!r)return;await new Promise(t=>setTimeout(t,500)),t||(t=u.getEditorId(e)??void 0);const o=p.getInstance({editorId:t,editor:e}),i=o.editorService.getSelectedText(e);if(!i)return;let a;const c=o.editorService.findOccurrenceIndex(e,i,null);if(!c)return;let s,d={};n&&"object"==typeof n&&(d={...d,...n}),d.textEditorConfig={text:i,occurrence:c,targetTextNodeId:a},t&&(d.textEditorConfig.editorId=t),r?.addManualComment({context:d,location:s})?.then(t=>{})}catch(t){h.catchError("Error adding lexical-velt-comments",t)}};function N(t,e,n){try{A({editor:t,editorId:n,commentAnnotations:e})}catch(t){h.catchError("Error highlighting lexical-velt-comments",t)}}const A=({editor:t,editorId:e,commentAnnotations:n})=>{try{if(t){if(e||(e=u.getEditorId(t)??void 0),!Array.isArray(n))return;n=JSON.parse(JSON.stringify(n));const r=n?.filter(t=>!!t.context?.textEditorConfig&&t.context?.textEditorConfig?.editorId===e)||[];p.getInstance({editorId:e,editor:t}).storeService.updateGlobalStore({editor:t,comments:r}),h.subscribeToSelectedAnnotationsMapSingleton()}}catch(t){h.catchError("Error highlighting lexical-velt-comments",t)}};export{d as $createCommentNode,l as $isCommentNode,s as CommentNode,b as addComment,I as deserializeCleanState,E as exportCleanEditorContent,N as highlightComments,A as renderComments,S as triggerAddComment};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|