@thangph2146/lexical-editor 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/editor-x/editor.cjs +280 -20
- package/dist/editor-x/editor.cjs.map +1 -1
- package/dist/editor-x/editor.css +27 -4
- package/dist/editor-x/editor.css.map +1 -1
- package/dist/editor-x/editor.js +281 -21
- package/dist/editor-x/editor.js.map +1 -1
- package/dist/index.cjs +292 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +27 -4
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +293 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/lexical-editor.tsx +19 -6
- package/src/context/uploads-context.tsx +1 -0
- package/src/editor-ui/content-editable.tsx +18 -2
- package/src/editor-x/nodes.ts +2 -0
- package/src/nodes/download-link-node.tsx +118 -0
- package/src/plugins/floating-link-editor-plugin.tsx +338 -91
- package/src/themes/core/_tables.scss +0 -1
- package/src/themes/plugins/_floating-link-editor.scss +28 -2
- package/src/themes/ui-components/_button.scss +1 -1
- package/src/themes/ui-components/_flex.scss +1 -0
- package/src/ui/button-group.tsx +10 -10
- package/src/ui/button.tsx +38 -38
- package/src/ui/collapsible.tsx +67 -67
- package/src/ui/command.tsx +48 -48
- package/src/ui/dialog.tsx +146 -146
- package/src/ui/flex.tsx +45 -45
- package/src/ui/input.tsx +20 -20
- package/src/ui/label.tsx +20 -20
- package/src/ui/number-input.tsx +104 -104
- package/src/ui/popover.tsx +128 -128
- package/src/ui/scroll-area.tsx +17 -17
- package/src/ui/select.tsx +171 -171
- package/src/ui/separator.tsx +20 -20
- package/src/ui/slider.tsx +14 -14
- package/src/ui/slot.tsx +3 -3
- package/src/ui/tabs.tsx +87 -87
- package/src/ui/toggle-group.tsx +109 -109
- package/src/ui/toggle.tsx +28 -28
- package/src/ui/tooltip.tsx +28 -28
- package/src/ui/typography.tsx +44 -44
package/dist/index.d.cts
CHANGED
|
@@ -3,13 +3,38 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
3
3
|
import { SerializedEditorState } from 'lexical';
|
|
4
4
|
export { SerializedEditorState } from 'lexical';
|
|
5
5
|
|
|
6
|
+
interface ImageItem {
|
|
7
|
+
fileName: string;
|
|
8
|
+
originalName: string;
|
|
9
|
+
size: number;
|
|
10
|
+
mimeType: string;
|
|
11
|
+
url: string;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
interface FolderNode {
|
|
16
|
+
name: string;
|
|
17
|
+
path: string;
|
|
18
|
+
images: ImageItem[];
|
|
19
|
+
subfolders: FolderNode[];
|
|
20
|
+
}
|
|
21
|
+
interface EditorUploadsContextType {
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
folderTree?: FolderNode;
|
|
24
|
+
onUploadFile?: (file: File) => Promise<{
|
|
25
|
+
url: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
|
|
6
30
|
interface LexicalEditorProps {
|
|
7
31
|
value?: unknown;
|
|
8
32
|
onChange?: (value: SerializedEditorState) => void;
|
|
9
33
|
readOnly?: boolean;
|
|
10
34
|
className?: string;
|
|
11
35
|
placeholder?: string;
|
|
36
|
+
uploadsContext?: EditorUploadsContextType;
|
|
12
37
|
}
|
|
13
|
-
declare function LexicalEditor({ value, onChange, readOnly, className, placeholder, }: LexicalEditorProps): react_jsx_runtime.JSX.Element;
|
|
38
|
+
declare function LexicalEditor({ value, onChange, readOnly, className, placeholder, uploadsContext, }: LexicalEditorProps): react_jsx_runtime.JSX.Element;
|
|
14
39
|
|
|
15
40
|
export { LexicalEditor, type LexicalEditorProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,13 +3,38 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
3
3
|
import { SerializedEditorState } from 'lexical';
|
|
4
4
|
export { SerializedEditorState } from 'lexical';
|
|
5
5
|
|
|
6
|
+
interface ImageItem {
|
|
7
|
+
fileName: string;
|
|
8
|
+
originalName: string;
|
|
9
|
+
size: number;
|
|
10
|
+
mimeType: string;
|
|
11
|
+
url: string;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
interface FolderNode {
|
|
16
|
+
name: string;
|
|
17
|
+
path: string;
|
|
18
|
+
images: ImageItem[];
|
|
19
|
+
subfolders: FolderNode[];
|
|
20
|
+
}
|
|
21
|
+
interface EditorUploadsContextType {
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
folderTree?: FolderNode;
|
|
24
|
+
onUploadFile?: (file: File) => Promise<{
|
|
25
|
+
url: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
|
|
6
30
|
interface LexicalEditorProps {
|
|
7
31
|
value?: unknown;
|
|
8
32
|
onChange?: (value: SerializedEditorState) => void;
|
|
9
33
|
readOnly?: boolean;
|
|
10
34
|
className?: string;
|
|
11
35
|
placeholder?: string;
|
|
36
|
+
uploadsContext?: EditorUploadsContextType;
|
|
12
37
|
}
|
|
13
|
-
declare function LexicalEditor({ value, onChange, readOnly, className, placeholder, }: LexicalEditorProps): react_jsx_runtime.JSX.Element;
|
|
38
|
+
declare function LexicalEditor({ value, onChange, readOnly, className, placeholder, uploadsContext, }: LexicalEditorProps): react_jsx_runtime.JSX.Element;
|
|
14
39
|
|
|
15
40
|
export { LexicalEditor, type LexicalEditorProps };
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { mergeRegister, IS_APPLE, $findMatchingParent, $getNearestNodeOfType, $g
|
|
|
7
7
|
import { $getNodeByKey, FORMAT_ELEMENT_COMMAND, $isNodeSelection, $getRoot, $getSelection, $setSelection, $isRangeSelection, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_LOW, CLICK_COMMAND, DRAGSTART_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_CRITICAL, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, UNDO_COMMAND, REDO_COMMAND, $isRootOrShadowRoot, FORMAT_TEXT_COMMAND, KEY_MODIFIER_COMMAND, COMMAND_PRIORITY_NORMAL, $isTextNode, $createParagraphNode, $isElementNode, COMMAND_PRIORITY_EDITOR, $getNearestNodeFromDOMNode, $insertNodes, COMMAND_PRIORITY_HIGH, DRAGOVER_COMMAND, DROP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, FOCUS_COMMAND, KEY_TAB_COMMAND, CUT_COMMAND, COPY_COMMAND, PASTE_COMMAND, $isDecoratorNode, $createTextNode, INDENT_CONTENT_COMMAND, CLEAR_HISTORY_COMMAND, CLEAR_EDITOR_COMMAND, ParagraphNode, TextNode, OUTDENT_CONTENT_COMMAND, $applyNodeReplacement, $createRangeSelection, $isParagraphNode, DecoratorNode, createEditor, RootNode, ElementNode, createCommand, HISTORY_MERGE_TAG, $isLineBreakNode, isHTMLElement, $addUpdateTag } from 'lexical';
|
|
8
8
|
import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents';
|
|
9
9
|
import { $isDecoratorBlockNode, DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
|
|
10
|
-
import { UndoIcon, RedoIcon, PlusIcon, ScissorsIcon, ImageIcon, TableIcon, Columns3Icon, TypeIcon, Minus, Plus, SubscriptIcon, SuperscriptIcon, LinkIcon, EraserIcon, BaselineIcon, PaintBucketIcon, IndentDecreaseIcon, IndentIncreaseIcon, CircleUserRoundIcon, GripVerticalIcon, TextIcon, ListTodoIcon, ListOrderedIcon, ListIcon, QuoteIcon, CodeIcon, MinusIcon, Combine, SplitSquareVertical, Columns3, Rows3, LayoutGrid, Palette, Hash, Link2Off, Scissors, Copy, Clipboard, ClipboardType, Trash2, SendIcon, UploadIcon, DownloadIcon, FileTextIcon, LockIcon, UnlockIcon, Trash2Icon, NotebookPenIcon, Loader2, PipetteIcon, Heading3Icon, Heading2Icon, Heading1Icon, AlignJustifyIcon, AlignRightIcon, AlignCenterIcon, AlignLeftIcon, YoutubeIcon, TwitterIcon, BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, ChevronRight, Folder, CircleCheckIcon, CopyIcon, X, Check, Pencil, Trash, MicIcon, ListPlusIcon, ListMinusIcon, CaseSensitiveIcon, AlignLeft, AlignCenter, AlignRight, Minimize2, Maximize2, ImageMinus, ImagePlus } from 'lucide-react';
|
|
10
|
+
import { UndoIcon, RedoIcon, PlusIcon, ScissorsIcon, ImageIcon, TableIcon, Columns3Icon, TypeIcon, Minus, Plus, SubscriptIcon, SuperscriptIcon, LinkIcon, EraserIcon, BaselineIcon, PaintBucketIcon, IndentDecreaseIcon, IndentIncreaseIcon, CircleUserRoundIcon, GripVerticalIcon, TextIcon, ListTodoIcon, ListOrderedIcon, ListIcon, QuoteIcon, CodeIcon, MinusIcon, Combine, SplitSquareVertical, Columns3, Rows3, LayoutGrid, Palette, Hash, Link2Off, Scissors, Copy, Clipboard, ClipboardType, Trash2, SendIcon, UploadIcon, DownloadIcon, FileTextIcon, LockIcon, UnlockIcon, Trash2Icon, NotebookPenIcon, Loader2, PipetteIcon, Heading3Icon, Heading2Icon, Heading1Icon, AlignJustifyIcon, AlignRightIcon, AlignCenterIcon, AlignLeftIcon, YoutubeIcon, TwitterIcon, BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, ChevronRight, Folder, CircleCheckIcon, CopyIcon, Upload, X, Check, Pencil, Trash, MicIcon, ListPlusIcon, ListMinusIcon, CaseSensitiveIcon, AlignLeft, AlignCenter, AlignRight, Minimize2, Maximize2, ImageMinus, ImagePlus } from 'lucide-react';
|
|
11
11
|
import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
|
|
12
12
|
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
|
|
13
13
|
import { createPortal } from 'react-dom';
|
|
@@ -19,9 +19,9 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
|
|
19
19
|
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
|
20
20
|
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
|
21
21
|
import { $isListNode, ListNode, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListItemNode, $getListDepth, $isListItemNode, $createListItemNode } from '@lexical/list';
|
|
22
|
+
import { TOGGLE_LINK_COMMAND, $isLinkNode, LinkNode, AutoLinkNode, $createLinkNode, $isAutoLinkNode } from '@lexical/link';
|
|
22
23
|
import { $isCodeNode, getLanguageFriendlyName, registerCodeHighlighting, $createCodeNode, CodeNode, CodeHighlightNode, $isCodeHighlightNode, CODE_LANGUAGE_FRIENDLY_NAME_MAP, CODE_LANGUAGE_MAP } from '@lexical/code';
|
|
23
24
|
import { HashtagNode } from '@lexical/hashtag';
|
|
24
|
-
import { TOGGLE_LINK_COMMAND, $isLinkNode, LinkNode, AutoLinkNode, $createLinkNode, $isAutoLinkNode } from '@lexical/link';
|
|
25
25
|
import { OverflowNode } from '@lexical/overflow';
|
|
26
26
|
import { INSERT_HORIZONTAL_RULE_COMMAND, HorizontalRuleNode, $createHorizontalRuleNode, $isHorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
27
27
|
import { $isHeadingNode, $isQuoteNode, $createHeadingNode, $createQuoteNode, DRAG_DROP_PASTE, HeadingNode, QuoteNode } from '@lexical/rich-text';
|
|
@@ -4192,6 +4192,12 @@ var init_tabs = __esm({
|
|
|
4192
4192
|
TabsContext = React21.createContext(null);
|
|
4193
4193
|
}
|
|
4194
4194
|
});
|
|
4195
|
+
function EditorUploadsProvider({
|
|
4196
|
+
children,
|
|
4197
|
+
value
|
|
4198
|
+
}) {
|
|
4199
|
+
return /* @__PURE__ */ jsx(EditorUploadsContext.Provider, { value, children });
|
|
4200
|
+
}
|
|
4195
4201
|
function useEditorUploads() {
|
|
4196
4202
|
const context = useContext(EditorUploadsContext);
|
|
4197
4203
|
if (context === void 0) {
|
|
@@ -5069,12 +5075,14 @@ var init_image_placeholder = __esm({
|
|
|
5069
5075
|
}
|
|
5070
5076
|
});
|
|
5071
5077
|
function ContentEditable({
|
|
5072
|
-
placeholder,
|
|
5078
|
+
placeholder = "",
|
|
5073
5079
|
className,
|
|
5074
5080
|
placeholderClassName,
|
|
5075
5081
|
placeholderDefaults = true
|
|
5076
5082
|
}) {
|
|
5077
5083
|
const isReadOnlyOrReview = className?.includes("--readonly") || className?.includes("--review");
|
|
5084
|
+
const text = placeholder.trim();
|
|
5085
|
+
const showLexicalPlaceholder = text.length > 0;
|
|
5078
5086
|
return /* @__PURE__ */ jsx(
|
|
5079
5087
|
ContentEditable$1,
|
|
5080
5088
|
{
|
|
@@ -5083,7 +5091,17 @@ function ContentEditable({
|
|
|
5083
5091
|
!isReadOnlyOrReview && "min-h-72 px-8 py-4",
|
|
5084
5092
|
className
|
|
5085
5093
|
),
|
|
5086
|
-
"aria-label": "Editor n\u1ED9i dung"
|
|
5094
|
+
"aria-label": "Editor n\u1ED9i dung",
|
|
5095
|
+
...showLexicalPlaceholder ? {
|
|
5096
|
+
"aria-placeholder": text,
|
|
5097
|
+
placeholder: /* @__PURE__ */ jsx(
|
|
5098
|
+
"div",
|
|
5099
|
+
{
|
|
5100
|
+
className: cn(placeholderDefaults && "editor-placeholder", placeholderClassName),
|
|
5101
|
+
children: text
|
|
5102
|
+
}
|
|
5103
|
+
)
|
|
5104
|
+
} : { placeholder: null }
|
|
5087
5105
|
}
|
|
5088
5106
|
);
|
|
5089
5107
|
}
|
|
@@ -6219,6 +6237,86 @@ var init_list_with_color_node = __esm({
|
|
|
6219
6237
|
};
|
|
6220
6238
|
}
|
|
6221
6239
|
});
|
|
6240
|
+
function $createDownloadLinkNode(url, download = null, attributes) {
|
|
6241
|
+
return new DownloadLinkNode(url, download, attributes);
|
|
6242
|
+
}
|
|
6243
|
+
function $isDownloadLinkNode(node) {
|
|
6244
|
+
return node instanceof DownloadLinkNode;
|
|
6245
|
+
}
|
|
6246
|
+
var DownloadLinkNode;
|
|
6247
|
+
var init_download_link_node = __esm({
|
|
6248
|
+
"src/nodes/download-link-node.tsx"() {
|
|
6249
|
+
DownloadLinkNode = class _DownloadLinkNode extends LinkNode {
|
|
6250
|
+
__download;
|
|
6251
|
+
static getType() {
|
|
6252
|
+
return "download-link";
|
|
6253
|
+
}
|
|
6254
|
+
static clone(node) {
|
|
6255
|
+
return new _DownloadLinkNode(
|
|
6256
|
+
node.getURL(),
|
|
6257
|
+
node.__download,
|
|
6258
|
+
{
|
|
6259
|
+
rel: node.getRel(),
|
|
6260
|
+
target: node.getTarget(),
|
|
6261
|
+
title: node.getTitle()
|
|
6262
|
+
},
|
|
6263
|
+
node.__key
|
|
6264
|
+
);
|
|
6265
|
+
}
|
|
6266
|
+
constructor(url, download = null, attributes, key) {
|
|
6267
|
+
super(url, attributes, key);
|
|
6268
|
+
this.__download = download;
|
|
6269
|
+
}
|
|
6270
|
+
getDownload() {
|
|
6271
|
+
return this.__download;
|
|
6272
|
+
}
|
|
6273
|
+
setDownload(download) {
|
|
6274
|
+
const writable = this.getWritable();
|
|
6275
|
+
writable.__download = download;
|
|
6276
|
+
return this;
|
|
6277
|
+
}
|
|
6278
|
+
createDOM(config) {
|
|
6279
|
+
const dom = super.createDOM(config);
|
|
6280
|
+
this.applyDownloadDOM(dom);
|
|
6281
|
+
return dom;
|
|
6282
|
+
}
|
|
6283
|
+
updateLinkDOM(prevNode, anchorElem, config) {
|
|
6284
|
+
super.updateLinkDOM(prevNode, anchorElem, config);
|
|
6285
|
+
this.applyDownloadDOM(anchorElem);
|
|
6286
|
+
}
|
|
6287
|
+
exportJSON() {
|
|
6288
|
+
return {
|
|
6289
|
+
...super.exportJSON(),
|
|
6290
|
+
type: _DownloadLinkNode.getType(),
|
|
6291
|
+
version: 1,
|
|
6292
|
+
download: this.__download
|
|
6293
|
+
};
|
|
6294
|
+
}
|
|
6295
|
+
static importJSON(serializedNode) {
|
|
6296
|
+
const node = new _DownloadLinkNode(
|
|
6297
|
+
serializedNode.url,
|
|
6298
|
+
serializedNode.download,
|
|
6299
|
+
{
|
|
6300
|
+
rel: serializedNode.rel ?? null,
|
|
6301
|
+
target: serializedNode.target ?? null,
|
|
6302
|
+
title: serializedNode.title ?? null
|
|
6303
|
+
},
|
|
6304
|
+
serializedNode.key
|
|
6305
|
+
);
|
|
6306
|
+
return node;
|
|
6307
|
+
}
|
|
6308
|
+
applyDownloadDOM(dom) {
|
|
6309
|
+
if (dom instanceof HTMLAnchorElement) {
|
|
6310
|
+
if (this.__download === null) {
|
|
6311
|
+
dom.removeAttribute("download");
|
|
6312
|
+
} else {
|
|
6313
|
+
dom.setAttribute("download", this.__download);
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6319
|
+
});
|
|
6222
6320
|
function $convertMentionElement(domNode) {
|
|
6223
6321
|
const textContent = domNode.textContent;
|
|
6224
6322
|
if (textContent !== null) {
|
|
@@ -6331,6 +6429,7 @@ var init_nodes = __esm({
|
|
|
6331
6429
|
init_layout_container_node();
|
|
6332
6430
|
init_layout_item_node();
|
|
6333
6431
|
init_list_with_color_node();
|
|
6432
|
+
init_download_link_node();
|
|
6334
6433
|
init_mention_node();
|
|
6335
6434
|
nodes = [
|
|
6336
6435
|
HeadingNode,
|
|
@@ -6346,6 +6445,7 @@ var init_nodes = __esm({
|
|
|
6346
6445
|
ListWithColorNode,
|
|
6347
6446
|
ListItemNode,
|
|
6348
6447
|
LinkNode,
|
|
6448
|
+
DownloadLinkNode,
|
|
6349
6449
|
OverflowNode,
|
|
6350
6450
|
HashtagNode,
|
|
6351
6451
|
TableNode,
|
|
@@ -27998,6 +28098,44 @@ var init_url = __esm({
|
|
|
27998
28098
|
);
|
|
27999
28099
|
}
|
|
28000
28100
|
});
|
|
28101
|
+
function shouldTreatUrlAsDownload(url) {
|
|
28102
|
+
if (typeof url !== "string") return false;
|
|
28103
|
+
const u = url.toLowerCase();
|
|
28104
|
+
if (u.includes("/api/uploads/") || u.includes("/uploads/") || u.includes("/api/admin/uploads/") || u.includes("/admin/uploads/"))
|
|
28105
|
+
return true;
|
|
28106
|
+
return /\.(pdf|doc|docx|xls|xlsx|csv|zip|rar|7z|txt|rtf|png|jpg|jpeg|gif|webp|mp3|wav|mp4|mov|avi)(\?.*)?$/.test(
|
|
28107
|
+
u
|
|
28108
|
+
);
|
|
28109
|
+
}
|
|
28110
|
+
function inferDownloadFileName(url) {
|
|
28111
|
+
try {
|
|
28112
|
+
const path = url.split("?")[0] ?? "";
|
|
28113
|
+
const last = path.split("/").filter(Boolean).pop();
|
|
28114
|
+
return last ? decodeURIComponent(last) : "download";
|
|
28115
|
+
} catch {
|
|
28116
|
+
return "download";
|
|
28117
|
+
}
|
|
28118
|
+
}
|
|
28119
|
+
function getCookieValue(name) {
|
|
28120
|
+
if (typeof document === "undefined") return null;
|
|
28121
|
+
const row = document.cookie.split("; ").find((item) => item.startsWith(`${name}=`));
|
|
28122
|
+
if (!row) return null;
|
|
28123
|
+
return row.split("=").slice(1).join("=") || null;
|
|
28124
|
+
}
|
|
28125
|
+
function buildHrefFromJsDownloadArg(jsArg) {
|
|
28126
|
+
const firstSegment = typeof window !== "undefined" ? window.location.pathname.split("/").filter(Boolean)[0] ?? "" : "";
|
|
28127
|
+
const serveBase = firstSegment === "admin" ? "/api/admin/uploads/serve" : "/api/uploads/serve";
|
|
28128
|
+
const arg = jsArg.trim();
|
|
28129
|
+
if (!arg) return "about:blank";
|
|
28130
|
+
if (/^https?:\/\//i.test(arg)) return arg;
|
|
28131
|
+
if (arg.startsWith("/api/")) return arg;
|
|
28132
|
+
if (arg.startsWith("images/") || arg.startsWith("files/")) {
|
|
28133
|
+
return `${serveBase}/${arg}`;
|
|
28134
|
+
}
|
|
28135
|
+
const m = arg.match(/(images|files)\/.+/i);
|
|
28136
|
+
if (m?.[0]) return `${serveBase}/${m[0]}`;
|
|
28137
|
+
return "about:blank";
|
|
28138
|
+
}
|
|
28001
28139
|
function FloatingLinkEditor({
|
|
28002
28140
|
editor,
|
|
28003
28141
|
isLink,
|
|
@@ -28011,6 +28149,9 @@ function FloatingLinkEditor({
|
|
|
28011
28149
|
const [linkUrl, setLinkUrl] = useState("");
|
|
28012
28150
|
const [editedLinkUrl, setEditedLinkUrl] = useState("https://");
|
|
28013
28151
|
const [lastSelection, setLastSelection] = useState(null);
|
|
28152
|
+
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
28153
|
+
const fileInputRef = useRef(null);
|
|
28154
|
+
const { onUploadFile } = useEditorUploads();
|
|
28014
28155
|
const $updateLinkEditor = useCallback(() => {
|
|
28015
28156
|
const selection = $getSelection();
|
|
28016
28157
|
let linkNode = null;
|
|
@@ -28204,9 +28345,11 @@ function FloatingLinkEditor({
|
|
|
28204
28345
|
setIsLinkEditMode(false);
|
|
28205
28346
|
}
|
|
28206
28347
|
};
|
|
28207
|
-
const handleLinkSubmission = () => {
|
|
28208
|
-
const
|
|
28209
|
-
|
|
28348
|
+
const handleLinkSubmission = (submittedUrl, originalFileName) => {
|
|
28349
|
+
const rawUrl = typeof submittedUrl === "string" ? submittedUrl : editedLinkUrl;
|
|
28350
|
+
const url = sanitizeUrl(rawUrl);
|
|
28351
|
+
const downloadFileName = originalFileName || (shouldTreatUrlAsDownload(url) ? inferDownloadFileName(url) : null);
|
|
28352
|
+
if (url && url !== "about:blank" && url !== "https://" && url !== "http://") {
|
|
28210
28353
|
editor.update(() => {
|
|
28211
28354
|
let selection = $getSelection();
|
|
28212
28355
|
if (!selection && lastSelection !== null) {
|
|
@@ -28231,8 +28374,11 @@ function FloatingLinkEditor({
|
|
|
28231
28374
|
const existingLinkNode = $findMatchingParent(node, $isLinkNode) || ($isLinkNode(node.getParent()) ? node.getParent() : null);
|
|
28232
28375
|
if (existingLinkNode) {
|
|
28233
28376
|
existingLinkNode.setURL(url);
|
|
28377
|
+
if (downloadFileName && $isDownloadLinkNode(existingLinkNode)) {
|
|
28378
|
+
existingLinkNode.setDownload(downloadFileName);
|
|
28379
|
+
}
|
|
28234
28380
|
} else {
|
|
28235
|
-
const linkNode = $createLinkNode(url);
|
|
28381
|
+
const linkNode = downloadFileName ? $createDownloadLinkNode(url, downloadFileName) : $createLinkNode(url);
|
|
28236
28382
|
$wrapNodeInElement(node, () => linkNode);
|
|
28237
28383
|
}
|
|
28238
28384
|
}
|
|
@@ -28241,25 +28387,114 @@ function FloatingLinkEditor({
|
|
|
28241
28387
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
|
|
28242
28388
|
const parent = getSelectedNode(selection).getParent();
|
|
28243
28389
|
if ($isAutoLinkNode(parent)) {
|
|
28244
|
-
const linkNode = $
|
|
28390
|
+
const linkNode = downloadFileName ? $createDownloadLinkNode(parent.getURL(), downloadFileName, {
|
|
28391
|
+
rel: parent.__rel,
|
|
28392
|
+
target: parent.__target,
|
|
28393
|
+
title: parent.__title
|
|
28394
|
+
}) : $createLinkNode(parent.getURL(), {
|
|
28245
28395
|
rel: parent.__rel,
|
|
28246
28396
|
target: parent.__target,
|
|
28247
28397
|
title: parent.__title
|
|
28248
28398
|
});
|
|
28249
28399
|
parent.replace(linkNode, true);
|
|
28250
28400
|
}
|
|
28401
|
+
if (downloadFileName) {
|
|
28402
|
+
const selectedNode = getSelectedNode(selection);
|
|
28403
|
+
const linkNode = $findMatchingParent(selectedNode, $isLinkNode) || ($isLinkNode(selectedNode) ? selectedNode : null);
|
|
28404
|
+
if (linkNode) {
|
|
28405
|
+
const currentText = linkNode.getTextContent();
|
|
28406
|
+
const targetText = originalFileName || downloadFileName;
|
|
28407
|
+
const downloadLinkNode = $createDownloadLinkNode(url, downloadFileName, {
|
|
28408
|
+
rel: linkNode.getRel(),
|
|
28409
|
+
target: linkNode.getTarget(),
|
|
28410
|
+
title: linkNode.getTitle()
|
|
28411
|
+
});
|
|
28412
|
+
if (currentText === url && targetText) {
|
|
28413
|
+
downloadLinkNode.append($createTextNode(targetText));
|
|
28414
|
+
} else {
|
|
28415
|
+
const children = linkNode.getChildren();
|
|
28416
|
+
if (children.length > 0) {
|
|
28417
|
+
downloadLinkNode.append(...children);
|
|
28418
|
+
}
|
|
28419
|
+
}
|
|
28420
|
+
linkNode.replace(downloadLinkNode);
|
|
28421
|
+
}
|
|
28422
|
+
}
|
|
28251
28423
|
}
|
|
28252
28424
|
});
|
|
28253
28425
|
setEditedLinkUrl("https://");
|
|
28254
28426
|
setIsLinkEditMode(false);
|
|
28255
28427
|
}
|
|
28256
28428
|
};
|
|
28429
|
+
const handlePickLocalFile = () => {
|
|
28430
|
+
if (isUploadingFile) return;
|
|
28431
|
+
fileInputRef.current?.click();
|
|
28432
|
+
};
|
|
28433
|
+
const handleUploadLocalFile = async (event) => {
|
|
28434
|
+
const file = event.target.files?.[0];
|
|
28435
|
+
if (!file) return;
|
|
28436
|
+
try {
|
|
28437
|
+
setIsUploadingFile(true);
|
|
28438
|
+
let uploadedUrl = void 0;
|
|
28439
|
+
if (onUploadFile) {
|
|
28440
|
+
const result = await onUploadFile(file);
|
|
28441
|
+
if (result.error) throw new Error(result.error);
|
|
28442
|
+
uploadedUrl = result.url;
|
|
28443
|
+
} else {
|
|
28444
|
+
const formData = new FormData();
|
|
28445
|
+
formData.append("file", file);
|
|
28446
|
+
const firstSegment = typeof window !== "undefined" ? window.location.pathname.split("/").filter(Boolean)[0] ?? "" : "";
|
|
28447
|
+
const pathPart = firstSegment === "admin" ? "/admin/uploads" : "/uploads";
|
|
28448
|
+
const endpoint = firstSegment === "admin" ? `/admin/api${pathPart}` : `/api${pathPart}`;
|
|
28449
|
+
const userId = getCookieValue("app_user_id");
|
|
28450
|
+
const authToken = getCookieValue("auth-token");
|
|
28451
|
+
const headers = {};
|
|
28452
|
+
if (userId) headers["X-User-Id"] = userId;
|
|
28453
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
28454
|
+
const res = await fetch(endpoint, {
|
|
28455
|
+
method: "POST",
|
|
28456
|
+
credentials: "include",
|
|
28457
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
28458
|
+
body: formData
|
|
28459
|
+
});
|
|
28460
|
+
if (!res.ok) {
|
|
28461
|
+
throw new Error(`Upload failed: HTTP ${res.status}`);
|
|
28462
|
+
}
|
|
28463
|
+
const payload = await res.json();
|
|
28464
|
+
uploadedUrl = payload?.data?.url;
|
|
28465
|
+
if (!payload?.success || !uploadedUrl) {
|
|
28466
|
+
throw new Error(payload?.message || payload?.error || "Upload failed");
|
|
28467
|
+
}
|
|
28468
|
+
}
|
|
28469
|
+
if (uploadedUrl) {
|
|
28470
|
+
setEditedLinkUrl(uploadedUrl);
|
|
28471
|
+
handleLinkSubmission(uploadedUrl, file.name);
|
|
28472
|
+
}
|
|
28473
|
+
} catch (error) {
|
|
28474
|
+
console.error("[FloatingLinkEditor] Upload local file failed:", error);
|
|
28475
|
+
} finally {
|
|
28476
|
+
setIsUploadingFile(false);
|
|
28477
|
+
if (fileInputRef.current) {
|
|
28478
|
+
fileInputRef.current.value = "";
|
|
28479
|
+
}
|
|
28480
|
+
}
|
|
28481
|
+
};
|
|
28257
28482
|
return /* @__PURE__ */ jsx(
|
|
28258
28483
|
"div",
|
|
28259
28484
|
{
|
|
28260
28485
|
ref: editorRef,
|
|
28261
28486
|
className: "editor-floating-link-editor",
|
|
28262
|
-
children: isLinkEditMode || isLink ? isLinkEditMode ? /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__input-container", children: [
|
|
28487
|
+
children: isLinkEditMode || isLink ? isLinkEditMode ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__input-container", children: [
|
|
28488
|
+
/* @__PURE__ */ jsx(
|
|
28489
|
+
"input",
|
|
28490
|
+
{
|
|
28491
|
+
ref: fileInputRef,
|
|
28492
|
+
type: "file",
|
|
28493
|
+
className: "hidden",
|
|
28494
|
+
onChange: handleUploadLocalFile,
|
|
28495
|
+
accept: ".pdf,.doc,.docx,.xls,.xlsx,.csv,.rtf,.txt,.zip,.rar,.7z,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.webp,.svg,.mp3,.wav,.mp4,.mov,.avi,.webm"
|
|
28496
|
+
}
|
|
28497
|
+
),
|
|
28263
28498
|
/* @__PURE__ */ jsx(
|
|
28264
28499
|
Input,
|
|
28265
28500
|
{
|
|
@@ -28270,6 +28505,18 @@ function FloatingLinkEditor({
|
|
|
28270
28505
|
className: "editor-flex-grow"
|
|
28271
28506
|
}
|
|
28272
28507
|
),
|
|
28508
|
+
/* @__PURE__ */ jsx(
|
|
28509
|
+
Button,
|
|
28510
|
+
{
|
|
28511
|
+
size: "icon",
|
|
28512
|
+
variant: "ghost",
|
|
28513
|
+
onClick: handlePickLocalFile,
|
|
28514
|
+
className: "editor-shrink-0",
|
|
28515
|
+
disabled: isUploadingFile,
|
|
28516
|
+
title: isUploadingFile ? "Uploading..." : "Upload file t\u1EEB thi\u1EBFt b\u1ECB",
|
|
28517
|
+
children: isUploadingFile ? /* @__PURE__ */ jsx(Loader2, { className: "editor-icon-sm animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "editor-icon-sm" })
|
|
28518
|
+
}
|
|
28519
|
+
),
|
|
28273
28520
|
/* @__PURE__ */ jsx(
|
|
28274
28521
|
Button,
|
|
28275
28522
|
{
|
|
@@ -28287,22 +28534,39 @@ function FloatingLinkEditor({
|
|
|
28287
28534
|
Button,
|
|
28288
28535
|
{
|
|
28289
28536
|
size: "icon",
|
|
28290
|
-
onClick: handleLinkSubmission,
|
|
28537
|
+
onClick: () => handleLinkSubmission(),
|
|
28291
28538
|
className: "editor-shrink-0",
|
|
28292
28539
|
children: /* @__PURE__ */ jsx(Check, { className: "editor-icon-sm" })
|
|
28293
28540
|
}
|
|
28294
28541
|
)
|
|
28295
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__view-container", children: [
|
|
28296
|
-
|
|
28297
|
-
|
|
28298
|
-
|
|
28299
|
-
|
|
28300
|
-
|
|
28301
|
-
|
|
28302
|
-
|
|
28303
|
-
|
|
28542
|
+
] }) }) : /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__view-container", children: [
|
|
28543
|
+
(() => {
|
|
28544
|
+
let href = sanitizeUrl(linkUrl);
|
|
28545
|
+
const jsDownloadMatch = typeof linkUrl === "string" ? linkUrl.match(/^javascript:download\(\s*(['"])(.*?)\1\s*\)\s*$/i) : null;
|
|
28546
|
+
let downloadAttr;
|
|
28547
|
+
if (jsDownloadMatch) {
|
|
28548
|
+
const jsArg = jsDownloadMatch[2] ?? "";
|
|
28549
|
+
href = buildHrefFromJsDownloadArg(jsArg);
|
|
28550
|
+
if (href !== "about:blank") {
|
|
28551
|
+
downloadAttr = inferDownloadFileName(href);
|
|
28552
|
+
}
|
|
28553
|
+
} else if (shouldTreatUrlAsDownload(href)) {
|
|
28554
|
+
downloadAttr = inferDownloadFileName(href);
|
|
28304
28555
|
}
|
|
28305
|
-
|
|
28556
|
+
const isDownload = typeof downloadAttr === "string" && downloadAttr.length > 0;
|
|
28557
|
+
const text = jsDownloadMatch ? "Download" : shouldTreatUrlAsDownload(href) ? inferDownloadFileName(href) : href === "about:blank" ? "Invalid URL" : linkUrl;
|
|
28558
|
+
return /* @__PURE__ */ jsx(
|
|
28559
|
+
"a",
|
|
28560
|
+
{
|
|
28561
|
+
href,
|
|
28562
|
+
download: downloadAttr,
|
|
28563
|
+
target: isDownload ? "_self" : "_blank",
|
|
28564
|
+
rel: isDownload ? void 0 : "noopener noreferrer",
|
|
28565
|
+
className: "editor-floating-link-editor__link",
|
|
28566
|
+
children: /* @__PURE__ */ jsx(TypographyPSmall, { className: "editor-truncate", children: text })
|
|
28567
|
+
}
|
|
28568
|
+
);
|
|
28569
|
+
})(),
|
|
28306
28570
|
/* @__PURE__ */ jsxs(Flex, { gap: 0, className: "editor-shrink-0", children: [
|
|
28307
28571
|
/* @__PURE__ */ jsx(
|
|
28308
28572
|
Button,
|
|
@@ -28616,6 +28880,8 @@ var init_floating_link_editor_plugin = __esm({
|
|
|
28616
28880
|
init_flex();
|
|
28617
28881
|
init_typography();
|
|
28618
28882
|
init_image_node();
|
|
28883
|
+
init_download_link_node();
|
|
28884
|
+
init_uploads_context();
|
|
28619
28885
|
}
|
|
28620
28886
|
});
|
|
28621
28887
|
|
|
@@ -33503,6 +33769,7 @@ function Editor({
|
|
|
33503
33769
|
);
|
|
33504
33770
|
}
|
|
33505
33771
|
init_logger();
|
|
33772
|
+
init_uploads_context();
|
|
33506
33773
|
function isValidSerializedEditorState(value) {
|
|
33507
33774
|
return value !== null && typeof value === "object" && "root" in value && value.root !== null && typeof value.root === "object" && "type" in value.root && value.root.type === "root";
|
|
33508
33775
|
}
|
|
@@ -33511,7 +33778,8 @@ function LexicalEditor11({
|
|
|
33511
33778
|
onChange,
|
|
33512
33779
|
readOnly = false,
|
|
33513
33780
|
className,
|
|
33514
|
-
placeholder = ""
|
|
33781
|
+
placeholder = "",
|
|
33782
|
+
uploadsContext
|
|
33515
33783
|
}) {
|
|
33516
33784
|
const [editorState, setEditorState] = useState(() => {
|
|
33517
33785
|
if (value && typeof value === "object" && value !== null) {
|
|
@@ -33581,7 +33849,7 @@ function LexicalEditor11({
|
|
|
33581
33849
|
onChange(newState);
|
|
33582
33850
|
}
|
|
33583
33851
|
};
|
|
33584
|
-
|
|
33852
|
+
const editorContent = /* @__PURE__ */ jsx(
|
|
33585
33853
|
Editor,
|
|
33586
33854
|
{
|
|
33587
33855
|
editorSerializedState: editorState,
|
|
@@ -33589,7 +33857,8 @@ function LexicalEditor11({
|
|
|
33589
33857
|
readOnly,
|
|
33590
33858
|
placeholder
|
|
33591
33859
|
}
|
|
33592
|
-
)
|
|
33860
|
+
);
|
|
33861
|
+
return /* @__PURE__ */ jsx("div", { className, children: uploadsContext ? /* @__PURE__ */ jsx(EditorUploadsProvider, { value: uploadsContext, children: editorContent }) : editorContent });
|
|
33593
33862
|
}
|
|
33594
33863
|
|
|
33595
33864
|
export { Editor, LexicalEditor11 as LexicalEditor };
|