@qwanyx/ai-editor 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.
Files changed (46) hide show
  1. package/dist/components/AIEditor.d.ts +15 -0
  2. package/dist/components/AIEditor.d.ts.map +1 -0
  3. package/dist/components/AIEditor.js +154 -0
  4. package/dist/components/AIToolbar.d.ts +22 -0
  5. package/dist/components/AIToolbar.d.ts.map +1 -0
  6. package/dist/components/AIToolbar.js +10 -0
  7. package/dist/components/EditorToolbar.d.ts +8 -0
  8. package/dist/components/EditorToolbar.d.ts.map +1 -0
  9. package/dist/components/EditorToolbar.js +241 -0
  10. package/dist/components/MarkdownPreview.d.ts +7 -0
  11. package/dist/components/MarkdownPreview.d.ts.map +1 -0
  12. package/dist/components/MarkdownPreview.js +40 -0
  13. package/dist/components/PromptModal.d.ts +9 -0
  14. package/dist/components/PromptModal.d.ts.map +1 -0
  15. package/dist/components/PromptModal.js +38 -0
  16. package/dist/components/RichTextEditor.d.ts +15 -0
  17. package/dist/components/RichTextEditor.d.ts.map +1 -0
  18. package/dist/components/RichTextEditor.js +253 -0
  19. package/dist/hooks/useAIEditor.d.ts +15 -0
  20. package/dist/hooks/useAIEditor.d.ts.map +1 -0
  21. package/dist/hooks/useAIEditor.js +46 -0
  22. package/dist/hooks/useSelection.d.ts +12 -0
  23. package/dist/hooks/useSelection.d.ts.map +1 -0
  24. package/dist/hooks/useSelection.js +45 -0
  25. package/dist/index.d.ts +15 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +14 -0
  28. package/dist/nodes/ImageLinkNode.d.ts +48 -0
  29. package/dist/nodes/ImageLinkNode.d.ts.map +1 -0
  30. package/dist/nodes/ImageLinkNode.js +157 -0
  31. package/dist/nodes/ImageNode.d.ts +62 -0
  32. package/dist/nodes/ImageNode.d.ts.map +1 -0
  33. package/dist/nodes/ImageNode.js +487 -0
  34. package/dist/nodes/LinkNode.d.ts +33 -0
  35. package/dist/nodes/LinkNode.d.ts.map +1 -0
  36. package/dist/nodes/LinkNode.js +108 -0
  37. package/dist/plugins/ImageLinkPlugin.d.ts +2 -0
  38. package/dist/plugins/ImageLinkPlugin.d.ts.map +1 -0
  39. package/dist/plugins/ImageLinkPlugin.js +112 -0
  40. package/dist/plugins/InsertObjectPlugin.d.ts +4 -0
  41. package/dist/plugins/InsertObjectPlugin.d.ts.map +1 -0
  42. package/dist/plugins/InsertObjectPlugin.js +464 -0
  43. package/dist/plugins/LinkPlugin.d.ts +2 -0
  44. package/dist/plugins/LinkPlugin.d.ts.map +1 -0
  45. package/dist/plugins/LinkPlugin.js +45 -0
  46. package/package.json +45 -0
