@underverse-ui/underverse 1.0.184 → 1.0.185

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/index.js CHANGED
@@ -891,6 +891,11 @@ var en_default = {
891
891
  borderColor: "Border Color",
892
892
  clearBorder: "Clear Border",
893
893
  formula: "Formula",
894
+ formulaBar: "Formula bar",
895
+ formulaError: "Formula error",
896
+ editFormula: "Edit formula",
897
+ clearCell: "Clear cell",
898
+ convertToValue: "Convert to value",
894
899
  numberFormat: "Number Format",
895
900
  formatText: "Text",
896
901
  formatDate: "Date",
@@ -1313,6 +1318,11 @@ var vi_default = {
1313
1318
  borderColor: "M\xE0u vi\u1EC1n",
1314
1319
  clearBorder: "X\xF3a vi\u1EC1n",
1315
1320
  formula: "C\xF4ng th\u1EE9c",
1321
+ formulaBar: "Thanh c\xF4ng th\u1EE9c",
1322
+ formulaError: "L\u1ED7i c\xF4ng th\u1EE9c",
1323
+ editFormula: "S\u1EEDa c\xF4ng th\u1EE9c",
1324
+ clearCell: "X\xF3a n\u1ED9i dung \xF4",
1325
+ convertToValue: "Chuy\u1EC3n th\xE0nh gi\xE1 tr\u1ECB",
1316
1326
  numberFormat: "\u0110\u1ECBnh d\u1EA1ng s\u1ED1",
1317
1327
  formatText: "Ch\u1EEF",
1318
1328
  formatDate: "Ng\xE0y",
@@ -1735,6 +1745,11 @@ var ko_default = {
1735
1745
  borderColor: "\uD14C\uB450\uB9AC \uC0C9\uC0C1",
1736
1746
  clearBorder: "\uD14C\uB450\uB9AC \uC9C0\uC6B0\uAE30",
1737
1747
  formula: "\uC218\uC2DD",
1748
+ formulaBar: "\uC218\uC2DD \uC785\uB825\uC904",
1749
+ formulaError: "\uC218\uC2DD \uC624\uB958",
1750
+ editFormula: "\uC218\uC2DD \uD3B8\uC9D1",
1751
+ clearCell: "\uC140 \uB0B4\uC6A9 \uC0AD\uC81C",
1752
+ convertToValue: "\uAC12\uC73C\uB85C \uBCC0\uD658",
1738
1753
  numberFormat: "\uD45C\uC2DC \uD615\uC2DD",
1739
1754
  formatText: "\uD14D\uC2A4\uD2B8",
1740
1755
  formatDate: "\uB0A0\uC9DC",
@@ -2157,6 +2172,11 @@ var ja_default = {
2157
2172
  borderColor: "\u67A0\u7DDA\u306E\u8272",
2158
2173
  clearBorder: "\u67A0\u7DDA\u3092\u30AF\u30EA\u30A2",
2159
2174
  formula: "\u6570\u5F0F",
2175
+ formulaBar: "\u6570\u5F0F\u30D0\u30FC",
2176
+ formulaError: "\u6570\u5F0F\u30A8\u30E9\u30FC",
2177
+ editFormula: "\u6570\u5F0F\u3092\u7DE8\u96C6",
2178
+ clearCell: "\u30BB\u30EB\u3092\u30AF\u30EA\u30A2",
2179
+ convertToValue: "\u5024\u306B\u5909\u63DB",
2160
2180
  numberFormat: "\u8868\u793A\u5F62\u5F0F",
2161
2181
  formatText: "\u30C6\u30AD\u30B9\u30C8",
2162
2182
  formatDate: "\u65E5\u4ED8",
@@ -27369,7 +27389,7 @@ function useLocale2() {
27369
27389
  }
27370
27390
 
27371
27391
  // src/components/UEditor/UEditor.tsx
27372
- import React87, { useEffect as useEffect40, useImperativeHandle as useImperativeHandle4, useMemo as useMemo27, useRef as useRef39 } from "react";
27392
+ import React88, { useEffect as useEffect41, useImperativeHandle as useImperativeHandle4, useMemo as useMemo27, useRef as useRef40 } from "react";
27373
27393
  import { useEditor, EditorContent } from "@tiptap/react";
27374
27394
 
27375
27395
  // src/components/UEditor/extensions.ts
@@ -27532,6 +27552,54 @@ import { ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
27532
27552
  import React72, { useEffect as useEffect32, useState as useState44 } from "react";
27533
27553
  import { NodeViewWrapper as NodeViewWrapper2 } from "@tiptap/react";
27534
27554
  import { AlertCircle as AlertCircle4, ExternalLink as ExternalLink2, Globe as Globe2, RefreshCw } from "lucide-react";
27555
+
27556
+ // src/components/UEditor/url-safety.ts
27557
+ var LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
27558
+ var IMAGE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
27559
+ var FILE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "blob:"]);
27560
+ function normalizeUrlInput(raw) {
27561
+ return raw.trim().replace(/[\u0000-\u001F\u007F\s]+/g, "");
27562
+ }
27563
+ function isProtocolRelativeUrl(value) {
27564
+ return value.startsWith("//");
27565
+ }
27566
+ function isRelativeUrl(value) {
27567
+ return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#");
27568
+ }
27569
+ function isDataImageUrl(value) {
27570
+ return /^data:image\/(?:png|jpe?g|gif|webp|svg\+xml|bmp|x-icon|avif);base64,/i.test(value);
27571
+ }
27572
+ function isDataFileUrl(value) {
27573
+ return /^data:[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*(?:;[a-z0-9!#$&^_.+-]+=[^;,]*)*;base64,[a-z0-9+/]*={0,2}$/i.test(value);
27574
+ }
27575
+ function isSafeUEditorUrl(raw, kind) {
27576
+ const value = normalizeUrlInput(raw);
27577
+ if (!value) return false;
27578
+ if (kind === "image" && isDataImageUrl(value)) return true;
27579
+ if (kind === "file" && isDataFileUrl(value)) return true;
27580
+ if (isProtocolRelativeUrl(value)) return false;
27581
+ if (isRelativeUrl(value)) return true;
27582
+ try {
27583
+ const parsed = new URL(value);
27584
+ if (kind === "image") return IMAGE_PROTOCOLS.has(parsed.protocol);
27585
+ if (kind === "file") return FILE_PROTOCOLS.has(parsed.protocol);
27586
+ return LINK_PROTOCOLS.has(parsed.protocol);
27587
+ } catch {
27588
+ return false;
27589
+ }
27590
+ }
27591
+ function sanitizeUEditorUrl(raw, kind) {
27592
+ const value = raw.trim();
27593
+ if (!value) return "";
27594
+ if (isSafeUEditorUrl(value, kind)) return normalizeUrlInput(value);
27595
+ if (kind === "link" && !isProtocolRelativeUrl(value) && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) {
27596
+ const withProtocol = `https://${value}`;
27597
+ return isSafeUEditorUrl(withProtocol, kind) ? withProtocol : "";
27598
+ }
27599
+ return "";
27600
+ }
27601
+
27602
+ // src/components/UEditor/BookmarkView.tsx
27535
27603
  import { jsx as jsx80, jsxs as jsxs65 } from "react/jsx-runtime";
27536
27604
  var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27537
27605
  const t = useSmartTranslations("UEditor");
@@ -27539,17 +27607,25 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27539
27607
  const [loading2, setLoading] = useState44(false);
27540
27608
  const [error, setError] = useState44(false);
27541
27609
  const [retryToken, setRetryToken] = useState44(0);
27542
- const fetchedRetryTokenRef = React72.useRef(-1);
27610
+ const lastFetchKeyRef = React72.useRef("");
27543
27611
  const fetchRequestIdRef = React72.useRef(0);
27612
+ const safeUrl = sanitizeUEditorUrl(String(url ?? ""), "link");
27613
+ const safeImage = sanitizeUEditorUrl(String(image ?? ""), "image");
27614
+ const clickToOpenNewTabMessage = t("bookmark.clickToOpenNewTab");
27615
+ const clickToOpenLinkMessage = t("bookmark.clickToOpenLink");
27616
+ const noDescriptionMessage = t("bookmark.noDescription");
27544
27617
  useEffect32(() => {
27545
27618
  if (title && retryToken === 0) return;
27546
- if (fetchedRetryTokenRef.current === retryToken) return;
27547
- fetchedRetryTokenRef.current = retryToken;
27619
+ if (!safeUrl) return;
27620
+ const fetchKey = `${safeUrl}\0${retryToken}`;
27621
+ if (lastFetchKeyRef.current === fetchKey) return;
27622
+ lastFetchKeyRef.current = fetchKey;
27548
27623
  const requestId = fetchRequestIdRef.current + 1;
27549
27624
  fetchRequestIdRef.current = requestId;
27625
+ let active = true;
27550
27626
  const safeUpdateAttributes = (attrs) => {
27551
27627
  Promise.resolve().then(() => {
27552
- if (fetchRequestIdRef.current === requestId && editor && !editor.isDestroyed) {
27628
+ if (active && fetchRequestIdRef.current === requestId && editor && !editor.isDestroyed) {
27553
27629
  updateAttributes(attrs);
27554
27630
  }
27555
27631
  });
@@ -27558,43 +27634,75 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27558
27634
  const fetchMetadata = extension?.options?.fetchMetadata;
27559
27635
  if (!fetchMetadata) {
27560
27636
  try {
27561
- const hostname = new URL(url).hostname;
27637
+ const hostname = new URL(safeUrl).hostname;
27562
27638
  safeUpdateAttributes({
27563
- title: hostname || url,
27564
- description: t("bookmark.clickToOpenNewTab"),
27639
+ title: hostname || safeUrl,
27640
+ description: clickToOpenNewTabMessage,
27565
27641
  publisher: hostname
27566
27642
  });
27567
27643
  } catch {
27568
27644
  safeUpdateAttributes({
27569
- title: url,
27570
- description: t("bookmark.clickToOpenLink"),
27645
+ title: safeUrl,
27646
+ description: clickToOpenLinkMessage,
27571
27647
  publisher: ""
27572
27648
  });
27573
27649
  }
27574
- return;
27650
+ return () => {
27651
+ active = false;
27652
+ };
27575
27653
  }
27576
- setLoading(true);
27577
- setError(false);
27578
- fetchMetadata(url).then((meta) => {
27579
- if (fetchRequestIdRef.current !== requestId) return;
27654
+ queueMicrotask(() => {
27655
+ if (!active) return;
27656
+ setLoading(true);
27657
+ setError(false);
27658
+ });
27659
+ fetchMetadata(safeUrl).then((meta) => {
27660
+ if (!active || fetchRequestIdRef.current !== requestId) return;
27661
+ const hostname = (() => {
27662
+ try {
27663
+ return new URL(safeUrl).hostname;
27664
+ } catch {
27665
+ return "";
27666
+ }
27667
+ })();
27580
27668
  safeUpdateAttributes({
27581
- title: meta.title || new URL(url).hostname || url,
27582
- description: meta.description || t("bookmark.noDescription"),
27583
- image: meta.image || "",
27584
- publisher: meta.publisher || new URL(url).hostname || ""
27669
+ title: meta.title || hostname || safeUrl,
27670
+ description: meta.description || noDescriptionMessage,
27671
+ image: meta.image ? sanitizeUEditorUrl(meta.image, "image") : "",
27672
+ publisher: meta.publisher || hostname
27585
27673
  });
27586
27674
  setLoading(false);
27587
27675
  }).catch(() => {
27588
- if (fetchRequestIdRef.current !== requestId) return;
27676
+ if (!active || fetchRequestIdRef.current !== requestId) return;
27589
27677
  setError(true);
27590
27678
  setLoading(false);
27591
27679
  });
27592
- return void 0;
27593
- }, [url, title, editor, updateAttributes, t, retryToken]);
27594
- const handleOpenLink = (e) => {
27595
- e.preventDefault();
27596
- e.stopPropagation();
27597
- window.open(url, "_blank", "noopener,noreferrer");
27680
+ return () => {
27681
+ active = false;
27682
+ };
27683
+ }, [
27684
+ safeUrl,
27685
+ title,
27686
+ editor,
27687
+ updateAttributes,
27688
+ retryToken,
27689
+ clickToOpenNewTabMessage,
27690
+ clickToOpenLinkMessage,
27691
+ noDescriptionMessage
27692
+ ]);
27693
+ const openLink = () => {
27694
+ if (safeUrl) window.open(safeUrl, "_blank", "noopener,noreferrer");
27695
+ };
27696
+ const handleOpenLink = (event) => {
27697
+ event.preventDefault();
27698
+ event.stopPropagation();
27699
+ openLink();
27700
+ };
27701
+ const handleKeyDown2 = (event) => {
27702
+ if (event.key !== "Enter") return;
27703
+ event.preventDefault();
27704
+ event.stopPropagation();
27705
+ openLink();
27598
27706
  };
27599
27707
  const handleRetryPreview = (event) => {
27600
27708
  event.preventDefault();
@@ -27603,7 +27711,7 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27603
27711
  };
27604
27712
  const displayPublisher = publisher || (() => {
27605
27713
  try {
27606
- return new URL(url).hostname;
27714
+ return safeUrl ? new URL(safeUrl).hostname : "";
27607
27715
  } catch {
27608
27716
  return "";
27609
27717
  }
@@ -27616,6 +27724,10 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27616
27724
  selected && "ring-2 ring-primary/45 border-primary/20 shadow-sm"
27617
27725
  ),
27618
27726
  onClick: handleOpenLink,
27727
+ onKeyDown: handleKeyDown2,
27728
+ role: "link",
27729
+ tabIndex: safeUrl ? 0 : -1,
27730
+ "aria-disabled": !safeUrl,
27619
27731
  children: loading2 ? /* @__PURE__ */ jsxs65("div", { className: "flex items-center justify-between p-4 gap-4 animate-pulse", children: [
27620
27732
  /* @__PURE__ */ jsxs65("div", { className: "flex-1 flex flex-col gap-2", children: [
27621
27733
  /* @__PURE__ */ jsx80("div", { className: "h-4 w-1/3 bg-muted-foreground/20 rounded" }),
@@ -27630,11 +27742,11 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27630
27742
  /* @__PURE__ */ jsx80(Globe2, { className: "h-3 w-3 flex-shrink-0" }),
27631
27743
  displayPublisher
27632
27744
  ] }),
27633
- /* @__PURE__ */ jsx80("h4", { className: "text-sm font-semibold text-foreground line-clamp-1 break-all", children: title || url }),
27745
+ /* @__PURE__ */ jsx80("h4", { className: "text-sm font-semibold text-foreground line-clamp-1 break-all", children: title || safeUrl || String(url ?? "") }),
27634
27746
  /* @__PURE__ */ jsx80("p", { className: "text-xs text-muted-foreground line-clamp-2 leading-relaxed", children: description })
27635
27747
  ] }),
27636
27748
  /* @__PURE__ */ jsxs65("span", { className: "text-[11px] text-primary/80 font-mono mt-2 truncate flex items-center gap-1", children: [
27637
- url,
27749
+ safeUrl || String(url ?? ""),
27638
27750
  /* @__PURE__ */ jsx80(ExternalLink2, { className: "h-3 w-3 flex-shrink-0" })
27639
27751
  ] }),
27640
27752
  error && /* @__PURE__ */ jsxs65("div", { className: "mt-2 flex flex-wrap items-center gap-2 text-xs text-destructive", children: [
@@ -27656,10 +27768,10 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27656
27768
  )
27657
27769
  ] })
27658
27770
  ] }),
27659
- image && /* @__PURE__ */ jsx80("div", { className: "relative w-full sm:w-44 h-32 sm:h-auto flex-shrink-0 bg-muted overflow-hidden border-t sm:border-t-0 sm:border-l border-border/50", children: /* @__PURE__ */ jsx80(
27771
+ safeImage && /* @__PURE__ */ jsx80("div", { className: "relative w-full sm:w-44 h-32 sm:h-auto flex-shrink-0 bg-muted overflow-hidden border-t sm:border-t-0 sm:border-l border-border/50", children: /* @__PURE__ */ jsx80(
27660
27772
  "img",
27661
27773
  {
27662
- src: image,
27774
+ src: safeImage,
27663
27775
  alt: title || "Link preview image",
27664
27776
  className: "absolute inset-0 w-full h-full object-cover transition-opacity duration-300",
27665
27777
  onError: (e) => {
@@ -27689,11 +27801,11 @@ var Bookmark = Node3.create({
27689
27801
  return {
27690
27802
  url: {
27691
27803
  default: "",
27692
- parseHTML: (element) => element.getAttribute("href") || element.getAttribute("data-url") || "",
27693
- renderHTML: (attributes) => ({
27694
- href: attributes.url,
27695
- "data-url": attributes.url
27696
- })
27804
+ parseHTML: (element) => sanitizeUEditorUrl(element.getAttribute("href") || element.getAttribute("data-url") || "", "link"),
27805
+ renderHTML: (attributes) => {
27806
+ const url = sanitizeUEditorUrl(String(attributes.url ?? ""), "link");
27807
+ return url ? { href: url, "data-url": url } : {};
27808
+ }
27697
27809
  },
27698
27810
  title: {
27699
27811
  default: "",
@@ -27711,10 +27823,11 @@ var Bookmark = Node3.create({
27711
27823
  },
27712
27824
  image: {
27713
27825
  default: "",
27714
- parseHTML: (element) => element.getAttribute("data-image") || "",
27715
- renderHTML: (attributes) => ({
27716
- "data-image": attributes.image
27717
- })
27826
+ parseHTML: (element) => sanitizeUEditorUrl(element.getAttribute("data-image") || "", "image"),
27827
+ renderHTML: (attributes) => {
27828
+ const image = sanitizeUEditorUrl(String(attributes.image ?? ""), "image");
27829
+ return image ? { "data-image": image } : {};
27830
+ }
27718
27831
  },
27719
27832
  publisher: {
27720
27833
  default: "",
@@ -27747,9 +27860,12 @@ var Bookmark = Node3.create({
27747
27860
  addCommands() {
27748
27861
  return {
27749
27862
  setBookmark: (attributes) => ({ commands }) => {
27863
+ const url = sanitizeUEditorUrl(attributes.url, "link");
27864
+ if (!url) return false;
27865
+ const image = attributes.image ? sanitizeUEditorUrl(attributes.image, "image") : "";
27750
27866
  return commands.insertContent({
27751
27867
  type: this.name,
27752
- attrs: attributes
27868
+ attrs: { ...attributes, url, image }
27753
27869
  });
27754
27870
  }
27755
27871
  };
@@ -27766,7 +27882,6 @@ var Bookmark = Node3.create({
27766
27882
  if (!text || !URL_REGEX.test(text)) return false;
27767
27883
  const { selection } = view.state;
27768
27884
  const parent = selection.$from.parent;
27769
- console.log("DEBUG handlePaste name:", parent.type.name, "size:", parent.content.size);
27770
27885
  const isEmptyParagraph = parent.type.name === "paragraph" && parent.content.size === 0;
27771
27886
  if (isEmptyParagraph) {
27772
27887
  event.preventDefault();
@@ -27794,15 +27909,15 @@ import { NodeViewWrapper as NodeViewWrapper3 } from "@tiptap/react";
27794
27909
  import { Download as Download2, File as LucideFile, FileText as FileText2, Image as Image3, Music, RefreshCw as RefreshCw2, Video } from "lucide-react";
27795
27910
  import { jsx as jsx81, jsxs as jsxs66 } from "react/jsx-runtime";
27796
27911
  function formatBytes(bytes, decimals = 1) {
27797
- if (!bytes || bytes <= 0) return "0 Bytes";
27912
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 Bytes";
27798
27913
  const k = 1024;
27799
27914
  const dm = decimals < 0 ? 0 : decimals;
27800
- const sizes = ["Bytes", "KB", "MB", "GB"];
27801
- const i = Math.floor(Math.log(bytes) / Math.log(k));
27915
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
27916
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
27802
27917
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
27803
27918
  }
27804
27919
  function getFileIcon2(type) {
27805
- const mime = (type || "").toLowerCase();
27920
+ const mime = String(type || "").toLowerCase();
27806
27921
  if (mime.includes("pdf")) return FileText2;
27807
27922
  if (mime.startsWith("image/")) return Image3;
27808
27923
  if (mime.startsWith("audio/")) return Music;
@@ -27828,11 +27943,13 @@ function dataURLtoFile(dataurl, filename, mimeType) {
27828
27943
  }
27829
27944
  }
27830
27945
  return new File([bytes], filename, { type: mime });
27831
- } catch (e) {
27832
- console.error("Error converting data URI to file:", e);
27946
+ } catch {
27833
27947
  return null;
27834
27948
  }
27835
27949
  }
27950
+ function getErrorMessage(error) {
27951
+ return error instanceof Error && error.message ? error.message : "Upload failed";
27952
+ }
27836
27953
  var FileCardView = ({ node, selected, editor, updateAttributes }) => {
27837
27954
  const t = useSmartTranslations("UEditor");
27838
27955
  const { src, fileName, fileSize, fileType } = node.attrs;
@@ -27840,49 +27957,64 @@ var FileCardView = ({ node, selected, editor, updateAttributes }) => {
27840
27957
  const [uploadError, setUploadError] = useState45(null);
27841
27958
  const [retryToken, setRetryToken] = useState45(0);
27842
27959
  const uploadAttemptedRef = useRef28(false);
27960
+ const lastUploadSrcRef = useRef28("");
27961
+ const uploadRequestIdRef = useRef28(0);
27962
+ const safeSrc = sanitizeUEditorUrl(String(src ?? ""), "file");
27963
+ const extension = editor?.extensionManager?.extensions?.find((item) => item.name === "fileCard");
27964
+ const uploadFn = extension?.options?.upload;
27843
27965
  useEffect33(() => {
27844
- if (!src || !src.startsWith("data:")) {
27966
+ if (lastUploadSrcRef.current !== safeSrc) {
27967
+ lastUploadSrcRef.current = safeSrc;
27968
+ uploadAttemptedRef.current = false;
27969
+ }
27970
+ if (!safeSrc || !safeSrc.startsWith("data:") || !uploadFn) {
27845
27971
  return;
27846
27972
  }
27847
- const extension = editor?.extensionManager?.extensions?.find((e) => e.name === "fileCard");
27848
- const uploadFn = extension?.options?.upload;
27849
- if (!uploadFn || uploadAttemptedRef.current || !updateAttributes) {
27973
+ if (uploadAttemptedRef.current || !updateAttributes) {
27850
27974
  return;
27851
27975
  }
27852
27976
  uploadAttemptedRef.current = true;
27853
27977
  setUploadError(null);
27978
+ const requestId = uploadRequestIdRef.current + 1;
27979
+ uploadRequestIdRef.current = requestId;
27980
+ let active = true;
27854
27981
  const runUpload = async () => {
27855
27982
  try {
27856
- const file = dataURLtoFile(src, fileName || "file", fileType);
27983
+ const file = dataURLtoFile(safeSrc, fileName || "file", fileType);
27857
27984
  if (!file) {
27858
27985
  throw new Error("Failed to parse file data");
27859
27986
  }
27860
27987
  const resolvedUrl = await uploadFn(file);
27988
+ const nextSrc = sanitizeUEditorUrl(String(resolvedUrl ?? ""), "file");
27989
+ if (!nextSrc) throw new Error("Upload handler returned an unsafe URL");
27861
27990
  Promise.resolve().then(() => {
27862
- if (editor && !editor.isDestroyed) {
27863
- updateAttributes({ src: resolvedUrl });
27991
+ if (active && uploadRequestIdRef.current === requestId && editor && !editor.isDestroyed) {
27992
+ updateAttributes({ src: nextSrc });
27864
27993
  }
27865
27994
  });
27866
- } catch (err) {
27867
- console.error("File upload failed:", err);
27868
- setUploadError(err?.message || "Upload failed");
27995
+ } catch (error) {
27996
+ if (!active || uploadRequestIdRef.current !== requestId) return;
27997
+ setUploadError(getErrorMessage(error));
27869
27998
  uploadAttemptedRef.current = false;
27870
27999
  }
27871
28000
  };
27872
28001
  void runUpload();
27873
- }, [src, fileName, fileType, editor, updateAttributes, retryToken]);
28002
+ return () => {
28003
+ active = false;
28004
+ };
28005
+ }, [safeSrc, fileName, fileType, editor, updateAttributes, uploadFn, retryToken]);
27874
28006
  const handleDownload = (e) => {
27875
28007
  e.preventDefault();
27876
28008
  e.stopPropagation();
27877
- if (!src) return;
28009
+ if (!safeSrc) return;
27878
28010
  const link = document.createElement("a");
27879
- link.href = src;
28011
+ link.href = safeSrc;
27880
28012
  link.download = fileName || "download";
27881
28013
  document.body.appendChild(link);
27882
28014
  link.click();
27883
28015
  document.body.removeChild(link);
27884
28016
  };
27885
- const isUploading = (src || "").startsWith("data:");
28017
+ const hasInlineUpload = safeSrc.startsWith("data:") && Boolean(uploadFn);
27886
28018
  const handleRetryUpload = (event) => {
27887
28019
  event.preventDefault();
27888
28020
  event.stopPropagation();
@@ -27905,11 +28037,11 @@ var FileCardView = ({ node, selected, editor, updateAttributes }) => {
27905
28037
  /* @__PURE__ */ jsx81("div", { className: "text-sm font-semibold text-foreground truncate break-all", children: fileName || t("fileCard.unnamed") }),
27906
28038
  /* @__PURE__ */ jsxs66("div", { className: "text-xs text-muted-foreground flex items-center gap-2 mt-0.5", children: [
27907
28039
  fileSize ? /* @__PURE__ */ jsx81("span", { children: formatBytes(fileSize) }) : null,
27908
- isUploading && /* @__PURE__ */ jsx81("span", { className: cn(
28040
+ hasInlineUpload && /* @__PURE__ */ jsx81("span", { className: cn(
27909
28041
  "font-medium truncate max-w-50",
27910
28042
  uploadError ? "text-destructive" : "text-primary animate-pulse"
27911
28043
  ), children: uploadError ? `${t("fileCard.uploadFailed") || "T\u1EA3i l\xEAn th\u1EA5t b\u1EA1i"}: ${uploadError}` : t("fileCard.uploading") }),
27912
- isUploading && uploadError && /* @__PURE__ */ jsxs66(
28044
+ hasInlineUpload && uploadError && /* @__PURE__ */ jsxs66(
27913
28045
  "button",
27914
28046
  {
27915
28047
  type: "button",
@@ -27924,7 +28056,7 @@ var FileCardView = ({ node, selected, editor, updateAttributes }) => {
27924
28056
  ] })
27925
28057
  ] })
27926
28058
  ] }),
27927
- src && /* @__PURE__ */ jsx81(
28059
+ safeSrc && /* @__PURE__ */ jsx81(
27928
28060
  "button",
27929
28061
  {
27930
28062
  type: "button",
@@ -27953,11 +28085,11 @@ var FileCard = Node4.create({
27953
28085
  return {
27954
28086
  src: {
27955
28087
  default: "",
27956
- parseHTML: (element) => element.getAttribute("href") || element.getAttribute("data-src") || "",
27957
- renderHTML: (attributes) => ({
27958
- href: attributes.src,
27959
- "data-src": attributes.src
27960
- })
28088
+ parseHTML: (element) => sanitizeUEditorUrl(element.getAttribute("href") || element.getAttribute("data-src") || "", "file"),
28089
+ renderHTML: (attributes) => {
28090
+ const src = sanitizeUEditorUrl(String(attributes.src ?? ""), "file");
28091
+ return src ? { href: src, "data-src": src } : {};
28092
+ }
27961
28093
  },
27962
28094
  fileName: {
27963
28095
  default: "",
@@ -27969,13 +28101,15 @@ var FileCard = Node4.create({
27969
28101
  fileSize: {
27970
28102
  default: null,
27971
28103
  parseHTML: (element) => {
27972
- const val = element.getAttribute("data-file-size");
27973
- return val ? Number(val) : null;
28104
+ const rawValue = element.getAttribute("data-file-size");
28105
+ const value = rawValue ? Number(rawValue) : Number.NaN;
28106
+ return Number.isFinite(value) && value >= 0 ? value : null;
27974
28107
  },
27975
28108
  renderHTML: (attributes) => {
27976
- if (attributes.fileSize === null || attributes.fileSize === void 0) return {};
28109
+ const fileSize = Number(attributes.fileSize);
28110
+ if (!Number.isFinite(fileSize) || fileSize < 0) return {};
27977
28111
  return {
27978
- "data-file-size": attributes.fileSize
28112
+ "data-file-size": fileSize
27979
28113
  };
27980
28114
  }
27981
28115
  },
@@ -28010,9 +28144,16 @@ var FileCard = Node4.create({
28010
28144
  addCommands() {
28011
28145
  return {
28012
28146
  setFileCard: (attributes) => ({ commands }) => {
28147
+ const src = sanitizeUEditorUrl(attributes.src, "file");
28148
+ if (!src) return false;
28149
+ const fileSize = Number(attributes.fileSize);
28013
28150
  return commands.insertContent({
28014
28151
  type: this.name,
28015
- attrs: attributes
28152
+ attrs: {
28153
+ ...attributes,
28154
+ src,
28155
+ fileSize: Number.isFinite(fileSize) && fileSize >= 0 ? fileSize : null
28156
+ }
28016
28157
  });
28017
28158
  }
28018
28159
  };
@@ -28443,6 +28584,10 @@ function destroyTippyInstance(instance) {
28443
28584
  if (!instance || instance.state.isDestroyed) return;
28444
28585
  instance.destroy();
28445
28586
  }
28587
+ function setTippyReferenceClientRect(instance, clientRect) {
28588
+ if (!instance || instance.state.isDestroyed) return;
28589
+ instance.setProps({ getReferenceClientRect: clientRect });
28590
+ }
28446
28591
 
28447
28592
  // src/components/UEditor/slash-command.tsx
28448
28593
  import { jsx as jsx84, jsxs as jsxs68 } from "react/jsx-runtime";
@@ -28815,9 +28960,7 @@ var SlashCommand = Extension.create({
28815
28960
  if (!props.clientRect) {
28816
28961
  return;
28817
28962
  }
28818
- popup?.[0]?.setProps({
28819
- getReferenceClientRect: props.clientRect
28820
- });
28963
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
28821
28964
  },
28822
28965
  onKeyDown(props) {
28823
28966
  if (!props || !props.event) return false;
@@ -29554,45 +29697,6 @@ function getClipboardTsvTableContent(dataTransfer) {
29554
29697
  );
29555
29698
  }
29556
29699
 
29557
- // src/components/UEditor/url-safety.ts
29558
- var LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
29559
- var IMAGE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
29560
- function normalizeUrlInput(raw) {
29561
- return raw.trim().replace(/[\u0000-\u001F\u007F\s]+/g, "");
29562
- }
29563
- function isProtocolRelativeUrl(value) {
29564
- return value.startsWith("//");
29565
- }
29566
- function isRelativeUrl(value) {
29567
- return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#");
29568
- }
29569
- function isDataImageUrl(value) {
29570
- return /^data:image\/(?:png|jpe?g|gif|webp|svg\+xml|bmp|x-icon|avif);base64,/i.test(value);
29571
- }
29572
- function isSafeUEditorUrl(raw, kind) {
29573
- const value = normalizeUrlInput(raw);
29574
- if (!value) return false;
29575
- if (kind === "image" && isDataImageUrl(value)) return true;
29576
- if (isRelativeUrl(value)) return true;
29577
- if (isProtocolRelativeUrl(value)) return false;
29578
- try {
29579
- const parsed = new URL(value);
29580
- return kind === "image" ? IMAGE_PROTOCOLS.has(parsed.protocol) : LINK_PROTOCOLS.has(parsed.protocol);
29581
- } catch {
29582
- return false;
29583
- }
29584
- }
29585
- function sanitizeUEditorUrl(raw, kind) {
29586
- const value = raw.trim();
29587
- if (!value) return "";
29588
- if (isSafeUEditorUrl(value, kind)) return normalizeUrlInput(value);
29589
- if (kind === "link" && !isProtocolRelativeUrl(value) && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) {
29590
- const withProtocol = `https://${value}`;
29591
- return isSafeUEditorUrl(withProtocol, kind) ? withProtocol : "";
29592
- }
29593
- return "";
29594
- }
29595
-
29596
29700
  // src/components/UEditor/clipboard-images.ts
29597
29701
  var DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE = 10 * 1024 * 1024;
29598
29702
  var DEFAULT_UEDITOR_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"];
@@ -29834,9 +29938,7 @@ var EmojiSuggestion = Extension3.create({
29834
29938
  if (!props.clientRect) {
29835
29939
  return;
29836
29940
  }
29837
- popup?.[0]?.setProps({
29838
- getReferenceClientRect: props.clientRect
29839
- });
29941
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
29840
29942
  },
29841
29943
  onKeyDown(props) {
29842
29944
  if (!props || !props.event) return false;
@@ -29933,7 +30035,7 @@ var FormulaSuggestionList = forwardRef16((props, ref) => {
29933
30035
  setSelectedIndex((prev) => (prev + 1) % props.items.length);
29934
30036
  return true;
29935
30037
  }
29936
- if (event.key === "Enter" || event.key === "Tab") {
30038
+ if (event.key === "Tab") {
29937
30039
  const item = props.items[selectedIndex];
29938
30040
  if (item) {
29939
30041
  props.command(item);
@@ -30023,9 +30125,7 @@ var FormulaSuggestion = Extension4.create({
30023
30125
  if (!props.clientRect) {
30024
30126
  return;
30025
30127
  }
30026
- popup?.[0]?.setProps({
30027
- getReferenceClientRect: props.clientRect
30028
- });
30128
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
30029
30129
  },
30030
30130
  onKeyDown(props) {
30031
30131
  const popupInstance = getFirstTippyInstance(popup);
@@ -31776,7 +31876,6 @@ function buildUEditorExtensions({
31776
31876
  Gapcursor,
31777
31877
  Link.configure({
31778
31878
  openOnClick: false,
31779
- protocols: ["http", "https", "mailto", "tel"],
31780
31879
  isAllowedUri: (url) => isSafeUEditorUrl(url ?? "", "link"),
31781
31880
  HTMLAttributes: {
31782
31881
  class: "text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer"
@@ -32868,7 +32967,7 @@ var EditorToolbar = ({
32868
32967
  const [showImageInput, setShowImageInput] = useState49(false);
32869
32968
  const [showLinkInput, setShowLinkInput] = useState49(false);
32870
32969
  const [isTableMenuOpen, setIsTableMenuOpen] = useState49(false);
32871
- const tableCommandAnchorPosRef = useRef33(null);
32970
+ const [preservedTableAnchorPos, setPreservedTableAnchorPos] = useState49(null);
32872
32971
  const fileInputRef = useRef33(null);
32873
32972
  const [isUploadingImage, setIsUploadingImage] = useState49(false);
32874
32973
  const [imageUploadError, setImageUploadError] = useState49(null);
@@ -32883,7 +32982,7 @@ var EditorToolbar = ({
32883
32982
  const tableAlignAttr = tableAttrs.tableAlign ?? tableAttrs.textAlign;
32884
32983
  const currentTableAlign = tableAlignAttr === "center" || tableAlignAttr === "right" ? tableAlignAttr : "left";
32885
32984
  const isTableSelected = tableInfo !== null;
32886
- const hasTableContext = isTableSelected || tableCommandAnchorPosRef.current !== null;
32985
+ const hasTableContext = isTableSelected || preservedTableAnchorPos !== null;
32887
32986
  const canMergeCells = hasTableContext && editor.can().mergeCells();
32888
32987
  const canSplitCell = hasTableContext && editor.can().splitCell();
32889
32988
  const currentCellVerticalAlign = normalizeStyleValue(editor.getAttributes("tableCell").verticalAlign || editor.getAttributes("tableHeader").verticalAlign) || "";
@@ -32908,7 +33007,7 @@ var EditorToolbar = ({
32908
33007
  const displayedFontFamilyValue = currentFontFamily || defaultFontFamilyValue;
32909
33008
  const displayedFontSizeLabel = currentFontSize ? currentFontSizeLabel : "13";
32910
33009
  const activeFontSize = currentFontSize || "13px";
32911
- const tableCommandAnchorPos = tableCommandAnchorPosRef.current ?? tableAnchorPos ?? void 0;
33010
+ const tableCommandAnchorPos = preservedTableAnchorPos ?? tableAnchorPos ?? void 0;
32912
33011
  const isMedium = variant === "medium";
32913
33012
  const isMediumFull = variant === "medium-full";
32914
33013
  const isFull = variant === "default" || variant === "full" || variant === "notion" || !variant;
@@ -33584,7 +33683,7 @@ var EditorToolbar = ({
33584
33683
  isOpen: isTableMenuOpen,
33585
33684
  onOpenChange: (open) => {
33586
33685
  setIsTableMenuOpen(open);
33587
- tableCommandAnchorPosRef.current = open ? getTableAnchorPos(editor) : null;
33686
+ setPreservedTableAnchorPos(open ? getTableAnchorPos(editor) : null);
33588
33687
  },
33589
33688
  trigger: /* @__PURE__ */ jsxs75(ToolbarButton, { onClick: () => {
33590
33689
  }, title: t("toolbar.table"), children: [
@@ -33601,6 +33700,7 @@ var EditorToolbar = ({
33601
33700
  onInsert: (rows, cols) => {
33602
33701
  editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
33603
33702
  setIsTableMenuOpen(false);
33703
+ setPreservedTableAnchorPos(null);
33604
33704
  }
33605
33705
  }
33606
33706
  ),
@@ -33816,6 +33916,7 @@ import {
33816
33916
  } from "lucide-react";
33817
33917
 
33818
33918
  // src/components/UEditor/table-formula-commands.ts
33919
+ import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
33819
33920
  import { selectedRect as selectedRect2, setCellAttr as setCellAttr2, TableMap as TableMap3 } from "@tiptap/pm/tables";
33820
33921
 
33821
33922
  // src/components/UEditor/table-formula.ts
@@ -34379,6 +34480,75 @@ function getSelectionTableCellNode(editor) {
34379
34480
  }
34380
34481
  return null;
34381
34482
  }
34483
+ function getSelectionTableCellPos(editor) {
34484
+ const cellSelection = editor.state.selection;
34485
+ const anchorCellPos = cellSelection.$anchorCell?.pos;
34486
+ if (typeof anchorCellPos === "number") {
34487
+ const node = editor.state.doc.nodeAt(anchorCellPos);
34488
+ if (node?.type.name === "tableCell" || node?.type.name === "tableHeader") {
34489
+ return anchorCellPos;
34490
+ }
34491
+ }
34492
+ const { $from } = editor.state.selection;
34493
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
34494
+ const node = $from.node(depth);
34495
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
34496
+ return $from.before(depth);
34497
+ }
34498
+ }
34499
+ return null;
34500
+ }
34501
+ function getSelectionTableCellAnchor(editor) {
34502
+ const cellPos = getSelectionTableCellPos(editor);
34503
+ if (cellPos == null) return null;
34504
+ const node = editor.state.doc.nodeAt(cellPos);
34505
+ if (!node || node.type.name !== "tableCell" && node.type.name !== "tableHeader") return null;
34506
+ const $cell = editor.state.doc.resolve(Math.min(cellPos + 1, editor.state.doc.content.size));
34507
+ for (let depth = $cell.depth; depth > 0; depth -= 1) {
34508
+ const tableNode = $cell.node(depth);
34509
+ if (tableNode.type.name !== "table") continue;
34510
+ const tableStart = $cell.start(depth);
34511
+ const rect = safeFindCell2(TableMap3.get(tableNode), cellPos - tableStart);
34512
+ if (!rect) return null;
34513
+ return {
34514
+ cellPos,
34515
+ column: rect.left,
34516
+ label: `${indexToColumnName(rect.left)}${rect.top + 1}`,
34517
+ node,
34518
+ row: rect.top,
34519
+ tablePos: tableStart - 1
34520
+ };
34521
+ }
34522
+ return null;
34523
+ }
34524
+ function restoreSelectionAtTableCell(editor, anchor) {
34525
+ const tableNode = editor.state.doc.nodeAt(anchor.tablePos);
34526
+ if (!tableNode || tableNode.type.name !== "table") return false;
34527
+ const map = TableMap3.get(tableNode);
34528
+ if (anchor.row >= map.height || anchor.column >= map.width) return false;
34529
+ const relativeCellPos = map.positionAt(anchor.row, anchor.column, tableNode);
34530
+ const cellPos = anchor.tablePos + 1 + relativeCellPos;
34531
+ const cellNode = editor.state.doc.nodeAt(cellPos);
34532
+ if (!cellNode || cellNode.type.name !== "tableCell" && cellNode.type.name !== "tableHeader") return false;
34533
+ const selectionPos = Math.min(cellPos + 2, editor.state.doc.content.size);
34534
+ editor.view.dispatch(
34535
+ editor.state.tr.setSelection(TextSelection3.near(editor.state.doc.resolve(selectionPos))).setMeta("addToHistory", false)
34536
+ );
34537
+ return true;
34538
+ }
34539
+ function getSelectedTableFormulaCell(editor) {
34540
+ const selectedCell = getSelectionTableCellAnchor(editor);
34541
+ if (!selectedCell) return null;
34542
+ const formula = typeof selectedCell.node.attrs.formula === "string" ? selectedCell.node.attrs.formula.trim() : "";
34543
+ if (!formula) return null;
34544
+ const computedValue = typeof selectedCell.node.attrs.computedValue === "string" ? selectedCell.node.attrs.computedValue : "";
34545
+ return {
34546
+ ...selectedCell,
34547
+ computedValue,
34548
+ formula,
34549
+ formulaState: computedValue.startsWith("#") ? "error" : "computed"
34550
+ };
34551
+ }
34382
34552
  function getSelectionTableInfo(editor) {
34383
34553
  const { $from } = editor.state.selection;
34384
34554
  for (let depth = $from.depth; depth > 0; depth -= 1) {
@@ -34425,6 +34595,28 @@ function createCellDisplayContent(cellNode, displayValue) {
34425
34595
  }
34426
34596
  return [paragraphType.create(null, cellNode.type.schema.text(displayValue))];
34427
34597
  }
34598
+ function removeSelectedTableCellFormula(editor, clearContent) {
34599
+ const selectedCell = getSelectedTableFormulaCell(editor);
34600
+ if (!selectedCell) return false;
34601
+ const nextCell2 = selectedCell.node.type.create(
34602
+ {
34603
+ ...selectedCell.node.attrs,
34604
+ formula: null,
34605
+ computedValue: null
34606
+ },
34607
+ clearContent ? createCellDisplayContent(selectedCell.node, "") : selectedCell.node.content,
34608
+ selectedCell.node.marks
34609
+ );
34610
+ let transaction = editor.state.tr.replaceWith(selectedCell.cellPos, selectedCell.cellPos + selectedCell.node.nodeSize, nextCell2).setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true);
34611
+ const selectionPos = Math.min(selectedCell.cellPos + 2, transaction.doc.content.size);
34612
+ transaction = transaction.setSelection(TextSelection3.near(transaction.doc.resolve(selectionPos)));
34613
+ editor.view.dispatch(transaction);
34614
+ recalculateActiveTableFormulas(editor);
34615
+ restoreSelectionAtTableCell(editor, selectedCell);
34616
+ editor.view.focus();
34617
+ dispatchTableLayoutChange(editor);
34618
+ return true;
34619
+ }
34428
34620
  function buildTableValueMap(tableNode) {
34429
34621
  const map = TableMap3.get(tableNode);
34430
34622
  const values = /* @__PURE__ */ new Map();
@@ -34447,21 +34639,15 @@ function normalizeFormulaInput(formula) {
34447
34639
  function setSelectedTableCellFormula(editor, formula) {
34448
34640
  const normalized = normalizeFormulaInput(formula);
34449
34641
  const { state, view } = editor;
34642
+ const selectionAnchor = getSelectionTableCellAnchor(editor);
34450
34643
  if (!normalized) {
34451
- const clearedFormula = setCellAttr2("formula", null)(state, view.dispatch.bind(view));
34452
- const clearedValue2 = setCellAttr2("computedValue", null)(editor.state, view.dispatch.bind(view));
34453
- if (clearedFormula || clearedValue2) {
34454
- recalculateActiveTableFormulas(editor);
34455
- view.focus();
34456
- dispatchTableLayoutChange(editor);
34457
- return true;
34458
- }
34459
- return false;
34644
+ return clearSelectedTableCellFormula(editor);
34460
34645
  }
34461
34646
  const appliedFormula = setCellAttr2("formula", normalized)(state, view.dispatch.bind(view));
34462
34647
  const clearedValue = setCellAttr2("computedValue", null)(editor.state, view.dispatch.bind(view));
34463
34648
  if (appliedFormula || clearedValue) {
34464
34649
  recalculateActiveTableFormulas(editor);
34650
+ if (selectionAnchor) restoreSelectionAtTableCell(editor, selectionAnchor);
34465
34651
  view.focus();
34466
34652
  dispatchTableLayoutChange(editor);
34467
34653
  return true;
@@ -34469,7 +34655,10 @@ function setSelectedTableCellFormula(editor, formula) {
34469
34655
  return false;
34470
34656
  }
34471
34657
  function clearSelectedTableCellFormula(editor) {
34472
- return setSelectedTableCellFormula(editor, "");
34658
+ return removeSelectedTableCellFormula(editor, true);
34659
+ }
34660
+ function convertSelectedTableCellFormulaToValue(editor) {
34661
+ return removeSelectedTableCellFormula(editor, false);
34473
34662
  }
34474
34663
  function setSelectedTableCellNumberFormat(editor, numberFormat) {
34475
34664
  const value = numberFormat && numberFormat !== "text" ? numberFormat : null;
@@ -34669,6 +34858,58 @@ function recalculateAllTableFormulas(editor) {
34669
34858
 
34670
34859
  // src/components/UEditor/menus.tsx
34671
34860
  import { Fragment as Fragment30, jsx as jsx92, jsxs as jsxs76 } from "react/jsx-runtime";
34861
+ var FloatingSlashCommandMenu = ({ editor, onClose }) => {
34862
+ const t = useSmartTranslations("UEditor");
34863
+ const messages = useMemo25(() => buildSlashCommandMessages(t), [t]);
34864
+ const items = useMemo25(() => buildSlashCommandItems({ query: "", messages }), [messages]);
34865
+ const listRef = useRef34(null);
34866
+ useEffect37(() => {
34867
+ const handleKeyDown2 = (event) => {
34868
+ if (event.key === "Escape") {
34869
+ event.preventDefault();
34870
+ onClose();
34871
+ return;
34872
+ }
34873
+ const handled = listRef.current?.onKeyDown({ event }) ?? false;
34874
+ if (handled) {
34875
+ event.preventDefault();
34876
+ }
34877
+ };
34878
+ document.addEventListener("keydown", handleKeyDown2);
34879
+ return () => document.removeEventListener("keydown", handleKeyDown2);
34880
+ }, [onClose]);
34881
+ return /* @__PURE__ */ jsx92(
34882
+ SlashCommandList,
34883
+ {
34884
+ ref: listRef,
34885
+ items,
34886
+ messages,
34887
+ command: (item) => {
34888
+ item.command({ editor });
34889
+ onClose();
34890
+ }
34891
+ }
34892
+ );
34893
+ };
34894
+ var FloatingMenuContent = ({ editor }) => {
34895
+ const t = useSmartTranslations("UEditor");
34896
+ const [showCommands, setShowCommands] = useState50(false);
34897
+ if (showCommands) {
34898
+ return /* @__PURE__ */ jsx92(FloatingSlashCommandMenu, { editor, onClose: () => setShowCommands(false) });
34899
+ }
34900
+ return /* @__PURE__ */ jsxs76(
34901
+ "button",
34902
+ {
34903
+ type: "button",
34904
+ onClick: () => setShowCommands(true),
34905
+ className: "flex items-center gap-1 px-2 py-1.5 rounded-lg hover:bg-accent transition-all group",
34906
+ children: [
34907
+ /* @__PURE__ */ jsx92(Plus3, { className: "w-4 h-4 text-muted-foreground group-hover:text-foreground" }),
34908
+ /* @__PURE__ */ jsx92("span", { className: "text-sm text-muted-foreground group-hover:text-foreground", children: t("floatingMenu.addBlock") })
34909
+ ]
34910
+ }
34911
+ );
34912
+ };
34672
34913
  function applyTableCellBackground(editor, color) {
34673
34914
  const value = color || null;
34674
34915
  const { state, view } = editor;
@@ -35407,7 +35648,8 @@ var LinkPreviewContent = ({ editor, onEdit }) => {
35407
35648
  const url = editor.getAttributes("link").href || "";
35408
35649
  const t = useSmartTranslations("UEditor");
35409
35650
  const handleOpen = () => {
35410
- window.open(url, "_blank", "noopener,noreferrer");
35651
+ const safeUrl = sanitizeUEditorUrl(url, "link");
35652
+ if (safeUrl) window.open(safeUrl, "_blank", "noopener,noreferrer");
35411
35653
  };
35412
35654
  const handleUnlink = () => {
35413
35655
  editor.chain().focus().unsetLink().run();
@@ -35437,16 +35679,19 @@ var CustomBubbleMenu = ({
35437
35679
  });
35438
35680
  const menuRef = useRef34(null);
35439
35681
  const keepOpenRef = useRef34(false);
35682
+ const [keepOpen, setKeepOpenState] = useState50(false);
35440
35683
  const showTimeoutRef = useRef34(null);
35441
35684
  const suppressShowUntilRef = useRef34(0);
35442
35685
  const setKeepOpen = useCallback23((next) => {
35443
35686
  keepOpenRef.current = next;
35687
+ setKeepOpenState(next);
35444
35688
  if (!next) setLinkInputOpen(false);
35445
35689
  if (next) setIsVisible(true);
35446
35690
  }, []);
35447
35691
  const closeBubbleMenu = useCallback23(() => {
35448
35692
  suppressShowUntilRef.current = Date.now() + 1e3;
35449
35693
  keepOpenRef.current = false;
35694
+ setKeepOpenState(false);
35450
35695
  setLinkInputOpen(false);
35451
35696
  setIsVisible(false);
35452
35697
  if (showTimeoutRef.current) {
@@ -35552,13 +35797,13 @@ var CustomBubbleMenu = ({
35552
35797
  onMouseDown: (e) => {
35553
35798
  const target = e.target;
35554
35799
  if (target?.closest?.("[data-ueditor-close-on-select]")) {
35555
- keepOpenRef.current = false;
35800
+ setKeepOpen(false);
35556
35801
  } else if (target?.closest?.("[data-ueditor-keep-open]")) {
35557
- keepOpenRef.current = true;
35802
+ setKeepOpen(true);
35558
35803
  }
35559
35804
  e.preventDefault();
35560
35805
  },
35561
- children: editor.isActive("link") && !keepOpenRef.current && !linkInputOpen ? /* @__PURE__ */ jsx92(
35806
+ children: editor.isActive("link") && !keepOpen && !linkInputOpen ? /* @__PURE__ */ jsx92(
35562
35807
  LinkPreviewContent,
35563
35808
  {
35564
35809
  editor,
@@ -35584,6 +35829,54 @@ var CustomBubbleMenu = ({
35584
35829
  document.body
35585
35830
  );
35586
35831
  };
35832
+ var CustomFloatingMenu = ({ editor }) => {
35833
+ const FLOATING_MENU_OFFSET = 16;
35834
+ const [isVisible, setIsVisible] = useState50(false);
35835
+ const [position, setPosition2] = useState50({ top: 0, left: 0 });
35836
+ useEffect37(() => {
35837
+ const updatePosition = () => {
35838
+ const { state, view } = editor;
35839
+ const { $from, empty } = state.selection;
35840
+ const isEmptyTextBlock = $from.parent.isTextblock && $from.parent.type.name === "paragraph" && $from.parent.textContent === "" && empty;
35841
+ if (!isEmptyTextBlock || !view.hasFocus()) {
35842
+ setIsVisible(false);
35843
+ return;
35844
+ }
35845
+ const coords = view.coordsAtPos($from.pos);
35846
+ setPosition2({ top: coords.top - FLOATING_MENU_OFFSET, left: coords.left });
35847
+ setIsVisible(true);
35848
+ };
35849
+ const handleBlur = () => setIsVisible(false);
35850
+ editor.on("selectionUpdate", updatePosition);
35851
+ editor.on("focus", updatePosition);
35852
+ editor.on("blur", handleBlur);
35853
+ editor.on("update", updatePosition);
35854
+ return () => {
35855
+ editor.off("selectionUpdate", updatePosition);
35856
+ editor.off("focus", updatePosition);
35857
+ editor.off("blur", handleBlur);
35858
+ editor.off("update", updatePosition);
35859
+ };
35860
+ }, [editor]);
35861
+ if (!isVisible) return null;
35862
+ return createPortal8(
35863
+ /* @__PURE__ */ jsx92(
35864
+ "div",
35865
+ {
35866
+ "data-popover": true,
35867
+ className: "fixed z-99999 rounded-2xl border border-border/50 bg-card text-card-foreground shadow-lg backdrop-blur-sm overflow-hidden animate-in fade-in-0 slide-in-from-bottom-2",
35868
+ style: {
35869
+ top: `${position.top}px`,
35870
+ left: `${position.left}px`,
35871
+ transform: "translate(-50%, -100%)"
35872
+ },
35873
+ onMouseDown: (e) => e.preventDefault(),
35874
+ children: /* @__PURE__ */ jsx92(FloatingMenuContent, { editor })
35875
+ }
35876
+ ),
35877
+ document.body
35878
+ );
35879
+ };
35587
35880
 
35588
35881
  // src/components/UEditor/CharacterCount.tsx
35589
35882
  import { jsxs as jsxs77 } from "react/jsx-runtime";
@@ -38659,7 +38952,7 @@ var Selection = class {
38659
38952
  found.
38660
38953
  */
38661
38954
  static findFrom($pos, dir, textOnly = false) {
38662
- let inner = $pos.parent.inlineContent ? new TextSelection3($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
38955
+ let inner = $pos.parent.inlineContent ? new TextSelection4($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
38663
38956
  if (inner)
38664
38957
  return inner;
38665
38958
  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
@@ -38728,7 +39021,7 @@ var Selection = class {
38728
39021
  returns the bookmark for that.
38729
39022
  */
38730
39023
  getBookmark() {
38731
- return TextSelection3.between(this.$anchor, this.$head).getBookmark();
39024
+ return TextSelection4.between(this.$anchor, this.$head).getBookmark();
38732
39025
  }
38733
39026
  };
38734
39027
  Selection.prototype.visible = true;
@@ -38748,7 +39041,7 @@ function checkTextSelection($pos) {
38748
39041
  console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
38749
39042
  }
38750
39043
  }
38751
- var TextSelection3 = class _TextSelection extends Selection {
39044
+ var TextSelection4 = class _TextSelection extends Selection {
38752
39045
  /**
38753
39046
  Construct a text selection between the given points.
38754
39047
  */
@@ -38834,7 +39127,7 @@ var TextSelection3 = class _TextSelection extends Selection {
38834
39127
  return new _TextSelection($anchor, $head);
38835
39128
  }
38836
39129
  };
38837
- Selection.jsonID("text", TextSelection3);
39130
+ Selection.jsonID("text", TextSelection4);
38838
39131
  var TextBookmark = class _TextBookmark {
38839
39132
  constructor(anchor, head) {
38840
39133
  this.anchor = anchor;
@@ -38844,7 +39137,7 @@ var TextBookmark = class _TextBookmark {
38844
39137
  return new _TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
38845
39138
  }
38846
39139
  resolve(doc) {
38847
- return TextSelection3.between(doc.resolve(this.anchor), doc.resolve(this.head));
39140
+ return TextSelection4.between(doc.resolve(this.anchor), doc.resolve(this.head));
38848
39141
  }
38849
39142
  };
38850
39143
  var NodeSelection2 = class _NodeSelection extends Selection {
@@ -38963,7 +39256,7 @@ var AllBookmark = {
38963
39256
  };
38964
39257
  function findSelectionIn(doc, node, pos, index, dir, text = false) {
38965
39258
  if (node.inlineContent)
38966
- return TextSelection3.create(doc, pos);
39259
+ return TextSelection4.create(doc, pos);
38967
39260
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
38968
39261
  let child = node.child(i);
38969
39262
  if (!child.isAtom) {
@@ -39555,7 +39848,7 @@ var CellSelection = class CellSelection2 extends Selection {
39555
39848
  else if (tableChanged && this.isColSelection()) return CellSelection2.colSelection($anchorCell, $headCell);
39556
39849
  else return new CellSelection2($anchorCell, $headCell);
39557
39850
  }
39558
- return TextSelection3.between($anchorCell, $headCell);
39851
+ return TextSelection4.between($anchorCell, $headCell);
39559
39852
  }
39560
39853
  content() {
39561
39854
  const table = this.$anchorCell.node(-1);
@@ -40170,7 +40463,7 @@ function shiftArrow(axis, dir) {
40170
40463
  };
40171
40464
  }
40172
40465
  function atEndOfCell(view, axis, dir) {
40173
- if (!(view.state.selection instanceof TextSelection3)) return null;
40466
+ if (!(view.state.selection instanceof TextSelection4)) return null;
40174
40467
  const { $head } = view.state.selection;
40175
40468
  for (let d = $head.depth - 1; d >= 0; d--) {
40176
40469
  const parent = $head.node(d);
@@ -41301,7 +41594,7 @@ function TableControls({ editor, containerRef }) {
41301
41594
  destructive: true
41302
41595
  }
41303
41596
  ];
41304
- }, [layout, runAtActiveCell, t]);
41597
+ }, [editor, layout, runAtActiveCell, t]);
41305
41598
  const getRowHandleMenuItems = React83.useCallback((rowHandle) => [
41306
41599
  {
41307
41600
  label: t("tableMenu.addRowBefore"),
@@ -41517,6 +41810,42 @@ var UEDITOR_PROSEMIRROR_CLASS_NAME = cn(
41517
41810
  "[&_th]:px-2",
41518
41811
  "[&_th]:py-0",
41519
41812
  "[&_th_p]:my-0",
41813
+ "[&_td[data-formula]]:pr-7",
41814
+ "[&_th[data-formula]]:pr-7",
41815
+ "[&_td[data-formula]]:before:pointer-events-none",
41816
+ "[&_th[data-formula]]:before:pointer-events-none",
41817
+ "[&_td[data-formula]]:before:absolute",
41818
+ "[&_th[data-formula]]:before:absolute",
41819
+ "[&_td[data-formula]]:before:right-1",
41820
+ "[&_th[data-formula]]:before:right-1",
41821
+ "[&_td[data-formula]]:before:top-1",
41822
+ "[&_th[data-formula]]:before:top-1",
41823
+ "[&_td[data-formula]]:before:z-[1]",
41824
+ "[&_th[data-formula]]:before:z-[1]",
41825
+ "[&_td[data-formula]]:before:rounded-sm",
41826
+ "[&_th[data-formula]]:before:rounded-sm",
41827
+ "[&_td[data-formula]]:before:bg-primary/10",
41828
+ "[&_th[data-formula]]:before:bg-primary/10",
41829
+ "[&_td[data-formula]]:before:px-1",
41830
+ "[&_th[data-formula]]:before:px-1",
41831
+ "[&_td[data-formula]]:before:font-mono",
41832
+ "[&_th[data-formula]]:before:font-mono",
41833
+ "[&_td[data-formula]]:before:text-[9px]",
41834
+ "[&_th[data-formula]]:before:text-[9px]",
41835
+ "[&_td[data-formula]]:before:font-semibold",
41836
+ "[&_th[data-formula]]:before:font-semibold",
41837
+ "[&_td[data-formula]]:before:leading-4",
41838
+ "[&_th[data-formula]]:before:leading-4",
41839
+ "[&_td[data-formula]]:before:text-primary",
41840
+ "[&_th[data-formula]]:before:text-primary",
41841
+ "[&_td[data-formula]]:before:content-['fx']",
41842
+ "[&_th[data-formula]]:before:content-['fx']",
41843
+ "[&_td[data-formula-state='error']]:bg-destructive/5",
41844
+ "[&_th[data-formula-state='error']]:bg-destructive/5",
41845
+ "[&_td[data-formula-state='error']]:before:bg-destructive/10",
41846
+ "[&_th[data-formula-state='error']]:before:bg-destructive/10",
41847
+ "[&_td[data-formula-state='error']]:before:text-destructive",
41848
+ "[&_th[data-formula-state='error']]:before:text-destructive",
41520
41849
  "[&_td[colwidth]]:min-w-0",
41521
41850
  "[&_th[colwidth]]:min-w-0",
41522
41851
  "[&_td[data-colwidth]]:min-w-0",
@@ -42022,7 +42351,7 @@ function useUEditorTableInteractions(editor, editable = true) {
42022
42351
  clearHoveredTableCell();
42023
42352
  clearAllTableResizeHover();
42024
42353
  };
42025
- }, [beginResize, cancelResize, cleanupRowResize, clearActiveTableCell, clearAllTableResizeHover, clearHoveredTableCell, editable, editor, handleRowResizePointerMove, handleRowResizePointerUp, hideColumnGuide, hideRowGuide, isRowResizing, scheduleTableLayoutSync, showColumnGuide, showRowGuide, syncActiveRowResizeGuide, syncActiveTableCellFromSelection, updateActiveCellHighlight]);
42354
+ }, [beginResize, cancelResize, cleanupRowResize, clearActiveTableCell, clearAllTableResizeHover, clearHoveredTableCell, editable, editor, handleRowResizePointerMove, handleRowResizePointerUp, hideColumnGuide, hideRowGuide, isRowResizing, scheduleTableLayoutSync, setActiveTableCell, setHoveredTableCell, showColumnGuide, showRowGuide, syncActiveRowResizeGuide, syncActiveTableCellFromSelection, updateActiveCellHighlight]);
42026
42355
  return {
42027
42356
  editorContentRef,
42028
42357
  tableColumnGuideRef,
@@ -42036,7 +42365,7 @@ import { useEffect as useEffect39, useRef as useRef37 } from "react";
42036
42365
  import { TableMap as TableMap6 } from "@tiptap/pm/tables";
42037
42366
 
42038
42367
  // src/components/UEditor/table-formula-range-picker.ts
42039
- import { TextSelection as TextSelection4 } from "@tiptap/pm/state";
42368
+ import { TextSelection as TextSelection5 } from "@tiptap/pm/state";
42040
42369
  import { TableMap as TableMap5 } from "@tiptap/pm/tables";
42041
42370
  function getCellText2(cellNode) {
42042
42371
  return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
@@ -42072,7 +42401,7 @@ function cancelFormulaEditing(view) {
42072
42401
  if (!context) return false;
42073
42402
  let tr = view.state.tr.delete(context.cellContentStart, context.cellContentEnd);
42074
42403
  const selectionPos = Math.min(context.cellContentStart, tr.doc.content.size);
42075
- tr = tr.setSelection(TextSelection4.near(tr.doc.resolve(selectionPos)));
42404
+ tr = tr.setSelection(TextSelection5.near(tr.doc.resolve(selectionPos)));
42076
42405
  view.dispatch(tr);
42077
42406
  view.focus();
42078
42407
  return true;
@@ -42139,7 +42468,7 @@ function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42139
42468
  }
42140
42469
  let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42141
42470
  const nextTo = pickState.insertedFrom + nextLabel.length;
42142
- tr = tr.setSelection(TextSelection4.create(tr.doc, nextTo));
42471
+ tr = tr.setSelection(TextSelection5.create(tr.doc, nextTo));
42143
42472
  view.dispatch(tr);
42144
42473
  return {
42145
42474
  ...pickState,
@@ -42159,7 +42488,7 @@ function beginFormulaRangePick(view, event) {
42159
42488
  const insertedText = `${prefix}${picked.label}`;
42160
42489
  const insertedFrom = from + prefix.length;
42161
42490
  let tr = view.state.tr.insertText(insertedText, from, to);
42162
- tr = tr.setSelection(TextSelection4.create(tr.doc, from + insertedText.length));
42491
+ tr = tr.setSelection(TextSelection5.create(tr.doc, from + insertedText.length));
42163
42492
  view.dispatch(tr);
42164
42493
  view.focus();
42165
42494
  event.preventDefault();
@@ -43338,9 +43667,156 @@ var MenuBar = ({
43338
43667
  ] });
43339
43668
  };
43340
43669
 
43341
- // src/components/UEditor/UEditor.tsx
43670
+ // src/components/UEditor/table-formula-bar.tsx
43671
+ import { useEffect as useEffect40, useRef as useRef39, useState as useState52 } from "react";
43672
+ import { useEditorState as useEditorState4 } from "@tiptap/react";
43673
+ import { AlertCircle as AlertCircle5, Check as Check14, Hash as Hash2, Sigma as Sigma3, Trash2 as Trash26 } from "lucide-react";
43342
43674
  import { jsx as jsx99, jsxs as jsxs82 } from "react/jsx-runtime";
43343
- var UEditor = React87.forwardRef(({
43675
+ function TableFormulaBar({ editor }) {
43676
+ const t = useSmartTranslations("UEditor");
43677
+ const inputRef = useRef39(null);
43678
+ const [draftState, setDraftState] = useState52(null);
43679
+ useEditorState4({
43680
+ editor,
43681
+ selector: ({ transactionNumber }) => transactionNumber
43682
+ });
43683
+ const selectedCell = getSelectedTableFormulaCell(editor);
43684
+ useEffect40(() => {
43685
+ const focusFormulaInput = () => {
43686
+ inputRef.current?.focus();
43687
+ };
43688
+ const handleDoubleClick = (event) => {
43689
+ const target = resolveEventElement(event.target);
43690
+ if (!target?.closest?.("td[data-formula],th[data-formula]")) return;
43691
+ event.preventDefault();
43692
+ focusFormulaInput();
43693
+ };
43694
+ editor.view.dom.addEventListener("dblclick", handleDoubleClick);
43695
+ return () => {
43696
+ editor.view.dom.removeEventListener("dblclick", handleDoubleClick);
43697
+ };
43698
+ }, [editor]);
43699
+ if (!selectedCell) return null;
43700
+ const selectedCellKey = `${selectedCell.tablePos}:${selectedCell.row}:${selectedCell.column}`;
43701
+ const draft = draftState?.cellKey === selectedCellKey && draftState.sourceFormula === selectedCell.formula ? draftState.value : selectedCell.formula;
43702
+ const setDraft = (value) => {
43703
+ setDraftState({
43704
+ cellKey: selectedCellKey,
43705
+ sourceFormula: selectedCell.formula,
43706
+ value
43707
+ });
43708
+ };
43709
+ const applyDraft = () => {
43710
+ const normalized = normalizeFormulaInput(draft);
43711
+ if (normalized) {
43712
+ setSelectedTableCellFormula(editor, normalized);
43713
+ } else {
43714
+ clearSelectedTableCellFormula(editor);
43715
+ }
43716
+ setDraftState(null);
43717
+ };
43718
+ const cancelDraft = () => {
43719
+ setDraftState(null);
43720
+ window.setTimeout(() => {
43721
+ if (!editor.isDestroyed) editor.commands.focus();
43722
+ }, 0);
43723
+ };
43724
+ return /* @__PURE__ */ jsxs82(
43725
+ "div",
43726
+ {
43727
+ "data-ueditor-formula-bar": "",
43728
+ role: "toolbar",
43729
+ "aria-label": t("tableMenu.formulaBar"),
43730
+ className: "flex min-h-10 items-center gap-2 border-b border-border/60 bg-muted/25 px-2 py-1.5",
43731
+ children: [
43732
+ /* @__PURE__ */ jsx99(
43733
+ "span",
43734
+ {
43735
+ "data-ueditor-formula-cell-label": "",
43736
+ className: "flex h-7 min-w-12 items-center justify-center rounded-md border border-border/60 bg-background px-2 font-mono text-xs font-semibold text-foreground",
43737
+ children: selectedCell.label
43738
+ }
43739
+ ),
43740
+ /* @__PURE__ */ jsx99(Sigma3, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-primary" }),
43741
+ /* @__PURE__ */ jsx99(
43742
+ "input",
43743
+ {
43744
+ ref: inputRef,
43745
+ "data-ueditor-formula-input": "",
43746
+ value: draft,
43747
+ onChange: (event) => setDraft(event.target.value),
43748
+ onKeyDown: (event) => {
43749
+ event.stopPropagation();
43750
+ if (event.key === "Enter") {
43751
+ event.preventDefault();
43752
+ applyDraft();
43753
+ } else if (event.key === "Escape") {
43754
+ event.preventDefault();
43755
+ cancelDraft();
43756
+ }
43757
+ },
43758
+ "aria-label": t("tableMenu.editFormula"),
43759
+ className: "h-7 min-w-0 flex-1 rounded-md border border-border/60 bg-background px-2 font-mono text-sm text-foreground outline-none focus:border-primary/40 focus:ring-2 focus:ring-primary/15"
43760
+ }
43761
+ ),
43762
+ selectedCell.formulaState === "error" && /* @__PURE__ */ jsxs82(
43763
+ "span",
43764
+ {
43765
+ role: "status",
43766
+ className: "hidden shrink-0 items-center gap-1 text-xs font-medium text-destructive sm:inline-flex",
43767
+ children: [
43768
+ /* @__PURE__ */ jsx99(AlertCircle5, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
43769
+ t("tableMenu.formulaError")
43770
+ ]
43771
+ }
43772
+ ),
43773
+ /* @__PURE__ */ jsx99(
43774
+ "button",
43775
+ {
43776
+ type: "button",
43777
+ onClick: applyDraft,
43778
+ "aria-label": t("tableMenu.apply"),
43779
+ title: t("tableMenu.apply"),
43780
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary/90",
43781
+ children: /* @__PURE__ */ jsx99(Check14, { "aria-hidden": "true", className: "h-4 w-4" })
43782
+ }
43783
+ ),
43784
+ /* @__PURE__ */ jsx99(
43785
+ "button",
43786
+ {
43787
+ type: "button",
43788
+ onClick: () => {
43789
+ convertSelectedTableCellFormulaToValue(editor);
43790
+ setDraftState(null);
43791
+ },
43792
+ "aria-label": t("tableMenu.convertToValue"),
43793
+ title: t("tableMenu.convertToValue"),
43794
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border/60 bg-background text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
43795
+ children: /* @__PURE__ */ jsx99(Hash2, { "aria-hidden": "true", className: "h-4 w-4" })
43796
+ }
43797
+ ),
43798
+ /* @__PURE__ */ jsx99(
43799
+ "button",
43800
+ {
43801
+ type: "button",
43802
+ onClick: () => {
43803
+ clearSelectedTableCellFormula(editor);
43804
+ setDraftState(null);
43805
+ },
43806
+ "aria-label": t("tableMenu.clearCell"),
43807
+ title: t("tableMenu.clearCell"),
43808
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-destructive/30 bg-background text-destructive transition-colors hover:bg-destructive/10",
43809
+ children: /* @__PURE__ */ jsx99(Trash26, { "aria-hidden": "true", className: "h-4 w-4" })
43810
+ }
43811
+ )
43812
+ ]
43813
+ }
43814
+ );
43815
+ }
43816
+
43817
+ // src/components/UEditor/UEditor.tsx
43818
+ import { jsx as jsx100, jsxs as jsxs83 } from "react/jsx-runtime";
43819
+ var UEditor = React88.forwardRef(({
43344
43820
  content = "",
43345
43821
  onChange,
43346
43822
  onHtmlChange,
@@ -43358,6 +43834,7 @@ var UEditor = React87.forwardRef(({
43358
43834
  autofocus = false,
43359
43835
  showToolbar = true,
43360
43836
  showBubbleMenu = true,
43837
+ showFloatingMenu = false,
43361
43838
  showCharacterCount = true,
43362
43839
  showFooter = true,
43363
43840
  maxCharacters,
@@ -43381,14 +43858,14 @@ var UEditor = React87.forwardRef(({
43381
43858
  }, ref) => {
43382
43859
  const t = useSmartTranslations("UEditor");
43383
43860
  const effectivePlaceholder = placeholder ?? t("placeholder");
43384
- const inFlightPrepareRef = useRef39(null);
43385
- const lastAppliedContentRef = useRef39(content ?? "");
43386
- const scheduledFormulaRecalculateRef = useRef39(false);
43387
- const editorInstanceRef = useRef39(null);
43388
- const formulaRangePickRef = useRef39(null);
43389
- const formulaRangeSurfaceRef = useRef39(null);
43390
- const [formulaRangeHighlight, setFormulaRangeHighlight] = React87.useState(null);
43391
- const scheduleFormulaRecalculate = React87.useCallback((editor2, options) => {
43861
+ const inFlightPrepareRef = useRef40(null);
43862
+ const lastAppliedContentRef = useRef40(content ?? "");
43863
+ const scheduledFormulaRecalculateRef = useRef40(false);
43864
+ const editorInstanceRef = useRef40(null);
43865
+ const formulaRangePickRef = useRef40(null);
43866
+ const formulaRangeSurfaceRef = useRef40(null);
43867
+ const [formulaRangeHighlight, setFormulaRangeHighlight] = React88.useState(null);
43868
+ const scheduleFormulaRecalculate = React88.useCallback((editor2, options) => {
43392
43869
  if (editor2.isDestroyed || scheduledFormulaRecalculateRef.current) return;
43393
43870
  if (!options?.force && isEditingTableFormulaText(editor2)) return;
43394
43871
  scheduledFormulaRecalculateRef.current = true;
@@ -43428,7 +43905,7 @@ var UEditor = React87.forwardRef(({
43428
43905
  ],
43429
43906
  [effectivePlaceholder, t, maxCharacters, uploadImage, resolvedUploadFile, imageInsertMode, maxImageFileSize, allowedImageMimeTypes, fallbackToDataUrl, editable, fetchMetadata, extraExtensions]
43430
43907
  );
43431
- const syncFormulaRangeHighlight = React87.useCallback((pickState) => {
43908
+ const syncFormulaRangeHighlight = React88.useCallback((pickState) => {
43432
43909
  const container = formulaRangeSurfaceRef.current;
43433
43910
  setFormulaRangeHighlight(container && pickState ? getFormulaRangePickHighlight(container, pickState) : null);
43434
43911
  }, []);
@@ -43477,9 +43954,9 @@ var UEditor = React87.forwardRef(({
43477
43954
  if (formulaContext && event.key === "Enter") {
43478
43955
  event.preventDefault();
43479
43956
  event.stopPropagation();
43480
- const activeEditor = editorInstanceRef.current;
43481
- if (activeEditor && !isDraftTableFormula(formulaContext.formula)) {
43482
- recalculateActiveTableFormulas(activeEditor);
43957
+ const activeEditor2 = editorInstanceRef.current;
43958
+ if (activeEditor2 && !isDraftTableFormula(formulaContext.formula)) {
43959
+ recalculateActiveTableFormulas(activeEditor2);
43483
43960
  }
43484
43961
  return true;
43485
43962
  }
@@ -43489,6 +43966,13 @@ var UEditor = React87.forwardRef(({
43489
43966
  cancelFormulaEditing(_view);
43490
43967
  return true;
43491
43968
  }
43969
+ const activeEditor = editorInstanceRef.current;
43970
+ if (activeEditor && (event.key === "Backspace" || event.key === "Delete") && getSelectedTableFormulaCell(activeEditor)) {
43971
+ event.preventDefault();
43972
+ event.stopPropagation();
43973
+ clearSelectedTableCellFormula(activeEditor);
43974
+ return true;
43975
+ }
43492
43976
  if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
43493
43977
  event.stopPropagation();
43494
43978
  }
@@ -43499,7 +43983,7 @@ var UEditor = React87.forwardRef(({
43499
43983
  if (event.button !== 0) return false;
43500
43984
  const target = resolveEventElement(event.target);
43501
43985
  const anchor = target?.closest?.("a[href]");
43502
- const href = anchor?.getAttribute("href") ?? "";
43986
+ const href = sanitizeUEditorUrl(anchor?.getAttribute("href") ?? "", "link");
43503
43987
  if (!href) return false;
43504
43988
  if (editable) return false;
43505
43989
  if (!view.state.selection.empty) return false;
@@ -43525,11 +44009,13 @@ var UEditor = React87.forwardRef(({
43525
44009
  onSelectionUpdate: ({ editor: editor2 }) => {
43526
44010
  scheduleFormulaRecalculate(editor2);
43527
44011
  },
43528
- onBlur: ({ editor: editor2 }) => {
44012
+ onBlur: ({ editor: editor2, event }) => {
44013
+ const nextTarget = event.relatedTarget;
44014
+ if (nextTarget instanceof Element && nextTarget.closest("[data-ueditor-formula-bar]")) return;
43529
44015
  scheduleFormulaRecalculate(editor2, { force: true });
43530
44016
  }
43531
44017
  });
43532
- useEffect40(() => {
44018
+ useEffect41(() => {
43533
44019
  editorInstanceRef.current = editor;
43534
44020
  return () => {
43535
44021
  if (editorInstanceRef.current === editor) editorInstanceRef.current = null;
@@ -43570,7 +44056,7 @@ var UEditor = React87.forwardRef(({
43570
44056
  }),
43571
44057
  [content, editor, uploadImageForSave, uploadFileForSave, uploadImageConcurrency]
43572
44058
  );
43573
- useEffect40(() => {
44059
+ useEffect41(() => {
43574
44060
  if (!editor) return;
43575
44061
  queueMicrotask(() => {
43576
44062
  if (!editor.isDestroyed) {
@@ -43578,7 +44064,7 @@ var UEditor = React87.forwardRef(({
43578
44064
  }
43579
44065
  });
43580
44066
  }, [editor]);
43581
- useEffect40(() => {
44067
+ useEffect41(() => {
43582
44068
  if (!editor) return;
43583
44069
  const nextContent = content ?? "";
43584
44070
  if (lastAppliedContentRef.current === nextContent) return;
@@ -43597,7 +44083,7 @@ var UEditor = React87.forwardRef(({
43597
44083
  }
43598
44084
  }, [content, editor]);
43599
44085
  if (!editor) {
43600
- return /* @__PURE__ */ jsx99(
44086
+ return /* @__PURE__ */ jsx100(
43601
44087
  "div",
43602
44088
  {
43603
44089
  className: cn("w-full border bg-background flex items-center justify-center text-muted-foreground", className),
@@ -43606,7 +44092,7 @@ var UEditor = React87.forwardRef(({
43606
44092
  }
43607
44093
  );
43608
44094
  }
43609
- return /* @__PURE__ */ jsxs82(
44095
+ return /* @__PURE__ */ jsxs83(
43610
44096
  "div",
43611
44097
  {
43612
44098
  className: cn(
@@ -43622,7 +44108,7 @@ var UEditor = React87.forwardRef(({
43622
44108
  className
43623
44109
  ),
43624
44110
  children: [
43625
- editable && showMenuBar && /* @__PURE__ */ jsx99(
44111
+ editable && showMenuBar && /* @__PURE__ */ jsx100(
43626
44112
  MenuBar,
43627
44113
  {
43628
44114
  editor,
@@ -43637,7 +44123,7 @@ var UEditor = React87.forwardRef(({
43637
44123
  onPreview
43638
44124
  }
43639
44125
  ),
43640
- editable && showToolbar && /* @__PURE__ */ jsx99(
44126
+ editable && showToolbar && /* @__PURE__ */ jsx100(
43641
44127
  EditorToolbar,
43642
44128
  {
43643
44129
  editor,
@@ -43652,7 +44138,8 @@ var UEditor = React87.forwardRef(({
43652
44138
  letterSpacings
43653
44139
  }
43654
44140
  ),
43655
- editable && showBubbleMenu && /* @__PURE__ */ jsx99(
44141
+ editable && /* @__PURE__ */ jsx100(TableFormulaBar, { editor }),
44142
+ editable && showBubbleMenu && /* @__PURE__ */ jsx100(
43656
44143
  CustomBubbleMenu,
43657
44144
  {
43658
44145
  editor,
@@ -43660,7 +44147,8 @@ var UEditor = React87.forwardRef(({
43660
44147
  lineHeights
43661
44148
  }
43662
44149
  ),
43663
- /* @__PURE__ */ jsxs82(
44150
+ editable && showFloatingMenu && /* @__PURE__ */ jsx100(CustomFloatingMenu, { editor }),
44151
+ /* @__PURE__ */ jsxs83(
43664
44152
  "div",
43665
44153
  {
43666
44154
  ref: (node) => {
@@ -43673,7 +44161,7 @@ var UEditor = React87.forwardRef(({
43673
44161
  maxHeight
43674
44162
  },
43675
44163
  children: [
43676
- /* @__PURE__ */ jsx99(
44164
+ /* @__PURE__ */ jsx100(
43677
44165
  "span",
43678
44166
  {
43679
44167
  ref: tableColumnGuideRef,
@@ -43681,7 +44169,7 @@ var UEditor = React87.forwardRef(({
43681
44169
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43682
44170
  }
43683
44171
  ),
43684
- /* @__PURE__ */ jsx99(
44172
+ /* @__PURE__ */ jsx100(
43685
44173
  "span",
43686
44174
  {
43687
44175
  ref: tableRowGuideRef,
@@ -43689,7 +44177,7 @@ var UEditor = React87.forwardRef(({
43689
44177
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43690
44178
  }
43691
44179
  ),
43692
- /* @__PURE__ */ jsx99(
44180
+ /* @__PURE__ */ jsx100(
43693
44181
  "span",
43694
44182
  {
43695
44183
  ref: activeTableCellHighlightRef,
@@ -43698,7 +44186,7 @@ var UEditor = React87.forwardRef(({
43698
44186
  className: "pointer-events-none hidden absolute z-20 rounded-[2px] border-2 border-primary bg-primary/10"
43699
44187
  }
43700
44188
  ),
43701
- /* @__PURE__ */ jsx99(
44189
+ /* @__PURE__ */ jsx100(
43702
44190
  "div",
43703
44191
  {
43704
44192
  ref: formulaCoordinateOverlayRef,
@@ -43706,7 +44194,7 @@ var UEditor = React87.forwardRef(({
43706
44194
  className: "pointer-events-none absolute inset-0 z-[21] hidden overflow-visible"
43707
44195
  }
43708
44196
  ),
43709
- formulaRangeHighlight && /* @__PURE__ */ jsx99(
44197
+ formulaRangeHighlight && /* @__PURE__ */ jsx100(
43710
44198
  "span",
43711
44199
  {
43712
44200
  "aria-hidden": "true",
@@ -43720,8 +44208,8 @@ var UEditor = React87.forwardRef(({
43720
44208
  }
43721
44209
  }
43722
44210
  ),
43723
- editable && /* @__PURE__ */ jsx99(TableControls, { editor, containerRef: editorContentRef }),
43724
- /* @__PURE__ */ jsx99(
44211
+ editable && /* @__PURE__ */ jsx100(TableControls, { editor, containerRef: editorContentRef }),
44212
+ /* @__PURE__ */ jsx100(
43725
44213
  EditorContent,
43726
44214
  {
43727
44215
  editor,
@@ -43731,7 +44219,7 @@ var UEditor = React87.forwardRef(({
43731
44219
  ]
43732
44220
  }
43733
44221
  ),
43734
- showFooter && showCharacterCount && /* @__PURE__ */ jsx99(CharacterCountDisplay, { editor, maxCharacters })
44222
+ showFooter && showCharacterCount && /* @__PURE__ */ jsx100(CharacterCountDisplay, { editor, maxCharacters })
43735
44223
  ]
43736
44224
  }
43737
44225
  );