@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.
- package/dist/components/AIEditor.d.ts +15 -0
- package/dist/components/AIEditor.d.ts.map +1 -0
- package/dist/components/AIEditor.js +154 -0
- package/dist/components/AIToolbar.d.ts +22 -0
- package/dist/components/AIToolbar.d.ts.map +1 -0
- package/dist/components/AIToolbar.js +10 -0
- package/dist/components/EditorToolbar.d.ts +8 -0
- package/dist/components/EditorToolbar.d.ts.map +1 -0
- package/dist/components/EditorToolbar.js +241 -0
- package/dist/components/MarkdownPreview.d.ts +7 -0
- package/dist/components/MarkdownPreview.d.ts.map +1 -0
- package/dist/components/MarkdownPreview.js +40 -0
- package/dist/components/PromptModal.d.ts +9 -0
- package/dist/components/PromptModal.d.ts.map +1 -0
- package/dist/components/PromptModal.js +38 -0
- package/dist/components/RichTextEditor.d.ts +15 -0
- package/dist/components/RichTextEditor.d.ts.map +1 -0
- package/dist/components/RichTextEditor.js +253 -0
- package/dist/hooks/useAIEditor.d.ts +15 -0
- package/dist/hooks/useAIEditor.d.ts.map +1 -0
- package/dist/hooks/useAIEditor.js +46 -0
- package/dist/hooks/useSelection.d.ts +12 -0
- package/dist/hooks/useSelection.d.ts.map +1 -0
- package/dist/hooks/useSelection.js +45 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/nodes/ImageLinkNode.d.ts +48 -0
- package/dist/nodes/ImageLinkNode.d.ts.map +1 -0
- package/dist/nodes/ImageLinkNode.js +157 -0
- package/dist/nodes/ImageNode.d.ts +62 -0
- package/dist/nodes/ImageNode.d.ts.map +1 -0
- package/dist/nodes/ImageNode.js +487 -0
- package/dist/nodes/LinkNode.d.ts +33 -0
- package/dist/nodes/LinkNode.d.ts.map +1 -0
- package/dist/nodes/LinkNode.js +108 -0
- package/dist/plugins/ImageLinkPlugin.d.ts +2 -0
- package/dist/plugins/ImageLinkPlugin.d.ts.map +1 -0
- package/dist/plugins/ImageLinkPlugin.js +112 -0
- package/dist/plugins/InsertObjectPlugin.d.ts +4 -0
- package/dist/plugins/InsertObjectPlugin.d.ts.map +1 -0
- package/dist/plugins/InsertObjectPlugin.js +464 -0
- package/dist/plugins/LinkPlugin.d.ts +2 -0
- package/dist/plugins/LinkPlugin.d.ts.map +1 -0
- package/dist/plugins/LinkPlugin.js +45 -0
- package/package.json +45 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { $applyNodeReplacement, ElementNode, } from 'lexical';
|
|
3
|
+
export class SimpleLinkNode extends ElementNode {
|
|
4
|
+
static getType() {
|
|
5
|
+
return 'simple-link';
|
|
6
|
+
}
|
|
7
|
+
static clone(node) {
|
|
8
|
+
return new SimpleLinkNode(node.__url, node.__title, node.__key);
|
|
9
|
+
}
|
|
10
|
+
constructor(url, title, key) {
|
|
11
|
+
super(key);
|
|
12
|
+
this.__url = url;
|
|
13
|
+
this.__title = title;
|
|
14
|
+
}
|
|
15
|
+
createDOM() {
|
|
16
|
+
const anchor = document.createElement('a');
|
|
17
|
+
anchor.href = this.__url;
|
|
18
|
+
anchor.target = '_blank';
|
|
19
|
+
anchor.rel = 'noopener noreferrer';
|
|
20
|
+
anchor.style.color = '#3b82f6';
|
|
21
|
+
anchor.style.textDecoration = 'underline';
|
|
22
|
+
anchor.style.cursor = 'pointer';
|
|
23
|
+
if (this.__title) {
|
|
24
|
+
anchor.title = this.__title;
|
|
25
|
+
}
|
|
26
|
+
return anchor;
|
|
27
|
+
}
|
|
28
|
+
updateDOM(prevNode, dom) {
|
|
29
|
+
if (prevNode.__url !== this.__url) {
|
|
30
|
+
dom.href = this.__url;
|
|
31
|
+
}
|
|
32
|
+
if (prevNode.__title !== this.__title) {
|
|
33
|
+
if (this.__title) {
|
|
34
|
+
dom.title = this.__title;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
dom.removeAttribute('title');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
getUrl() {
|
|
43
|
+
return this.__url;
|
|
44
|
+
}
|
|
45
|
+
setUrl(url) {
|
|
46
|
+
const writable = this.getWritable();
|
|
47
|
+
writable.__url = url;
|
|
48
|
+
}
|
|
49
|
+
getTitle() {
|
|
50
|
+
return this.__title;
|
|
51
|
+
}
|
|
52
|
+
setTitle(title) {
|
|
53
|
+
const writable = this.getWritable();
|
|
54
|
+
writable.__title = title;
|
|
55
|
+
}
|
|
56
|
+
static importJSON(serializedNode) {
|
|
57
|
+
const node = $createSimpleLinkNode({
|
|
58
|
+
url: serializedNode.url,
|
|
59
|
+
title: serializedNode.title,
|
|
60
|
+
});
|
|
61
|
+
node.setFormat(serializedNode.format);
|
|
62
|
+
node.setIndent(serializedNode.indent);
|
|
63
|
+
node.setDirection(serializedNode.direction);
|
|
64
|
+
return node;
|
|
65
|
+
}
|
|
66
|
+
exportJSON() {
|
|
67
|
+
return {
|
|
68
|
+
...super.exportJSON(),
|
|
69
|
+
type: 'simple-link',
|
|
70
|
+
url: this.__url,
|
|
71
|
+
title: this.__title,
|
|
72
|
+
version: 1,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
canInsertTextBefore() {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
canInsertTextAfter() {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
canBeEmpty() {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
isInline() {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
extractWithChild() {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function $createSimpleLinkNode(payload) {
|
|
92
|
+
return $applyNodeReplacement(new SimpleLinkNode(payload.url, payload.title));
|
|
93
|
+
}
|
|
94
|
+
export function $isSimpleLinkNode(node) {
|
|
95
|
+
return node instanceof SimpleLinkNode;
|
|
96
|
+
}
|
|
97
|
+
// Helper to wrap selection in a link
|
|
98
|
+
export function $wrapSelectionInSimpleLink(selection, url, title) {
|
|
99
|
+
const nodes = selection.extract();
|
|
100
|
+
const linkNode = $createSimpleLinkNode({ url, title });
|
|
101
|
+
if (nodes.length > 0) {
|
|
102
|
+
const firstNode = nodes[0];
|
|
103
|
+
firstNode.insertBefore(linkNode);
|
|
104
|
+
for (const node of nodes) {
|
|
105
|
+
linkNode.append(node);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageLinkPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/ImageLinkPlugin.tsx"],"names":[],"mappings":"AAuIA,wBAAgB,eAAe,4CA6E9B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
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, CLICK_COMMAND, } from 'lexical';
|
|
6
|
+
import { $isImageLinkNode } from '../nodes/ImageLinkNode';
|
|
7
|
+
// Fullscreen modal component
|
|
8
|
+
function ImageLinkFullscreenModal({ url, altText, copyright, photographer, comment, onClose, }) {
|
|
9
|
+
// Close on Escape key
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const handleKeyDown = (e) => {
|
|
12
|
+
if (e.key === 'Escape') {
|
|
13
|
+
onClose();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
17
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
18
|
+
}, [onClose]);
|
|
19
|
+
const hasMetadata = copyright || photographer || comment;
|
|
20
|
+
return (_jsxs("div", { style: {
|
|
21
|
+
position: 'fixed',
|
|
22
|
+
inset: 0,
|
|
23
|
+
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|
24
|
+
display: 'flex',
|
|
25
|
+
flexDirection: 'column',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
justifyContent: 'center',
|
|
28
|
+
zIndex: 100000,
|
|
29
|
+
cursor: 'pointer',
|
|
30
|
+
}, onClick: onClose, children: [_jsx("button", { onClick: onClose, style: {
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
top: '20px',
|
|
33
|
+
right: '20px',
|
|
34
|
+
width: '40px',
|
|
35
|
+
height: '40px',
|
|
36
|
+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
37
|
+
border: 'none',
|
|
38
|
+
borderRadius: '50%',
|
|
39
|
+
cursor: 'pointer',
|
|
40
|
+
display: 'flex',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
justifyContent: 'center',
|
|
43
|
+
transition: 'background-color 0.2s',
|
|
44
|
+
}, onMouseEnter: (e) => e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.2)', onMouseLeave: (e) => e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.1)', title: "Fermer (\u00C9chap)", children: _jsx("span", { className: "material-icons", style: { fontSize: '24px', color: 'white' }, children: "close" }) }), _jsx("img", { src: url, alt: altText || '', title: comment || undefined, style: {
|
|
45
|
+
maxWidth: '95vw',
|
|
46
|
+
maxHeight: hasMetadata ? '85vh' : '95vh',
|
|
47
|
+
objectFit: 'contain',
|
|
48
|
+
borderRadius: '4px',
|
|
49
|
+
boxShadow: '0 25px 50px rgba(0,0,0,0.5)',
|
|
50
|
+
}, onClick: (e) => e.stopPropagation() }), hasMetadata && (_jsxs("div", { style: {
|
|
51
|
+
marginTop: '16px',
|
|
52
|
+
padding: '12px 20px',
|
|
53
|
+
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
54
|
+
borderRadius: '8px',
|
|
55
|
+
display: 'flex',
|
|
56
|
+
flexDirection: 'column',
|
|
57
|
+
gap: '4px',
|
|
58
|
+
maxWidth: '90vw',
|
|
59
|
+
}, onClick: (e) => e.stopPropagation(), children: [photographer && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', color: 'white', fontSize: '14px' }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '16px', color: '#9ca3af' }, children: "camera_alt" }), photographer] })), copyright && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', color: 'white', fontSize: '14px' }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '16px', color: '#9ca3af' }, children: "copyright" }), copyright] })), comment && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', color: '#d1d5db', fontSize: '13px', fontStyle: 'italic' }, children: [_jsx("span", { className: "material-icons", style: { fontSize: '16px', color: '#9ca3af' }, children: "chat" }), comment] }))] }))] }));
|
|
60
|
+
}
|
|
61
|
+
export function ImageLinkPlugin() {
|
|
62
|
+
const [editor] = useLexicalComposerContext();
|
|
63
|
+
const [fullscreenData, setFullscreenData] = useState(null);
|
|
64
|
+
// Find ImageLinkNode at current selection
|
|
65
|
+
const getImageLinkNodeAtSelection = useCallback(() => {
|
|
66
|
+
let foundNode = null;
|
|
67
|
+
editor.getEditorState().read(() => {
|
|
68
|
+
const selection = $getSelection();
|
|
69
|
+
if ($isRangeSelection(selection)) {
|
|
70
|
+
const anchorNode = selection.anchor.getNode();
|
|
71
|
+
// Check if we're inside an ImageLinkNode
|
|
72
|
+
let node = anchorNode;
|
|
73
|
+
while (node) {
|
|
74
|
+
if ($isImageLinkNode(node)) {
|
|
75
|
+
foundNode = node;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
const parent = node.getParent();
|
|
79
|
+
if (!parent)
|
|
80
|
+
break;
|
|
81
|
+
node = parent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return foundNode;
|
|
86
|
+
}, [editor]);
|
|
87
|
+
// Handle Ctrl+Click on ImageLinkNode
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
return editor.registerCommand(CLICK_COMMAND, (event) => {
|
|
90
|
+
const target = event.target;
|
|
91
|
+
// Check if we clicked on an element inside the editor that is an ImageLinkNode
|
|
92
|
+
const imageLinkNode = getImageLinkNodeAtSelection();
|
|
93
|
+
if (imageLinkNode && event.ctrlKey) {
|
|
94
|
+
// Ctrl+Click opens fullscreen
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
setFullscreenData({
|
|
97
|
+
url: imageLinkNode.getUrl(),
|
|
98
|
+
altText: imageLinkNode.getAltText(),
|
|
99
|
+
copyright: imageLinkNode.getCopyright(),
|
|
100
|
+
photographer: imageLinkNode.getPhotographer(),
|
|
101
|
+
comment: imageLinkNode.getComment(),
|
|
102
|
+
});
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
107
|
+
}, [editor, getImageLinkNodeAtSelection]);
|
|
108
|
+
const closeFullscreen = useCallback(() => {
|
|
109
|
+
setFullscreenData(null);
|
|
110
|
+
}, []);
|
|
111
|
+
return (_jsx(_Fragment, { children: fullscreenData && (_jsx(ImageLinkFullscreenModal, { url: fullscreenData.url, altText: fullscreenData.altText, copyright: fullscreenData.copyright, photographer: fullscreenData.photographer, comment: fullscreenData.comment, onClose: closeFullscreen })) }));
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InsertObjectPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/InsertObjectPlugin.tsx"],"names":[],"mappings":"AAIA,OAAO,EAML,cAAc,EACf,MAAM,SAAS,CAAA;AAMhB,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,IAAI,CAA0C,CAAA;AA2ZjG,wBAAgB,kBAAkB,4CA6RjC"}
|