@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/editor-x/editor.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';
|
|
@@ -5069,12 +5069,14 @@ var init_image_placeholder = __esm({
|
|
|
5069
5069
|
}
|
|
5070
5070
|
});
|
|
5071
5071
|
function ContentEditable({
|
|
5072
|
-
placeholder,
|
|
5072
|
+
placeholder = "",
|
|
5073
5073
|
className,
|
|
5074
5074
|
placeholderClassName,
|
|
5075
5075
|
placeholderDefaults = true
|
|
5076
5076
|
}) {
|
|
5077
5077
|
const isReadOnlyOrReview = className?.includes("--readonly") || className?.includes("--review");
|
|
5078
|
+
const text = placeholder.trim();
|
|
5079
|
+
const showLexicalPlaceholder = text.length > 0;
|
|
5078
5080
|
return /* @__PURE__ */ jsx(
|
|
5079
5081
|
ContentEditable$1,
|
|
5080
5082
|
{
|
|
@@ -5083,7 +5085,17 @@ function ContentEditable({
|
|
|
5083
5085
|
!isReadOnlyOrReview && "min-h-72 px-8 py-4",
|
|
5084
5086
|
className
|
|
5085
5087
|
),
|
|
5086
|
-
"aria-label": "Editor n\u1ED9i dung"
|
|
5088
|
+
"aria-label": "Editor n\u1ED9i dung",
|
|
5089
|
+
...showLexicalPlaceholder ? {
|
|
5090
|
+
"aria-placeholder": text,
|
|
5091
|
+
placeholder: /* @__PURE__ */ jsx(
|
|
5092
|
+
"div",
|
|
5093
|
+
{
|
|
5094
|
+
className: cn(placeholderDefaults && "editor-placeholder", placeholderClassName),
|
|
5095
|
+
children: text
|
|
5096
|
+
}
|
|
5097
|
+
)
|
|
5098
|
+
} : { placeholder: null }
|
|
5087
5099
|
}
|
|
5088
5100
|
);
|
|
5089
5101
|
}
|
|
@@ -6219,6 +6231,86 @@ var init_list_with_color_node = __esm({
|
|
|
6219
6231
|
};
|
|
6220
6232
|
}
|
|
6221
6233
|
});
|
|
6234
|
+
function $createDownloadLinkNode(url, download = null, attributes) {
|
|
6235
|
+
return new DownloadLinkNode(url, download, attributes);
|
|
6236
|
+
}
|
|
6237
|
+
function $isDownloadLinkNode(node) {
|
|
6238
|
+
return node instanceof DownloadLinkNode;
|
|
6239
|
+
}
|
|
6240
|
+
var DownloadLinkNode;
|
|
6241
|
+
var init_download_link_node = __esm({
|
|
6242
|
+
"src/nodes/download-link-node.tsx"() {
|
|
6243
|
+
DownloadLinkNode = class _DownloadLinkNode extends LinkNode {
|
|
6244
|
+
__download;
|
|
6245
|
+
static getType() {
|
|
6246
|
+
return "download-link";
|
|
6247
|
+
}
|
|
6248
|
+
static clone(node) {
|
|
6249
|
+
return new _DownloadLinkNode(
|
|
6250
|
+
node.getURL(),
|
|
6251
|
+
node.__download,
|
|
6252
|
+
{
|
|
6253
|
+
rel: node.getRel(),
|
|
6254
|
+
target: node.getTarget(),
|
|
6255
|
+
title: node.getTitle()
|
|
6256
|
+
},
|
|
6257
|
+
node.__key
|
|
6258
|
+
);
|
|
6259
|
+
}
|
|
6260
|
+
constructor(url, download = null, attributes, key) {
|
|
6261
|
+
super(url, attributes, key);
|
|
6262
|
+
this.__download = download;
|
|
6263
|
+
}
|
|
6264
|
+
getDownload() {
|
|
6265
|
+
return this.__download;
|
|
6266
|
+
}
|
|
6267
|
+
setDownload(download) {
|
|
6268
|
+
const writable = this.getWritable();
|
|
6269
|
+
writable.__download = download;
|
|
6270
|
+
return this;
|
|
6271
|
+
}
|
|
6272
|
+
createDOM(config) {
|
|
6273
|
+
const dom = super.createDOM(config);
|
|
6274
|
+
this.applyDownloadDOM(dom);
|
|
6275
|
+
return dom;
|
|
6276
|
+
}
|
|
6277
|
+
updateLinkDOM(prevNode, anchorElem, config) {
|
|
6278
|
+
super.updateLinkDOM(prevNode, anchorElem, config);
|
|
6279
|
+
this.applyDownloadDOM(anchorElem);
|
|
6280
|
+
}
|
|
6281
|
+
exportJSON() {
|
|
6282
|
+
return {
|
|
6283
|
+
...super.exportJSON(),
|
|
6284
|
+
type: _DownloadLinkNode.getType(),
|
|
6285
|
+
version: 1,
|
|
6286
|
+
download: this.__download
|
|
6287
|
+
};
|
|
6288
|
+
}
|
|
6289
|
+
static importJSON(serializedNode) {
|
|
6290
|
+
const node = new _DownloadLinkNode(
|
|
6291
|
+
serializedNode.url,
|
|
6292
|
+
serializedNode.download,
|
|
6293
|
+
{
|
|
6294
|
+
rel: serializedNode.rel ?? null,
|
|
6295
|
+
target: serializedNode.target ?? null,
|
|
6296
|
+
title: serializedNode.title ?? null
|
|
6297
|
+
},
|
|
6298
|
+
serializedNode.key
|
|
6299
|
+
);
|
|
6300
|
+
return node;
|
|
6301
|
+
}
|
|
6302
|
+
applyDownloadDOM(dom) {
|
|
6303
|
+
if (dom instanceof HTMLAnchorElement) {
|
|
6304
|
+
if (this.__download === null) {
|
|
6305
|
+
dom.removeAttribute("download");
|
|
6306
|
+
} else {
|
|
6307
|
+
dom.setAttribute("download", this.__download);
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
});
|
|
6222
6314
|
function $convertMentionElement(domNode) {
|
|
6223
6315
|
const textContent = domNode.textContent;
|
|
6224
6316
|
if (textContent !== null) {
|
|
@@ -6331,6 +6423,7 @@ var init_nodes = __esm({
|
|
|
6331
6423
|
init_layout_container_node();
|
|
6332
6424
|
init_layout_item_node();
|
|
6333
6425
|
init_list_with_color_node();
|
|
6426
|
+
init_download_link_node();
|
|
6334
6427
|
init_mention_node();
|
|
6335
6428
|
nodes = [
|
|
6336
6429
|
HeadingNode,
|
|
@@ -6346,6 +6439,7 @@ var init_nodes = __esm({
|
|
|
6346
6439
|
ListWithColorNode,
|
|
6347
6440
|
ListItemNode,
|
|
6348
6441
|
LinkNode,
|
|
6442
|
+
DownloadLinkNode,
|
|
6349
6443
|
OverflowNode,
|
|
6350
6444
|
HashtagNode,
|
|
6351
6445
|
TableNode,
|
|
@@ -27998,6 +28092,44 @@ var init_url = __esm({
|
|
|
27998
28092
|
);
|
|
27999
28093
|
}
|
|
28000
28094
|
});
|
|
28095
|
+
function shouldTreatUrlAsDownload(url) {
|
|
28096
|
+
if (typeof url !== "string") return false;
|
|
28097
|
+
const u = url.toLowerCase();
|
|
28098
|
+
if (u.includes("/api/uploads/") || u.includes("/uploads/") || u.includes("/api/admin/uploads/") || u.includes("/admin/uploads/"))
|
|
28099
|
+
return true;
|
|
28100
|
+
return /\.(pdf|doc|docx|xls|xlsx|csv|zip|rar|7z|txt|rtf|png|jpg|jpeg|gif|webp|mp3|wav|mp4|mov|avi)(\?.*)?$/.test(
|
|
28101
|
+
u
|
|
28102
|
+
);
|
|
28103
|
+
}
|
|
28104
|
+
function inferDownloadFileName(url) {
|
|
28105
|
+
try {
|
|
28106
|
+
const path = url.split("?")[0] ?? "";
|
|
28107
|
+
const last = path.split("/").filter(Boolean).pop();
|
|
28108
|
+
return last ? decodeURIComponent(last) : "download";
|
|
28109
|
+
} catch {
|
|
28110
|
+
return "download";
|
|
28111
|
+
}
|
|
28112
|
+
}
|
|
28113
|
+
function getCookieValue(name) {
|
|
28114
|
+
if (typeof document === "undefined") return null;
|
|
28115
|
+
const row = document.cookie.split("; ").find((item) => item.startsWith(`${name}=`));
|
|
28116
|
+
if (!row) return null;
|
|
28117
|
+
return row.split("=").slice(1).join("=") || null;
|
|
28118
|
+
}
|
|
28119
|
+
function buildHrefFromJsDownloadArg(jsArg) {
|
|
28120
|
+
const firstSegment = typeof window !== "undefined" ? window.location.pathname.split("/").filter(Boolean)[0] ?? "" : "";
|
|
28121
|
+
const serveBase = firstSegment === "admin" ? "/api/admin/uploads/serve" : "/api/uploads/serve";
|
|
28122
|
+
const arg = jsArg.trim();
|
|
28123
|
+
if (!arg) return "about:blank";
|
|
28124
|
+
if (/^https?:\/\//i.test(arg)) return arg;
|
|
28125
|
+
if (arg.startsWith("/api/")) return arg;
|
|
28126
|
+
if (arg.startsWith("images/") || arg.startsWith("files/")) {
|
|
28127
|
+
return `${serveBase}/${arg}`;
|
|
28128
|
+
}
|
|
28129
|
+
const m = arg.match(/(images|files)\/.+/i);
|
|
28130
|
+
if (m?.[0]) return `${serveBase}/${m[0]}`;
|
|
28131
|
+
return "about:blank";
|
|
28132
|
+
}
|
|
28001
28133
|
function FloatingLinkEditor({
|
|
28002
28134
|
editor,
|
|
28003
28135
|
isLink,
|
|
@@ -28011,6 +28143,9 @@ function FloatingLinkEditor({
|
|
|
28011
28143
|
const [linkUrl, setLinkUrl] = useState("");
|
|
28012
28144
|
const [editedLinkUrl, setEditedLinkUrl] = useState("https://");
|
|
28013
28145
|
const [lastSelection, setLastSelection] = useState(null);
|
|
28146
|
+
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
28147
|
+
const fileInputRef = useRef(null);
|
|
28148
|
+
const { onUploadFile } = useEditorUploads();
|
|
28014
28149
|
const $updateLinkEditor = useCallback(() => {
|
|
28015
28150
|
const selection = $getSelection();
|
|
28016
28151
|
let linkNode = null;
|
|
@@ -28204,9 +28339,11 @@ function FloatingLinkEditor({
|
|
|
28204
28339
|
setIsLinkEditMode(false);
|
|
28205
28340
|
}
|
|
28206
28341
|
};
|
|
28207
|
-
const handleLinkSubmission = () => {
|
|
28208
|
-
const
|
|
28209
|
-
|
|
28342
|
+
const handleLinkSubmission = (submittedUrl, originalFileName) => {
|
|
28343
|
+
const rawUrl = typeof submittedUrl === "string" ? submittedUrl : editedLinkUrl;
|
|
28344
|
+
const url = sanitizeUrl(rawUrl);
|
|
28345
|
+
const downloadFileName = originalFileName || (shouldTreatUrlAsDownload(url) ? inferDownloadFileName(url) : null);
|
|
28346
|
+
if (url && url !== "about:blank" && url !== "https://" && url !== "http://") {
|
|
28210
28347
|
editor.update(() => {
|
|
28211
28348
|
let selection = $getSelection();
|
|
28212
28349
|
if (!selection && lastSelection !== null) {
|
|
@@ -28231,8 +28368,11 @@ function FloatingLinkEditor({
|
|
|
28231
28368
|
const existingLinkNode = $findMatchingParent(node, $isLinkNode) || ($isLinkNode(node.getParent()) ? node.getParent() : null);
|
|
28232
28369
|
if (existingLinkNode) {
|
|
28233
28370
|
existingLinkNode.setURL(url);
|
|
28371
|
+
if (downloadFileName && $isDownloadLinkNode(existingLinkNode)) {
|
|
28372
|
+
existingLinkNode.setDownload(downloadFileName);
|
|
28373
|
+
}
|
|
28234
28374
|
} else {
|
|
28235
|
-
const linkNode = $createLinkNode(url);
|
|
28375
|
+
const linkNode = downloadFileName ? $createDownloadLinkNode(url, downloadFileName) : $createLinkNode(url);
|
|
28236
28376
|
$wrapNodeInElement(node, () => linkNode);
|
|
28237
28377
|
}
|
|
28238
28378
|
}
|
|
@@ -28241,25 +28381,114 @@ function FloatingLinkEditor({
|
|
|
28241
28381
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
|
|
28242
28382
|
const parent = getSelectedNode(selection).getParent();
|
|
28243
28383
|
if ($isAutoLinkNode(parent)) {
|
|
28244
|
-
const linkNode = $
|
|
28384
|
+
const linkNode = downloadFileName ? $createDownloadLinkNode(parent.getURL(), downloadFileName, {
|
|
28385
|
+
rel: parent.__rel,
|
|
28386
|
+
target: parent.__target,
|
|
28387
|
+
title: parent.__title
|
|
28388
|
+
}) : $createLinkNode(parent.getURL(), {
|
|
28245
28389
|
rel: parent.__rel,
|
|
28246
28390
|
target: parent.__target,
|
|
28247
28391
|
title: parent.__title
|
|
28248
28392
|
});
|
|
28249
28393
|
parent.replace(linkNode, true);
|
|
28250
28394
|
}
|
|
28395
|
+
if (downloadFileName) {
|
|
28396
|
+
const selectedNode = getSelectedNode(selection);
|
|
28397
|
+
const linkNode = $findMatchingParent(selectedNode, $isLinkNode) || ($isLinkNode(selectedNode) ? selectedNode : null);
|
|
28398
|
+
if (linkNode) {
|
|
28399
|
+
const currentText = linkNode.getTextContent();
|
|
28400
|
+
const targetText = originalFileName || downloadFileName;
|
|
28401
|
+
const downloadLinkNode = $createDownloadLinkNode(url, downloadFileName, {
|
|
28402
|
+
rel: linkNode.getRel(),
|
|
28403
|
+
target: linkNode.getTarget(),
|
|
28404
|
+
title: linkNode.getTitle()
|
|
28405
|
+
});
|
|
28406
|
+
if (currentText === url && targetText) {
|
|
28407
|
+
downloadLinkNode.append($createTextNode(targetText));
|
|
28408
|
+
} else {
|
|
28409
|
+
const children = linkNode.getChildren();
|
|
28410
|
+
if (children.length > 0) {
|
|
28411
|
+
downloadLinkNode.append(...children);
|
|
28412
|
+
}
|
|
28413
|
+
}
|
|
28414
|
+
linkNode.replace(downloadLinkNode);
|
|
28415
|
+
}
|
|
28416
|
+
}
|
|
28251
28417
|
}
|
|
28252
28418
|
});
|
|
28253
28419
|
setEditedLinkUrl("https://");
|
|
28254
28420
|
setIsLinkEditMode(false);
|
|
28255
28421
|
}
|
|
28256
28422
|
};
|
|
28423
|
+
const handlePickLocalFile = () => {
|
|
28424
|
+
if (isUploadingFile) return;
|
|
28425
|
+
fileInputRef.current?.click();
|
|
28426
|
+
};
|
|
28427
|
+
const handleUploadLocalFile = async (event) => {
|
|
28428
|
+
const file = event.target.files?.[0];
|
|
28429
|
+
if (!file) return;
|
|
28430
|
+
try {
|
|
28431
|
+
setIsUploadingFile(true);
|
|
28432
|
+
let uploadedUrl = void 0;
|
|
28433
|
+
if (onUploadFile) {
|
|
28434
|
+
const result = await onUploadFile(file);
|
|
28435
|
+
if (result.error) throw new Error(result.error);
|
|
28436
|
+
uploadedUrl = result.url;
|
|
28437
|
+
} else {
|
|
28438
|
+
const formData = new FormData();
|
|
28439
|
+
formData.append("file", file);
|
|
28440
|
+
const firstSegment = typeof window !== "undefined" ? window.location.pathname.split("/").filter(Boolean)[0] ?? "" : "";
|
|
28441
|
+
const pathPart = firstSegment === "admin" ? "/admin/uploads" : "/uploads";
|
|
28442
|
+
const endpoint = firstSegment === "admin" ? `/admin/api${pathPart}` : `/api${pathPart}`;
|
|
28443
|
+
const userId = getCookieValue("app_user_id");
|
|
28444
|
+
const authToken = getCookieValue("auth-token");
|
|
28445
|
+
const headers = {};
|
|
28446
|
+
if (userId) headers["X-User-Id"] = userId;
|
|
28447
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
28448
|
+
const res = await fetch(endpoint, {
|
|
28449
|
+
method: "POST",
|
|
28450
|
+
credentials: "include",
|
|
28451
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
28452
|
+
body: formData
|
|
28453
|
+
});
|
|
28454
|
+
if (!res.ok) {
|
|
28455
|
+
throw new Error(`Upload failed: HTTP ${res.status}`);
|
|
28456
|
+
}
|
|
28457
|
+
const payload = await res.json();
|
|
28458
|
+
uploadedUrl = payload?.data?.url;
|
|
28459
|
+
if (!payload?.success || !uploadedUrl) {
|
|
28460
|
+
throw new Error(payload?.message || payload?.error || "Upload failed");
|
|
28461
|
+
}
|
|
28462
|
+
}
|
|
28463
|
+
if (uploadedUrl) {
|
|
28464
|
+
setEditedLinkUrl(uploadedUrl);
|
|
28465
|
+
handleLinkSubmission(uploadedUrl, file.name);
|
|
28466
|
+
}
|
|
28467
|
+
} catch (error) {
|
|
28468
|
+
console.error("[FloatingLinkEditor] Upload local file failed:", error);
|
|
28469
|
+
} finally {
|
|
28470
|
+
setIsUploadingFile(false);
|
|
28471
|
+
if (fileInputRef.current) {
|
|
28472
|
+
fileInputRef.current.value = "";
|
|
28473
|
+
}
|
|
28474
|
+
}
|
|
28475
|
+
};
|
|
28257
28476
|
return /* @__PURE__ */ jsx(
|
|
28258
28477
|
"div",
|
|
28259
28478
|
{
|
|
28260
28479
|
ref: editorRef,
|
|
28261
28480
|
className: "editor-floating-link-editor",
|
|
28262
|
-
children: isLinkEditMode || isLink ? isLinkEditMode ? /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__input-container", children: [
|
|
28481
|
+
children: isLinkEditMode || isLink ? isLinkEditMode ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__input-container", children: [
|
|
28482
|
+
/* @__PURE__ */ jsx(
|
|
28483
|
+
"input",
|
|
28484
|
+
{
|
|
28485
|
+
ref: fileInputRef,
|
|
28486
|
+
type: "file",
|
|
28487
|
+
className: "hidden",
|
|
28488
|
+
onChange: handleUploadLocalFile,
|
|
28489
|
+
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"
|
|
28490
|
+
}
|
|
28491
|
+
),
|
|
28263
28492
|
/* @__PURE__ */ jsx(
|
|
28264
28493
|
Input,
|
|
28265
28494
|
{
|
|
@@ -28270,6 +28499,18 @@ function FloatingLinkEditor({
|
|
|
28270
28499
|
className: "editor-flex-grow"
|
|
28271
28500
|
}
|
|
28272
28501
|
),
|
|
28502
|
+
/* @__PURE__ */ jsx(
|
|
28503
|
+
Button,
|
|
28504
|
+
{
|
|
28505
|
+
size: "icon",
|
|
28506
|
+
variant: "ghost",
|
|
28507
|
+
onClick: handlePickLocalFile,
|
|
28508
|
+
className: "editor-shrink-0",
|
|
28509
|
+
disabled: isUploadingFile,
|
|
28510
|
+
title: isUploadingFile ? "Uploading..." : "Upload file t\u1EEB thi\u1EBFt b\u1ECB",
|
|
28511
|
+
children: isUploadingFile ? /* @__PURE__ */ jsx(Loader2, { className: "editor-icon-sm animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "editor-icon-sm" })
|
|
28512
|
+
}
|
|
28513
|
+
),
|
|
28273
28514
|
/* @__PURE__ */ jsx(
|
|
28274
28515
|
Button,
|
|
28275
28516
|
{
|
|
@@ -28287,22 +28528,39 @@ function FloatingLinkEditor({
|
|
|
28287
28528
|
Button,
|
|
28288
28529
|
{
|
|
28289
28530
|
size: "icon",
|
|
28290
|
-
onClick: handleLinkSubmission,
|
|
28531
|
+
onClick: () => handleLinkSubmission(),
|
|
28291
28532
|
className: "editor-shrink-0",
|
|
28292
28533
|
children: /* @__PURE__ */ jsx(Check, { className: "editor-icon-sm" })
|
|
28293
28534
|
}
|
|
28294
28535
|
)
|
|
28295
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__view-container", children: [
|
|
28296
|
-
|
|
28297
|
-
|
|
28298
|
-
|
|
28299
|
-
|
|
28300
|
-
|
|
28301
|
-
|
|
28302
|
-
|
|
28303
|
-
|
|
28536
|
+
] }) }) : /* @__PURE__ */ jsxs("div", { className: "editor-floating-link-editor__view-container", children: [
|
|
28537
|
+
(() => {
|
|
28538
|
+
let href = sanitizeUrl(linkUrl);
|
|
28539
|
+
const jsDownloadMatch = typeof linkUrl === "string" ? linkUrl.match(/^javascript:download\(\s*(['"])(.*?)\1\s*\)\s*$/i) : null;
|
|
28540
|
+
let downloadAttr;
|
|
28541
|
+
if (jsDownloadMatch) {
|
|
28542
|
+
const jsArg = jsDownloadMatch[2] ?? "";
|
|
28543
|
+
href = buildHrefFromJsDownloadArg(jsArg);
|
|
28544
|
+
if (href !== "about:blank") {
|
|
28545
|
+
downloadAttr = inferDownloadFileName(href);
|
|
28546
|
+
}
|
|
28547
|
+
} else if (shouldTreatUrlAsDownload(href)) {
|
|
28548
|
+
downloadAttr = inferDownloadFileName(href);
|
|
28304
28549
|
}
|
|
28305
|
-
|
|
28550
|
+
const isDownload = typeof downloadAttr === "string" && downloadAttr.length > 0;
|
|
28551
|
+
const text = jsDownloadMatch ? "Download" : shouldTreatUrlAsDownload(href) ? inferDownloadFileName(href) : href === "about:blank" ? "Invalid URL" : linkUrl;
|
|
28552
|
+
return /* @__PURE__ */ jsx(
|
|
28553
|
+
"a",
|
|
28554
|
+
{
|
|
28555
|
+
href,
|
|
28556
|
+
download: downloadAttr,
|
|
28557
|
+
target: isDownload ? "_self" : "_blank",
|
|
28558
|
+
rel: isDownload ? void 0 : "noopener noreferrer",
|
|
28559
|
+
className: "editor-floating-link-editor__link",
|
|
28560
|
+
children: /* @__PURE__ */ jsx(TypographyPSmall, { className: "editor-truncate", children: text })
|
|
28561
|
+
}
|
|
28562
|
+
);
|
|
28563
|
+
})(),
|
|
28306
28564
|
/* @__PURE__ */ jsxs(Flex, { gap: 0, className: "editor-shrink-0", children: [
|
|
28307
28565
|
/* @__PURE__ */ jsx(
|
|
28308
28566
|
Button,
|
|
@@ -28616,6 +28874,8 @@ var init_floating_link_editor_plugin = __esm({
|
|
|
28616
28874
|
init_flex();
|
|
28617
28875
|
init_typography();
|
|
28618
28876
|
init_image_node();
|
|
28877
|
+
init_download_link_node();
|
|
28878
|
+
init_uploads_context();
|
|
28619
28879
|
}
|
|
28620
28880
|
});
|
|
28621
28881
|
|