@@ -0,0 +1,464 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useState, useCallback } from 'react';
4
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
5
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND, createCommand, } from 'lexical';
6
+ import { $createImageNode, $isImageNode } from '../nodes/ImageNode';
7
+ import { $wrapSelectionInImageLink, $isImageLinkNode } from '../nodes/ImageLinkNode';
8
+ import { $wrapSelectionInSimpleLink, $isSimpleLinkNode } from '../nodes/LinkNode';
9
+ // Command to open the insert object dialog
10
+ export const INSERT_OBJECT_COMMAND = createCommand('INSERT_OBJECT_COMMAND');
11
+ function InsertDialog({ isOpen, onClose, onInsert, initialData, isEditing }) {
12
+ const [objectType, setObjectType] = useState('image');
13
+ const [url, setUrl] = useState('');
14
+ const [altText, setAltText] = useState('');
15
+ const [copyright, setCopyright] = useState('');
16
+ const [photographer, setPhotographer] = useState('');
17
+ const [comment, setComment] = useState('');
18
+ // Pre-fill form when editing
19
+ useEffect(() => {
20
+ if (isOpen && initialData) {
21
+ setObjectType(initialData.type);
22
+ setUrl(initialData.url);
23
+ setAltText(initialData.altText);
24
+ setCopyright(initialData.copyright || '');
25
+ setPhotographer(initialData.photographer || '');
26
+ setComment(initialData.comment || '');
27
+ }
28
+ else if (isOpen && !initialData) {
29
+ setObjectType('image');
30
+ setUrl('');
31
+ setAltText('');
32
+ setCopyright('');
33
+ setPhotographer('');
34
+ setComment('');
35
+ }
36
+ }, [isOpen, initialData]);
37
+ const handleSubmit = (e) => {
38
+ e.preventDefault();
39
+ if (url.trim()) {
40
+ onInsert(objectType, {
41
+ url: url.trim(),
42
+ altText: altText.trim(),
43
+ copyright: copyright.trim(),
44
+ photographer: photographer.trim(),
45
+ comment: comment.trim(),
46
+ });
47
+ setUrl('');
48
+ setAltText('');
49
+ setCopyright('');
50
+ setPhotographer('');
51
+ setComment('');
52
+ onClose();
53
+ }
54
+ };
55
+ const handleKeyDown = (e) => {
56
+ if (e.key === 'Escape') {
57
+ onClose();
58
+ }
59
+ };
60
+ if (!isOpen)
61
+ return null;
62
+ return (_jsx("div", { style: {
63
+ position: 'fixed',
64
+ inset: 0,
65
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
66
+ display: 'flex',
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ zIndex: 100000,
70
+ }, onClick: onClose, onKeyDown: handleKeyDown, children: _jsxs("div", { style: {
71
+ backgroundColor: 'white',
72
+ borderRadius: '8px',
73
+ padding: '24px',
74
+ minWidth: '400px',
75
+ maxWidth: '500px',
76
+ boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
77
+ }, onClick: (e) => e.stopPropagation(), children: [_jsx("h3", { style: { margin: '0 0 16px 0', fontSize: '18px', fontWeight: 600, color: '#111827' }, children: isEditing
78
+ ? (initialData?.type === 'image' ? "Modifier l'image" : initialData?.type === 'link' ? 'Modifier le lien' : 'Modifier le lien image')
79
+ : 'Insérer un objet' }), _jsxs("form", { onSubmit: handleSubmit, children: [!isEditing && (_jsxs("div", { style: { marginBottom: '16px' }, children: [_jsx("label", { style: { display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: "Type" }), _jsxs("div", { style: { display: 'flex', gap: '8px' }, children: [_jsxs("button", { type: "button", onClick: () => setObjectType('image'), style: {
80
+ flex: 1,
81
+ padding: '8px 16px',
82
+ border: '1px solid',
83
+ borderColor: objectType === 'image' ? '#3b82f6' : '#d1d5db',
84
+ backgroundColor: objectType === 'image' ? '#eff6ff' : 'white',
85
+ color: objectType === 'image' ? '#1d4ed8' : '#374151',
86
+ borderRadius: '6px',
87
+ cursor: 'pointer',
88
+ fontSize: '14px',
89
+ display: 'flex',
90
+ alignItems: 'center',
91
+ justifyContent: 'center',
92
+ gap: '6px',
93
+ }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px' }, children: "image" }), "Image"] }), _jsxs("button", { type: "button", onClick: () => setObjectType('image-link'), style: {
94
+ flex: 1,
95
+ padding: '8px 16px',
96
+ border: '1px solid',
97
+ borderColor: objectType === 'image-link' ? '#3b82f6' : '#d1d5db',
98
+ backgroundColor: objectType === 'image-link' ? '#eff6ff' : 'white',
99
+ color: objectType === 'image-link' ? '#1d4ed8' : '#374151',
100
+ borderRadius: '6px',
101
+ cursor: 'pointer',
102
+ fontSize: '14px',
103
+ display: 'flex',
104
+ alignItems: 'center',
105
+ justifyContent: 'center',
106
+ gap: '6px',
107
+ }, title: "Lien vers une image (s'ouvre en plein \u00E9cran)", children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px' }, children: "photo_library" }), "Lien image"] }), _jsxs("button", { type: "button", onClick: () => setObjectType('link'), style: {
108
+ flex: 1,
109
+ padding: '8px 16px',
110
+ border: '1px solid',
111
+ borderColor: objectType === 'link' ? '#3b82f6' : '#d1d5db',
112
+ backgroundColor: objectType === 'link' ? '#eff6ff' : 'white',
113
+ color: objectType === 'link' ? '#1d4ed8' : '#374151',
114
+ borderRadius: '6px',
115
+ cursor: 'pointer',
116
+ fontSize: '14px',
117
+ display: 'flex',
118
+ alignItems: 'center',
119
+ justifyContent: 'center',
120
+ gap: '6px',
121
+ }, title: "Lien internet (s'ouvre dans un nouvel onglet)", children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px' }, children: "link" }), "Lien"] })] })] })), _jsxs("div", { style: { marginBottom: '16px' }, children: [_jsx("label", { style: { display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: objectType === 'link' ? "URL du lien" : objectType === 'image-link' ? "URL de l'image à afficher" : "URL de l'image" }), _jsx("input", { type: "url", value: url, onChange: (e) => setUrl(e.target.value), placeholder: "https://example.com/image.jpg", autoFocus: true, style: {
122
+ width: '100%',
123
+ padding: '10px 12px',
124
+ border: '1px solid #d1d5db',
125
+ borderRadius: '6px',
126
+ fontSize: '14px',
127
+ outline: 'none',
128
+ boxSizing: 'border-box',
129
+ }, onFocus: (e) => e.target.style.borderColor = '#3b82f6', onBlur: (e) => e.target.style.borderColor = '#d1d5db' })] }), _jsxs("div", { style: { marginBottom: '16px' }, children: [_jsx("label", { style: { display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: objectType === 'link' ? 'Titre (tooltip, optionnel)' : 'Texte alternatif (optionnel)' }), _jsx("input", { type: "text", value: altText, onChange: (e) => setAltText(e.target.value), placeholder: objectType === 'link' ? 'Texte affiché au survol' : 'Description de l\'image', style: {
130
+ width: '100%',
131
+ padding: '10px 12px',
132
+ border: '1px solid #d1d5db',
133
+ borderRadius: '6px',
134
+ fontSize: '14px',
135
+ outline: 'none',
136
+ boxSizing: 'border-box',
137
+ }, onFocus: (e) => e.target.style.borderColor = '#3b82f6', onBlur: (e) => e.target.style.borderColor = '#d1d5db' })] }), objectType === 'image-link' && (_jsxs(_Fragment, { children: [_jsxs("div", { style: { marginBottom: '16px' }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px', color: '#6b7280' }, children: "camera_alt" }), "Photographe"] }), _jsx("input", { type: "text", value: photographer, onChange: (e) => setPhotographer(e.target.value), placeholder: "Nom du photographe", style: {
138
+ width: '100%',
139
+ padding: '10px 12px',
140
+ border: '1px solid #d1d5db',
141
+ borderRadius: '6px',
142
+ fontSize: '14px',
143
+ outline: 'none',
144
+ boxSizing: 'border-box',
145
+ }, onFocus: (e) => e.target.style.borderColor = '#3b82f6', onBlur: (e) => e.target.style.borderColor = '#d1d5db' })] }), _jsxs("div", { style: { marginBottom: '16px' }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px', color: '#6b7280' }, children: "copyright" }), "Copyright"] }), _jsx("input", { type: "text", value: copyright, onChange: (e) => setCopyright(e.target.value), placeholder: "\u00A9 2024 Nom", style: {
146
+ width: '100%',
147
+ padding: '10px 12px',
148
+ border: '1px solid #d1d5db',
149
+ borderRadius: '6px',
150
+ fontSize: '14px',
151
+ outline: 'none',
152
+ boxSizing: 'border-box',
153
+ }, onFocus: (e) => e.target.style.borderColor = '#3b82f6', onBlur: (e) => e.target.style.borderColor = '#d1d5db' })] }), _jsxs("div", { style: { marginBottom: '16px' }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px', color: '#6b7280' }, children: "chat" }), "Commentaire (tooltip)"] }), _jsx("textarea", { value: comment, onChange: (e) => setComment(e.target.value), placeholder: "Commentaire affich\u00E9 au survol", rows: 2, style: {
154
+ width: '100%',
155
+ padding: '10px 12px',
156
+ border: '1px solid #d1d5db',
157
+ borderRadius: '6px',
158
+ fontSize: '14px',
159
+ outline: 'none',
160
+ boxSizing: 'border-box',
161
+ resize: 'vertical',
162
+ fontFamily: 'inherit',
163
+ }, onFocus: (e) => e.target.style.borderColor = '#3b82f6', onBlur: (e) => e.target.style.borderColor = '#d1d5db' })] })] })), objectType === 'image-link' && (_jsxs("div", { style: {
164
+ marginBottom: '16px',
165
+ padding: '12px',
166
+ backgroundColor: '#f3e8ff',
167
+ borderRadius: '6px',
168
+ fontSize: '13px',
169
+ color: '#6b21a8',
170
+ display: 'flex',
171
+ alignItems: 'center',
172
+ gap: '8px',
173
+ }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px' }, children: "info" }), "Le texte s\u00E9lectionn\u00E9 deviendra un lien. Un clic ouvrira l'image en plein \u00E9cran."] })), objectType === 'link' && (_jsxs("div", { style: {
174
+ marginBottom: '16px',
175
+ padding: '12px',
176
+ backgroundColor: '#dbeafe',
177
+ borderRadius: '6px',
178
+ fontSize: '13px',
179
+ color: '#1e40af',
180
+ display: 'flex',
181
+ alignItems: 'center',
182
+ gap: '8px',
183
+ }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '18px' }, children: "info" }), "Le texte s\u00E9lectionn\u00E9 deviendra un lien. Ctrl+Clic pour ouvrir dans un nouvel onglet."] })), url && (objectType === 'image' || objectType === 'image-link') && (_jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("label", { style: { display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: '#374151' }, children: "Aper\u00E7u" }), _jsx("div", { style: {
184
+ border: '1px solid #e5e7eb',
185
+ borderRadius: '6px',
186
+ padding: '8px',
187
+ backgroundColor: '#f9fafb',
188
+ maxHeight: '150px',
189
+ overflow: 'hidden',
190
+ }, children: _jsx("img", { src: url, alt: altText || 'Preview', style: { maxWidth: '100%', maxHeight: '130px', display: 'block', margin: '0 auto' }, onError: (e) => {
191
+ e.target.style.display = 'none';
192
+ } }) })] })), _jsxs("div", { style: { display: 'flex', gap: '12px', justifyContent: 'flex-end' }, children: [_jsx("button", { type: "button", onClick: onClose, style: {
193
+ padding: '10px 20px',
194
+ border: '1px solid #d1d5db',
195
+ backgroundColor: 'white',
196
+ color: '#374151',
197
+ borderRadius: '6px',
198
+ cursor: 'pointer',
199
+ fontSize: '14px',
200
+ fontWeight: 500,
201
+ }, children: "Annuler" }), _jsx("button", { type: "submit", disabled: !url.trim(), style: {
202
+ padding: '10px 20px',
203
+ border: 'none',
204
+ backgroundColor: url.trim() ? '#3b82f6' : '#9ca3af',
205
+ color: 'white',
206
+ borderRadius: '6px',
207
+ cursor: url.trim() ? 'pointer' : 'not-allowed',
208
+ fontSize: '14px',
209
+ fontWeight: 500,
210
+ }, children: "Ins\u00E9rer" })] })] })] }) }));
211
+ }
212
+ export function InsertObjectPlugin() {
213
+ const [editor] = useLexicalComposerContext();
214
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
215
+ const [editingImageLinkNode, setEditingImageLinkNode] = useState(null);
216
+ const [editingImageNode, setEditingImageNode] = useState(null);
217
+ const [editingLinkNode, setEditingLinkNode] = useState(null);
218
+ const [initialData, setInitialData] = useState(null);
219
+ const [dialogKey, setDialogKey] = useState(0);
220
+ // Find ImageLinkNode at current selection
221
+ const getImageLinkNodeAtSelection = useCallback(() => {
222
+ let foundNode = null;
223
+ editor.getEditorState().read(() => {
224
+ const selection = $getSelection();
225
+ if ($isRangeSelection(selection)) {
226
+ const anchorNode = selection.anchor.getNode();
227
+ let node = anchorNode;
228
+ while (node) {
229
+ if ($isImageLinkNode(node)) {
230
+ foundNode = node;
231
+ break;
232
+ }
233
+ const parent = node.getParent();
234
+ if (!parent)
235
+ break;
236
+ node = parent;
237
+ }
238
+ }
239
+ });
240
+ return foundNode;
241
+ }, [editor]);
242
+ // Find ImageNode at current selection (for DecoratorNodes we check siblings)
243
+ const getImageNodeAtSelection = useCallback(() => {
244
+ let foundNode = null;
245
+ editor.getEditorState().read(() => {
246
+ const selection = $getSelection();
247
+ if ($isRangeSelection(selection)) {
248
+ const nodes = selection.getNodes();
249
+ for (const node of nodes) {
250
+ if ($isImageNode(node)) {
251
+ foundNode = node;
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ });
257
+ return foundNode;
258
+ }, [editor]);
259
+ // Find SimpleLinkNode at current selection
260
+ const getSimpleLinkNodeAtSelection = useCallback(() => {
261
+ let foundNode = null;
262
+ editor.getEditorState().read(() => {
263
+ const selection = $getSelection();
264
+ if ($isRangeSelection(selection)) {
265
+ const anchorNode = selection.anchor.getNode();
266
+ let node = anchorNode;
267
+ while (node) {
268
+ if ($isSimpleLinkNode(node)) {
269
+ foundNode = node;
270
+ break;
271
+ }
272
+ const parent = node.getParent();
273
+ if (!parent)
274
+ break;
275
+ node = parent;
276
+ }
277
+ }
278
+ });
279
+ return foundNode;
280
+ }, [editor]);
281
+ // Handle Ctrl+K
282
+ useEffect(() => {
283
+ return editor.registerCommand(KEY_DOWN_COMMAND, (event) => {
284
+ if (event.ctrlKey && event.key === 'k') {
285
+ event.preventDefault();
286
+ // Check if we're on an ImageNode first
287
+ const imageNode = getImageNodeAtSelection();
288
+ if (imageNode) {
289
+ setEditingImageNode(imageNode);
290
+ setEditingImageLinkNode(null);
291
+ setInitialData({
292
+ url: imageNode.__src,
293
+ altText: imageNode.__altText || '',
294
+ type: 'image',
295
+ });
296
+ setDialogKey(k => k + 1);
297
+ setIsDialogOpen(true);
298
+ return true;
299
+ }
300
+ // Check if we're inside an ImageLinkNode
301
+ const imageLinkNode = getImageLinkNodeAtSelection();
302
+ if (imageLinkNode) {
303
+ setEditingImageLinkNode(imageLinkNode);
304
+ setEditingImageNode(null);
305
+ setEditingLinkNode(null);
306
+ setInitialData({
307
+ url: imageLinkNode.getUrl(),
308
+ altText: imageLinkNode.getAltText() || '',
309
+ copyright: imageLinkNode.getCopyright() || '',
310
+ photographer: imageLinkNode.getPhotographer() || '',
311
+ comment: imageLinkNode.getComment() || '',
312
+ type: 'image-link',
313
+ });
314
+ setDialogKey(k => k + 1);
315
+ setIsDialogOpen(true);
316
+ return true;
317
+ }
318
+ // Check if we're inside a SimpleLinkNode
319
+ const simpleLinkNode = getSimpleLinkNodeAtSelection();
320
+ if (simpleLinkNode) {
321
+ setEditingLinkNode(simpleLinkNode);
322
+ setEditingImageNode(null);
323
+ setEditingImageLinkNode(null);
324
+ setInitialData({
325
+ url: simpleLinkNode.getUrl(),
326
+ altText: simpleLinkNode.getTitle() || '',
327
+ type: 'link',
328
+ });
329
+ setDialogKey(k => k + 1);
330
+ setIsDialogOpen(true);
331
+ return true;
332
+ }
333
+ // Create new
334
+ setEditingImageLinkNode(null);
335
+ setEditingImageNode(null);
336
+ setEditingLinkNode(null);
337
+ setInitialData(null);
338
+ setDialogKey(k => k + 1);
339
+ setIsDialogOpen(true);
340
+ return true;
341
+ }
342
+ return false;
343
+ }, COMMAND_PRIORITY_HIGH);
344
+ }, [editor, getImageLinkNodeAtSelection, getImageNodeAtSelection, getSimpleLinkNodeAtSelection]);
345
+ // Handle INSERT_OBJECT_COMMAND (from toolbar)
346
+ useEffect(() => {
347
+ return editor.registerCommand(INSERT_OBJECT_COMMAND, () => {
348
+ // Check if we're on an ImageNode first
349
+ const imageNode = getImageNodeAtSelection();
350
+ if (imageNode) {
351
+ setEditingImageNode(imageNode);
352
+ setEditingImageLinkNode(null);
353
+ setInitialData({
354
+ url: imageNode.__src,
355
+ altText: imageNode.__altText || '',
356
+ type: 'image',
357
+ });
358
+ setDialogKey(k => k + 1);
359
+ setIsDialogOpen(true);
360
+ return true;
361
+ }
362
+ const imageLinkNode = getImageLinkNodeAtSelection();
363
+ if (imageLinkNode) {
364
+ setEditingImageLinkNode(imageLinkNode);
365
+ setEditingImageNode(null);
366
+ setEditingLinkNode(null);
367
+ setInitialData({
368
+ url: imageLinkNode.getUrl(),
369
+ altText: imageLinkNode.getAltText() || '',
370
+ copyright: imageLinkNode.getCopyright() || '',
371
+ photographer: imageLinkNode.getPhotographer() || '',
372
+ comment: imageLinkNode.getComment() || '',
373
+ type: 'image-link',
374
+ });
375
+ setDialogKey(k => k + 1);
376
+ setIsDialogOpen(true);
377
+ return true;
378
+ }
379
+ const simpleLinkNode = getSimpleLinkNodeAtSelection();
380
+ if (simpleLinkNode) {
381
+ setEditingLinkNode(simpleLinkNode);
382
+ setEditingImageNode(null);
383
+ setEditingImageLinkNode(null);
384
+ setInitialData({
385
+ url: simpleLinkNode.getUrl(),
386
+ altText: simpleLinkNode.getTitle() || '',
387
+ type: 'link',
388
+ });
389
+ setDialogKey(k => k + 1);
390
+ setIsDialogOpen(true);
391
+ return true;
392
+ }
393
+ setEditingImageLinkNode(null);
394
+ setEditingImageNode(null);
395
+ setEditingLinkNode(null);
396
+ setInitialData(null);
397
+ setDialogKey(k => k + 1);
398
+ setIsDialogOpen(true);
399
+ return true;
400
+ }, COMMAND_PRIORITY_HIGH);
401
+ }, [editor, getImageLinkNodeAtSelection, getImageNodeAtSelection, getSimpleLinkNodeAtSelection]);
402
+ const handleInsert = useCallback((type, data) => {
403
+ if (editingImageNode && type === 'image') {
404
+ // Update existing ImageNode
405
+ editor.update(() => {
406
+ editingImageNode.setSrc(data.url);
407
+ editingImageNode.setAltText(data.altText || '');
408
+ });
409
+ }
410
+ else if (editingImageLinkNode && type === 'image-link') {
411
+ // Update existing ImageLinkNode
412
+ editor.update(() => {
413
+ editingImageLinkNode.setUrl(data.url);
414
+ editingImageLinkNode.setAltText(data.altText || undefined);
415
+ editingImageLinkNode.setCopyright(data.copyright || undefined);
416
+ editingImageLinkNode.setPhotographer(data.photographer || undefined);
417
+ editingImageLinkNode.setComment(data.comment || undefined);
418
+ });
419
+ }
420
+ else if (type === 'image') {
421
+ editor.update(() => {
422
+ const selection = $getSelection();
423
+ if ($isRangeSelection(selection)) {
424
+ const imageNode = $createImageNode({
425
+ src: data.url,
426
+ altText: data.altText || '',
427
+ });
428
+ selection.insertNodes([imageNode]);
429
+ }
430
+ });
431
+ }
432
+ else if (editingLinkNode && type === 'link') {
433
+ // Update existing LinkNode
434
+ editor.update(() => {
435
+ editingLinkNode.setUrl(data.url);
436
+ editingLinkNode.setTitle(data.altText || undefined);
437
+ });
438
+ }
439
+ else if (type === 'image-link') {
440
+ editor.update(() => {
441
+ const selection = $getSelection();
442
+ if ($isRangeSelection(selection) && !selection.isCollapsed()) {
443
+ $wrapSelectionInImageLink(selection, data.url, data.altText || '', data.copyright || '', data.photographer || '', data.comment || '');
444
+ }
445
+ });
446
+ }
447
+ else if (type === 'link') {
448
+ editor.update(() => {
449
+ const selection = $getSelection();
450
+ if ($isRangeSelection(selection) && !selection.isCollapsed()) {
451
+ $wrapSelectionInSimpleLink(selection, data.url, data.altText || undefined);
452
+ }
453
+ });
454
+ }
455
+ }, [editor, editingImageNode, editingImageLinkNode, editingLinkNode]);
456
+ const handleClose = useCallback(() => {
457
+ setIsDialogOpen(false);
458
+ setEditingImageLinkNode(null);
459
+ setEditingImageNode(null);
460
+ setEditingLinkNode(null);
461
+ setInitialData(null);
462
+ }, []);
463
+ return (_jsx(InsertDialog, { isOpen: isDialogOpen, onClose: handleClose, onInsert: handleInsert, initialData: initialData, isEditing: !!(editingImageNode || editingImageLinkNode || editingLinkNode) }, dialogKey));
464
+ }
@@ -0,0 +1,2 @@
1
+ export declare function SimpleLinkPlugin(): null;
2
+ //# sourceMappingURL=LinkPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LinkPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/LinkPlugin.tsx"],"names":[],"mappings":"AAYA,wBAAgB,gBAAgB,SA6C/B"}
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import { useEffect, useCallback } from 'react';
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, CLICK_COMMAND, } from 'lexical';
5
+ import { $isSimpleLinkNode } from '../nodes/LinkNode';
6
+ export function SimpleLinkPlugin() {
7
+ const [editor] = useLexicalComposerContext();
8
+ // Find SimpleLinkNode at current selection
9
+ const getSimpleLinkNodeAtSelection = useCallback(() => {
10
+ let foundNode = null;
11
+ editor.getEditorState().read(() => {
12
+ const selection = $getSelection();
13
+ if ($isRangeSelection(selection)) {
14
+ const anchorNode = selection.anchor.getNode();
15
+ // Check if we're inside a SimpleLinkNode
16
+ let node = anchorNode;
17
+ while (node) {
18
+ if ($isSimpleLinkNode(node)) {
19
+ foundNode = node;
20
+ break;
21
+ }
22
+ const parent = node.getParent();
23
+ if (!parent)
24
+ break;
25
+ node = parent;
26
+ }
27
+ }
28
+ });
29
+ return foundNode;
30
+ }, [editor]);
31
+ // Handle Ctrl+Click on SimpleLinkNode to open in new tab
32
+ useEffect(() => {
33
+ return editor.registerCommand(CLICK_COMMAND, (event) => {
34
+ const linkNode = getSimpleLinkNodeAtSelection();
35
+ if (linkNode && event.ctrlKey) {
36
+ // Ctrl+Click opens in new tab
37
+ event.preventDefault();
38
+ window.open(linkNode.getUrl(), '_blank', 'noopener,noreferrer');
39
+ return true;
40
+ }
41
+ return false;
42
+ }, COMMAND_PRIORITY_HIGH);
43
+ }, [editor, getSimpleLinkNodeAtSelection]);
44
+ return null;
45
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@qwanyx/ai-editor",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered WYSIWYG rich text editor",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": false,
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "dependencies": {
25
+ "@lexical/react": "^0.17.0",
26
+ "@lexical/rich-text": "^0.17.0",
27
+ "@lexical/history": "^0.17.0",
28
+ "@lexical/list": "^0.17.0",
29
+ "@lexical/link": "^0.17.0",
30
+ "@lexical/selection": "^0.17.0",
31
+ "lexical": "^0.17.0",
32
+ "react": "^18.3.1",
33
+ "react-dom": "^18.3.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20",
37
+ "@types/react": "^18",
38
+ "@types/react-dom": "^18",
39
+ "typescript": "^5"
40
+ },
41
+ "peerDependencies": {
42
+ "react": ">=18",
43
+ "react-dom": ">=18"
44
+ }
45
+ }