@underverse-ui/underverse 1.0.183 → 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 useEffect39, useImperativeHandle as useImperativeHandle4, useMemo as useMemo27, useRef as useRef38 } 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;
@@ -29905,11 +30007,17 @@ function isInTableCell(editor) {
29905
30007
  }
29906
30008
  function buildFormulaSuggestionItems({ query }) {
29907
30009
  const normalizedQuery = query.trim().toUpperCase();
30010
+ if (!isFormulaFunctionSuggestionQuery(normalizedQuery)) {
30011
+ return [];
30012
+ }
29908
30013
  if (!normalizedQuery) {
29909
30014
  return FORMULA_FUNCTIONS;
29910
30015
  }
29911
30016
  return FORMULA_FUNCTIONS.filter((item) => item.name.startsWith(normalizedQuery));
29912
30017
  }
30018
+ function isFormulaFunctionSuggestionQuery(query) {
30019
+ return /^[A-Z]*$/i.test(query.trim());
30020
+ }
29913
30021
  var FormulaSuggestionList = forwardRef16((props, ref) => {
29914
30022
  const t = useSmartTranslations("UEditor");
29915
30023
  const [selectedIndex, setSelectedIndex] = React76.useState(0);
@@ -29927,7 +30035,7 @@ var FormulaSuggestionList = forwardRef16((props, ref) => {
29927
30035
  setSelectedIndex((prev) => (prev + 1) % props.items.length);
29928
30036
  return true;
29929
30037
  }
29930
- if (event.key === "Enter" || event.key === "Tab") {
30038
+ if (event.key === "Tab") {
29931
30039
  const item = props.items[selectedIndex];
29932
30040
  if (item) {
29933
30041
  props.command(item);
@@ -29980,7 +30088,11 @@ var FormulaSuggestion = Extension4.create({
29980
30088
  editor: this.editor,
29981
30089
  char: "=",
29982
30090
  pluginKey: new PluginKey3("formulaSuggestion"),
29983
- allow: ({ editor }) => isInTableCell(editor),
30091
+ allow: ({ editor, range }) => {
30092
+ if (!isInTableCell(editor)) return false;
30093
+ const suggestionText = editor.state.doc.textBetween(range.from, range.to, "", "");
30094
+ return suggestionText.startsWith("=") && isFormulaFunctionSuggestionQuery(suggestionText.slice(1));
30095
+ },
29984
30096
  command: ({ editor, range, props }) => {
29985
30097
  insertFormulaFunction(editor, range, props);
29986
30098
  },
@@ -30013,9 +30125,7 @@ var FormulaSuggestion = Extension4.create({
30013
30125
  if (!props.clientRect) {
30014
30126
  return;
30015
30127
  }
30016
- popup?.[0]?.setProps({
30017
- getReferenceClientRect: props.clientRect
30018
- });
30128
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
30019
30129
  },
30020
30130
  onKeyDown(props) {
30021
30131
  const popupInstance = getFirstTippyInstance(popup);
@@ -31766,7 +31876,6 @@ function buildUEditorExtensions({
31766
31876
  Gapcursor,
31767
31877
  Link.configure({
31768
31878
  openOnClick: false,
31769
- protocols: ["http", "https", "mailto", "tel"],
31770
31879
  isAllowedUri: (url) => isSafeUEditorUrl(url ?? "", "link"),
31771
31880
  HTMLAttributes: {
31772
31881
  class: "text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer"
@@ -32858,7 +32967,7 @@ var EditorToolbar = ({
32858
32967
  const [showImageInput, setShowImageInput] = useState49(false);
32859
32968
  const [showLinkInput, setShowLinkInput] = useState49(false);
32860
32969
  const [isTableMenuOpen, setIsTableMenuOpen] = useState49(false);
32861
- const tableCommandAnchorPosRef = useRef33(null);
32970
+ const [preservedTableAnchorPos, setPreservedTableAnchorPos] = useState49(null);
32862
32971
  const fileInputRef = useRef33(null);
32863
32972
  const [isUploadingImage, setIsUploadingImage] = useState49(false);
32864
32973
  const [imageUploadError, setImageUploadError] = useState49(null);
@@ -32873,7 +32982,7 @@ var EditorToolbar = ({
32873
32982
  const tableAlignAttr = tableAttrs.tableAlign ?? tableAttrs.textAlign;
32874
32983
  const currentTableAlign = tableAlignAttr === "center" || tableAlignAttr === "right" ? tableAlignAttr : "left";
32875
32984
  const isTableSelected = tableInfo !== null;
32876
- const hasTableContext = isTableSelected || tableCommandAnchorPosRef.current !== null;
32985
+ const hasTableContext = isTableSelected || preservedTableAnchorPos !== null;
32877
32986
  const canMergeCells = hasTableContext && editor.can().mergeCells();
32878
32987
  const canSplitCell = hasTableContext && editor.can().splitCell();
32879
32988
  const currentCellVerticalAlign = normalizeStyleValue(editor.getAttributes("tableCell").verticalAlign || editor.getAttributes("tableHeader").verticalAlign) || "";
@@ -32898,7 +33007,7 @@ var EditorToolbar = ({
32898
33007
  const displayedFontFamilyValue = currentFontFamily || defaultFontFamilyValue;
32899
33008
  const displayedFontSizeLabel = currentFontSize ? currentFontSizeLabel : "13";
32900
33009
  const activeFontSize = currentFontSize || "13px";
32901
- const tableCommandAnchorPos = tableCommandAnchorPosRef.current ?? tableAnchorPos ?? void 0;
33010
+ const tableCommandAnchorPos = preservedTableAnchorPos ?? tableAnchorPos ?? void 0;
32902
33011
  const isMedium = variant === "medium";
32903
33012
  const isMediumFull = variant === "medium-full";
32904
33013
  const isFull = variant === "default" || variant === "full" || variant === "notion" || !variant;
@@ -33574,7 +33683,7 @@ var EditorToolbar = ({
33574
33683
  isOpen: isTableMenuOpen,
33575
33684
  onOpenChange: (open) => {
33576
33685
  setIsTableMenuOpen(open);
33577
- tableCommandAnchorPosRef.current = open ? getTableAnchorPos(editor) : null;
33686
+ setPreservedTableAnchorPos(open ? getTableAnchorPos(editor) : null);
33578
33687
  },
33579
33688
  trigger: /* @__PURE__ */ jsxs75(ToolbarButton, { onClick: () => {
33580
33689
  }, title: t("toolbar.table"), children: [
@@ -33591,6 +33700,7 @@ var EditorToolbar = ({
33591
33700
  onInsert: (rows, cols) => {
33592
33701
  editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
33593
33702
  setIsTableMenuOpen(false);
33703
+ setPreservedTableAnchorPos(null);
33594
33704
  }
33595
33705
  }
33596
33706
  ),
@@ -33806,6 +33916,7 @@ import {
33806
33916
  } from "lucide-react";
33807
33917
 
33808
33918
  // src/components/UEditor/table-formula-commands.ts
33919
+ import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
33809
33920
  import { selectedRect as selectedRect2, setCellAttr as setCellAttr2, TableMap as TableMap3 } from "@tiptap/pm/tables";
33810
33921
 
33811
33922
  // src/components/UEditor/table-formula.ts
@@ -33983,51 +34094,98 @@ function buildTableFormulaDependencyGraph(cells) {
33983
34094
  return { dependencies, dependents, formulas };
33984
34095
  }
33985
34096
  function getTableFormulaCircularReferences(graph) {
33986
- const visiting = /* @__PURE__ */ new Set();
33987
- const visited = /* @__PURE__ */ new Set();
33988
34097
  const circular = /* @__PURE__ */ new Set();
33989
- const stack = [];
33990
- const visit = (label) => {
33991
- if (visiting.has(label)) {
33992
- const start = stack.indexOf(label);
33993
- for (const cycleLabel of start >= 0 ? stack.slice(start) : [label]) {
33994
- circular.add(cycleLabel);
34098
+ const visited = /* @__PURE__ */ new Set();
34099
+ const finishOrder = [];
34100
+ for (const startLabel of graph.formulas.keys()) {
34101
+ if (visited.has(startLabel)) continue;
34102
+ visited.add(startLabel);
34103
+ const stack = [
34104
+ {
34105
+ label: startLabel,
34106
+ references: Array.from(graph.dependencies.get(startLabel) ?? []).filter((ref) => graph.formulas.has(ref)),
34107
+ nextIndex: 0
33995
34108
  }
33996
- return;
33997
- }
33998
- if (visited.has(label)) return;
33999
- visiting.add(label);
34000
- stack.push(label);
34001
- for (const ref of graph.dependencies.get(label) ?? []) {
34002
- if (graph.formulas.has(ref)) {
34003
- visit(ref);
34109
+ ];
34110
+ while (stack.length > 0) {
34111
+ const frame = stack[stack.length - 1];
34112
+ if (!frame) break;
34113
+ const reference = frame.references[frame.nextIndex];
34114
+ if (reference) {
34115
+ frame.nextIndex += 1;
34116
+ if (!visited.has(reference)) {
34117
+ visited.add(reference);
34118
+ stack.push({
34119
+ label: reference,
34120
+ references: Array.from(graph.dependencies.get(reference) ?? []).filter((ref) => graph.formulas.has(ref)),
34121
+ nextIndex: 0
34122
+ });
34123
+ }
34124
+ continue;
34004
34125
  }
34126
+ stack.pop();
34127
+ finishOrder.push(frame.label);
34128
+ }
34129
+ }
34130
+ const assigned = /* @__PURE__ */ new Set();
34131
+ for (let index = finishOrder.length - 1; index >= 0; index -= 1) {
34132
+ const startLabel = finishOrder[index];
34133
+ if (!startLabel || assigned.has(startLabel)) continue;
34134
+ const component = [];
34135
+ const stack = [startLabel];
34136
+ assigned.add(startLabel);
34137
+ while (stack.length > 0) {
34138
+ const label = stack.pop();
34139
+ if (!label) continue;
34140
+ component.push(label);
34141
+ for (const dependent of graph.dependents.get(label) ?? []) {
34142
+ if (!graph.formulas.has(dependent) || assigned.has(dependent)) continue;
34143
+ assigned.add(dependent);
34144
+ stack.push(dependent);
34145
+ }
34146
+ }
34147
+ if (component.length > 1) {
34148
+ for (const label of component) circular.add(label);
34149
+ } else {
34150
+ const label = component[0];
34151
+ if (label && graph.dependencies.get(label)?.has(label)) circular.add(label);
34005
34152
  }
34006
- stack.pop();
34007
- visiting.delete(label);
34008
- visited.add(label);
34009
- };
34010
- for (const label of graph.formulas.keys()) {
34011
- visit(label);
34012
34153
  }
34013
34154
  return circular;
34014
34155
  }
34015
34156
  function getTableFormulaRecalculationOrder(graph) {
34016
34157
  const circular = getTableFormulaCircularReferences(graph);
34158
+ const visiting = /* @__PURE__ */ new Set();
34017
34159
  const visited = /* @__PURE__ */ new Set();
34018
34160
  const order = [];
34019
- const visit = (label) => {
34020
- if (visited.has(label) || circular.has(label)) return;
34021
- visited.add(label);
34022
- for (const ref of graph.dependencies.get(label) ?? []) {
34023
- if (graph.formulas.has(ref)) {
34024
- visit(ref);
34161
+ for (const startLabel of graph.formulas.keys()) {
34162
+ if (visited.has(startLabel) || circular.has(startLabel)) continue;
34163
+ const stack = [];
34164
+ const push = (label) => {
34165
+ visiting.add(label);
34166
+ stack.push({
34167
+ label,
34168
+ references: Array.from(graph.dependencies.get(label) ?? []).filter((ref) => graph.formulas.has(ref) && !circular.has(ref)),
34169
+ nextIndex: 0
34170
+ });
34171
+ };
34172
+ push(startLabel);
34173
+ while (stack.length > 0) {
34174
+ const frame = stack[stack.length - 1];
34175
+ if (!frame) break;
34176
+ const reference = frame.references[frame.nextIndex];
34177
+ if (reference) {
34178
+ frame.nextIndex += 1;
34179
+ if (!visited.has(reference) && !visiting.has(reference)) {
34180
+ push(reference);
34181
+ }
34182
+ continue;
34025
34183
  }
34184
+ stack.pop();
34185
+ visiting.delete(frame.label);
34186
+ visited.add(frame.label);
34187
+ order.push(frame.label);
34026
34188
  }
34027
- order.push(label);
34028
- };
34029
- for (const label of graph.formulas.keys()) {
34030
- visit(label);
34031
34189
  }
34032
34190
  return { order, circular };
34033
34191
  }
@@ -34086,9 +34244,11 @@ function tokenizeFormula(formula) {
34086
34244
  index += 1;
34087
34245
  continue;
34088
34246
  }
34089
- const numberMatch = formula.slice(index).match(/^\d+(?:\.\d+)?/);
34247
+ const numberMatch = formula.slice(index).match(/^(?:\d+(?:\.\d*)?|\.\d+)/);
34090
34248
  if (numberMatch?.[0]) {
34091
- tokens.push({ type: "number", value: Number.parseFloat(numberMatch[0]) });
34249
+ const value = Number.parseFloat(numberMatch[0]);
34250
+ if (!Number.isFinite(value)) return null;
34251
+ tokens.push({ type: "number", value });
34092
34252
  index += numberMatch[0].length;
34093
34253
  continue;
34094
34254
  }
@@ -34106,6 +34266,28 @@ function tokenizeFormula(formula) {
34106
34266
  }
34107
34267
  return tokens;
34108
34268
  }
34269
+ function toFiniteFormulaResult(value) {
34270
+ return Number.isFinite(value) ? { value, error: null } : { value: null, error: "invalid-formula" };
34271
+ }
34272
+ function parseTableCellNumericValue(value) {
34273
+ if (typeof value === "number") {
34274
+ return Number.isFinite(value) ? value : null;
34275
+ }
34276
+ let normalized = String(value ?? "").trim();
34277
+ if (!normalized || normalized.startsWith("#")) return null;
34278
+ const isPercent = normalized.endsWith("%");
34279
+ if (isPercent) normalized = normalized.slice(0, -1).trim();
34280
+ const currencyMatch = normalized.match(/^([+-]?)\$(.+)$/);
34281
+ if (currencyMatch) {
34282
+ normalized = `${currencyMatch[1] ?? ""}${currencyMatch[2] ?? ""}`.trim();
34283
+ }
34284
+ const groupedNumber = /^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?(?:[eE][+-]?\d+)?$/;
34285
+ const plainNumber = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
34286
+ if (!groupedNumber.test(normalized) && !plainNumber.test(normalized)) return null;
34287
+ const parsed = Number(normalized.replace(/,/g, ""));
34288
+ if (!Number.isFinite(parsed)) return null;
34289
+ return isPercent ? parsed / 100 : parsed;
34290
+ }
34109
34291
  var FormulaParser = class {
34110
34292
  constructor(tokens, getCellValue) {
34111
34293
  this.tokens = tokens;
@@ -34123,10 +34305,7 @@ var FormulaParser = class {
34123
34305
  this.index += 1;
34124
34306
  const right = this.parseTerm();
34125
34307
  if (right.error) return right;
34126
- left = {
34127
- value: operator.value === "+" ? left.value + right.value : left.value - right.value,
34128
- error: null
34129
- };
34308
+ left = toFiniteFormulaResult(operator.value === "+" ? left.value + right.value : left.value - right.value);
34130
34309
  }
34131
34310
  return left;
34132
34311
  }
@@ -34141,10 +34320,7 @@ var FormulaParser = class {
34141
34320
  if (operator.value === "/" && right.value === 0) {
34142
34321
  return { value: null, error: "division-by-zero" };
34143
34322
  }
34144
- left = {
34145
- value: operator.value === "*" ? left.value * right.value : left.value / right.value,
34146
- error: null
34147
- };
34323
+ left = toFiniteFormulaResult(operator.value === "*" ? left.value * right.value : left.value / right.value);
34148
34324
  }
34149
34325
  return left;
34150
34326
  }
@@ -34153,11 +34329,11 @@ var FormulaParser = class {
34153
34329
  if (!token) {
34154
34330
  return { value: null, error: "invalid-formula" };
34155
34331
  }
34156
- if (token.type === "operator" && token.value === "-") {
34332
+ if (token.type === "operator" && (token.value === "-" || token.value === "+")) {
34157
34333
  this.index += 1;
34158
34334
  const value = this.parseFactor();
34159
34335
  if (value.error) return value;
34160
- return { value: -value.value, error: null };
34336
+ return toFiniteFormulaResult(token.value === "-" ? -value.value : value.value);
34161
34337
  }
34162
34338
  if (token.type === "number") {
34163
34339
  this.index += 1;
@@ -34202,11 +34378,10 @@ var FormulaParser = class {
34202
34378
  if (cellValue2 != null) values.push(cellValue2);
34203
34379
  continue;
34204
34380
  }
34205
- const cellValue = this.readCellNumber(label);
34206
- if (cellValue.error) return cellValue;
34207
- values.push(cellValue.value);
34381
+ const cellValue = this.readOptionalCellNumber(label);
34382
+ if (cellValue != null) values.push(cellValue);
34208
34383
  }
34209
- } else if (name === "COUNT" && token.type === "cell") {
34384
+ } else if (token.type === "cell") {
34210
34385
  this.index += 1;
34211
34386
  const cellValue = this.readOptionalCellNumber(token.value);
34212
34387
  if (cellValue != null) values.push(cellValue);
@@ -34223,28 +34398,24 @@ var FormulaParser = class {
34223
34398
  }
34224
34399
  return { value: null, error: "invalid-formula" };
34225
34400
  }
34226
- if (values.length === 0 && name !== "COUNT") {
34227
- return { value: null, error: "invalid-formula" };
34401
+ if (name === "SUM") return toFiniteFormulaResult(values.reduce((sum, value) => sum + value, 0));
34402
+ if (name === "AVG") {
34403
+ return values.length > 0 ? toFiniteFormulaResult(values.reduce((sum, value) => sum + value, 0) / values.length) : { value: null, error: "division-by-zero" };
34228
34404
  }
34229
- if (name === "SUM") return { value: values.reduce((sum, value) => sum + value, 0), error: null };
34230
- if (name === "AVG") return { value: values.reduce((sum, value) => sum + value, 0) / values.length, error: null };
34231
- if (name === "MIN") return { value: Math.min(...values), error: null };
34232
- if (name === "MAX") return { value: Math.max(...values), error: null };
34405
+ if (name === "MIN") return toFiniteFormulaResult(values.length > 0 ? Math.min(...values) : 0);
34406
+ if (name === "MAX") return toFiniteFormulaResult(values.length > 0 ? Math.max(...values) : 0);
34233
34407
  if (name === "COUNT") return { value: values.length, error: null };
34234
34408
  return { value: null, error: "invalid-formula" };
34235
34409
  }
34236
34410
  readCellNumber(label) {
34237
- const value = this.getCellValue(label);
34238
- const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
34239
- if (!Number.isFinite(parsed)) {
34411
+ const parsed = parseTableCellNumericValue(this.getCellValue(label));
34412
+ if (parsed == null) {
34240
34413
  return { value: null, error: "invalid-reference" };
34241
34414
  }
34242
34415
  return { value: parsed, error: null };
34243
34416
  }
34244
34417
  readOptionalCellNumber(label) {
34245
- const value = this.getCellValue(label);
34246
- const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
34247
- return Number.isFinite(parsed) ? parsed : null;
34418
+ return parseTableCellNumericValue(this.getCellValue(label));
34248
34419
  }
34249
34420
  peekOperator(operators) {
34250
34421
  const token = this.tokens[this.index];
@@ -34309,6 +34480,75 @@ function getSelectionTableCellNode(editor) {
34309
34480
  }
34310
34481
  return null;
34311
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
+ }
34312
34552
  function getSelectionTableInfo(editor) {
34313
34553
  const { $from } = editor.state.selection;
34314
34554
  for (let depth = $from.depth; depth > 0; depth -= 1) {
@@ -34355,6 +34595,28 @@ function createCellDisplayContent(cellNode, displayValue) {
34355
34595
  }
34356
34596
  return [paragraphType.create(null, cellNode.type.schema.text(displayValue))];
34357
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
+ }
34358
34620
  function buildTableValueMap(tableNode) {
34359
34621
  const map = TableMap3.get(tableNode);
34360
34622
  const values = /* @__PURE__ */ new Map();
@@ -34377,21 +34639,15 @@ function normalizeFormulaInput(formula) {
34377
34639
  function setSelectedTableCellFormula(editor, formula) {
34378
34640
  const normalized = normalizeFormulaInput(formula);
34379
34641
  const { state, view } = editor;
34642
+ const selectionAnchor = getSelectionTableCellAnchor(editor);
34380
34643
  if (!normalized) {
34381
- const clearedFormula = setCellAttr2("formula", null)(state, view.dispatch.bind(view));
34382
- const clearedValue2 = setCellAttr2("computedValue", null)(editor.state, view.dispatch.bind(view));
34383
- if (clearedFormula || clearedValue2) {
34384
- recalculateActiveTableFormulas(editor);
34385
- view.focus();
34386
- dispatchTableLayoutChange(editor);
34387
- return true;
34388
- }
34389
- return false;
34644
+ return clearSelectedTableCellFormula(editor);
34390
34645
  }
34391
34646
  const appliedFormula = setCellAttr2("formula", normalized)(state, view.dispatch.bind(view));
34392
34647
  const clearedValue = setCellAttr2("computedValue", null)(editor.state, view.dispatch.bind(view));
34393
34648
  if (appliedFormula || clearedValue) {
34394
34649
  recalculateActiveTableFormulas(editor);
34650
+ if (selectionAnchor) restoreSelectionAtTableCell(editor, selectionAnchor);
34395
34651
  view.focus();
34396
34652
  dispatchTableLayoutChange(editor);
34397
34653
  return true;
@@ -34399,7 +34655,10 @@ function setSelectedTableCellFormula(editor, formula) {
34399
34655
  return false;
34400
34656
  }
34401
34657
  function clearSelectedTableCellFormula(editor) {
34402
- return setSelectedTableCellFormula(editor, "");
34658
+ return removeSelectedTableCellFormula(editor, true);
34659
+ }
34660
+ function convertSelectedTableCellFormulaToValue(editor) {
34661
+ return removeSelectedTableCellFormula(editor, false);
34403
34662
  }
34404
34663
  function setSelectedTableCellNumberFormat(editor, numberFormat) {
34405
34664
  const value = numberFormat && numberFormat !== "text" ? numberFormat : null;
@@ -34599,6 +34858,58 @@ function recalculateAllTableFormulas(editor) {
34599
34858
 
34600
34859
  // src/components/UEditor/menus.tsx
34601
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
+ };
34602
34913
  function applyTableCellBackground(editor, color) {
34603
34914
  const value = color || null;
34604
34915
  const { state, view } = editor;
@@ -35337,7 +35648,8 @@ var LinkPreviewContent = ({ editor, onEdit }) => {
35337
35648
  const url = editor.getAttributes("link").href || "";
35338
35649
  const t = useSmartTranslations("UEditor");
35339
35650
  const handleOpen = () => {
35340
- window.open(url, "_blank", "noopener,noreferrer");
35651
+ const safeUrl = sanitizeUEditorUrl(url, "link");
35652
+ if (safeUrl) window.open(safeUrl, "_blank", "noopener,noreferrer");
35341
35653
  };
35342
35654
  const handleUnlink = () => {
35343
35655
  editor.chain().focus().unsetLink().run();
@@ -35360,23 +35672,26 @@ var CustomBubbleMenu = ({
35360
35672
  const BUBBLE_MENU_ESTIMATED_HEIGHT = 44;
35361
35673
  const [isVisible, setIsVisible] = useState50(false);
35362
35674
  const [linkInputOpen, setLinkInputOpen] = useState50(false);
35363
- const [position, setPosition] = useState50({
35675
+ const [position, setPosition2] = useState50({
35364
35676
  top: 0,
35365
35677
  left: 0,
35366
35678
  placement: "top"
35367
35679
  });
35368
35680
  const menuRef = useRef34(null);
35369
35681
  const keepOpenRef = useRef34(false);
35682
+ const [keepOpen, setKeepOpenState] = useState50(false);
35370
35683
  const showTimeoutRef = useRef34(null);
35371
35684
  const suppressShowUntilRef = useRef34(0);
35372
35685
  const setKeepOpen = useCallback23((next) => {
35373
35686
  keepOpenRef.current = next;
35687
+ setKeepOpenState(next);
35374
35688
  if (!next) setLinkInputOpen(false);
35375
35689
  if (next) setIsVisible(true);
35376
35690
  }, []);
35377
35691
  const closeBubbleMenu = useCallback23(() => {
35378
35692
  suppressShowUntilRef.current = Date.now() + 1e3;
35379
35693
  keepOpenRef.current = false;
35694
+ setKeepOpenState(false);
35380
35695
  setLinkInputOpen(false);
35381
35696
  setIsVisible(false);
35382
35697
  if (showTimeoutRef.current) {
@@ -35432,7 +35747,7 @@ var CustomBubbleMenu = ({
35432
35747
  Math.max(viewportPadding, (start.left + end.left) / 2)
35433
35748
  );
35434
35749
  const top = placement === "top" ? Math.max(viewportPadding, selectionTop - BUBBLE_MENU_OFFSET) : Math.min(window.innerHeight - viewportPadding, selectionBottom + BUBBLE_MENU_OFFSET);
35435
- setPosition({ top, left, placement });
35750
+ setPosition2({ top, left, placement });
35436
35751
  if (keepOpenRef.current) {
35437
35752
  clearShowTimeout();
35438
35753
  setIsVisible(true);
@@ -35482,13 +35797,13 @@ var CustomBubbleMenu = ({
35482
35797
  onMouseDown: (e) => {
35483
35798
  const target = e.target;
35484
35799
  if (target?.closest?.("[data-ueditor-close-on-select]")) {
35485
- keepOpenRef.current = false;
35800
+ setKeepOpen(false);
35486
35801
  } else if (target?.closest?.("[data-ueditor-keep-open]")) {
35487
- keepOpenRef.current = true;
35802
+ setKeepOpen(true);
35488
35803
  }
35489
35804
  e.preventDefault();
35490
35805
  },
35491
- children: editor.isActive("link") && !keepOpenRef.current && !linkInputOpen ? /* @__PURE__ */ jsx92(
35806
+ children: editor.isActive("link") && !keepOpen && !linkInputOpen ? /* @__PURE__ */ jsx92(
35492
35807
  LinkPreviewContent,
35493
35808
  {
35494
35809
  editor,
@@ -35514,6 +35829,54 @@ var CustomBubbleMenu = ({
35514
35829
  document.body
35515
35830
  );
35516
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
+ };
35517
35880
 
35518
35881
  // src/components/UEditor/CharacterCount.tsx
35519
35882
  import { jsxs as jsxs77 } from "react/jsx-runtime";
@@ -38589,7 +38952,7 @@ var Selection = class {
38589
38952
  found.
38590
38953
  */
38591
38954
  static findFrom($pos, dir, textOnly = false) {
38592
- 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);
38593
38956
  if (inner)
38594
38957
  return inner;
38595
38958
  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
@@ -38658,7 +39021,7 @@ var Selection = class {
38658
39021
  returns the bookmark for that.
38659
39022
  */
38660
39023
  getBookmark() {
38661
- return TextSelection3.between(this.$anchor, this.$head).getBookmark();
39024
+ return TextSelection4.between(this.$anchor, this.$head).getBookmark();
38662
39025
  }
38663
39026
  };
38664
39027
  Selection.prototype.visible = true;
@@ -38678,7 +39041,7 @@ function checkTextSelection($pos) {
38678
39041
  console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
38679
39042
  }
38680
39043
  }
38681
- var TextSelection3 = class _TextSelection extends Selection {
39044
+ var TextSelection4 = class _TextSelection extends Selection {
38682
39045
  /**
38683
39046
  Construct a text selection between the given points.
38684
39047
  */
@@ -38764,7 +39127,7 @@ var TextSelection3 = class _TextSelection extends Selection {
38764
39127
  return new _TextSelection($anchor, $head);
38765
39128
  }
38766
39129
  };
38767
- Selection.jsonID("text", TextSelection3);
39130
+ Selection.jsonID("text", TextSelection4);
38768
39131
  var TextBookmark = class _TextBookmark {
38769
39132
  constructor(anchor, head) {
38770
39133
  this.anchor = anchor;
@@ -38774,7 +39137,7 @@ var TextBookmark = class _TextBookmark {
38774
39137
  return new _TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
38775
39138
  }
38776
39139
  resolve(doc) {
38777
- return TextSelection3.between(doc.resolve(this.anchor), doc.resolve(this.head));
39140
+ return TextSelection4.between(doc.resolve(this.anchor), doc.resolve(this.head));
38778
39141
  }
38779
39142
  };
38780
39143
  var NodeSelection2 = class _NodeSelection extends Selection {
@@ -38893,7 +39256,7 @@ var AllBookmark = {
38893
39256
  };
38894
39257
  function findSelectionIn(doc, node, pos, index, dir, text = false) {
38895
39258
  if (node.inlineContent)
38896
- return TextSelection3.create(doc, pos);
39259
+ return TextSelection4.create(doc, pos);
38897
39260
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
38898
39261
  let child = node.child(i);
38899
39262
  if (!child.isAtom) {
@@ -39485,7 +39848,7 @@ var CellSelection = class CellSelection2 extends Selection {
39485
39848
  else if (tableChanged && this.isColSelection()) return CellSelection2.colSelection($anchorCell, $headCell);
39486
39849
  else return new CellSelection2($anchorCell, $headCell);
39487
39850
  }
39488
- return TextSelection3.between($anchorCell, $headCell);
39851
+ return TextSelection4.between($anchorCell, $headCell);
39489
39852
  }
39490
39853
  content() {
39491
39854
  const table = this.$anchorCell.node(-1);
@@ -40100,7 +40463,7 @@ function shiftArrow(axis, dir) {
40100
40463
  };
40101
40464
  }
40102
40465
  function atEndOfCell(view, axis, dir) {
40103
- if (!(view.state.selection instanceof TextSelection3)) return null;
40466
+ if (!(view.state.selection instanceof TextSelection4)) return null;
40104
40467
  const { $head } = view.state.selection;
40105
40468
  for (let d = $head.depth - 1; d >= 0; d--) {
40106
40469
  const parent = $head.node(d);
@@ -41231,7 +41594,7 @@ function TableControls({ editor, containerRef }) {
41231
41594
  destructive: true
41232
41595
  }
41233
41596
  ];
41234
- }, [layout, runAtActiveCell, t]);
41597
+ }, [editor, layout, runAtActiveCell, t]);
41235
41598
  const getRowHandleMenuItems = React83.useCallback((rowHandle) => [
41236
41599
  {
41237
41600
  label: t("tableMenu.addRowBefore"),
@@ -41447,6 +41810,42 @@ var UEDITOR_PROSEMIRROR_CLASS_NAME = cn(
41447
41810
  "[&_th]:px-2",
41448
41811
  "[&_th]:py-0",
41449
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",
41450
41849
  "[&_td[colwidth]]:min-w-0",
41451
41850
  "[&_th[colwidth]]:min-w-0",
41452
41851
  "[&_td[data-colwidth]]:min-w-0",
@@ -41952,7 +42351,7 @@ function useUEditorTableInteractions(editor, editable = true) {
41952
42351
  clearHoveredTableCell();
41953
42352
  clearAllTableResizeHover();
41954
42353
  };
41955
- }, [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]);
41956
42355
  return {
41957
42356
  editorContentRef,
41958
42357
  tableColumnGuideRef,
@@ -41961,8 +42360,401 @@ function useUEditorTableInteractions(editor, editable = true) {
41961
42360
  };
41962
42361
  }
41963
42362
 
42363
+ // src/components/UEditor/use-formula-coordinate-overlay.ts
42364
+ import { useEffect as useEffect39, useRef as useRef37 } from "react";
42365
+ import { TableMap as TableMap6 } from "@tiptap/pm/tables";
42366
+
42367
+ // src/components/UEditor/table-formula-range-picker.ts
42368
+ import { TextSelection as TextSelection5 } from "@tiptap/pm/state";
42369
+ import { TableMap as TableMap5 } from "@tiptap/pm/tables";
42370
+ function getCellText2(cellNode) {
42371
+ return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
42372
+ }
42373
+ function getFormulaEditingTableContext(view) {
42374
+ const { $from } = view.state.selection;
42375
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
42376
+ const node = $from.node(depth);
42377
+ if (node.type.name !== "tableCell" && node.type.name !== "tableHeader") continue;
42378
+ if (!getCellText2(node).startsWith("=")) return null;
42379
+ const cellPos = $from.before(depth);
42380
+ const cellDom = view.nodeDOM(cellPos);
42381
+ if (!(cellDom instanceof HTMLTableCellElement)) return null;
42382
+ const tableDepth = depth - 2;
42383
+ const tableNode = tableDepth > 0 ? $from.node(tableDepth) : null;
42384
+ if (!tableNode || tableNode.type.name !== "table") return null;
42385
+ const tableDom = cellDom.closest("table");
42386
+ if (!(tableDom instanceof HTMLTableElement)) return null;
42387
+ return {
42388
+ cellContentEnd: $from.end(depth),
42389
+ cellContentStart: $from.start(depth),
42390
+ cellDom,
42391
+ formula: getCellText2(node),
42392
+ tableDom,
42393
+ tableNode,
42394
+ tablePos: $from.before(tableDepth)
42395
+ };
42396
+ }
42397
+ return null;
42398
+ }
42399
+ function cancelFormulaEditing(view) {
42400
+ const context = getFormulaEditingTableContext(view);
42401
+ if (!context) return false;
42402
+ let tr = view.state.tr.delete(context.cellContentStart, context.cellContentEnd);
42403
+ const selectionPos = Math.min(context.cellContentStart, tr.doc.content.size);
42404
+ tr = tr.setSelection(TextSelection5.near(tr.doc.resolve(selectionPos)));
42405
+ view.dispatch(tr);
42406
+ view.focus();
42407
+ return true;
42408
+ }
42409
+ function getReferenceInsertionPrefix(view, cellContentStart, insertionPos) {
42410
+ const textBeforeCursor = view.state.doc.textBetween(cellContentStart, insertionPos, "", "");
42411
+ const isInsideFunctionArguments = /[A-Z]+\([^)]*$/i.test(textBeforeCursor);
42412
+ const followsReference = /[A-Z]+[1-9]\d*(?::[A-Z]+[1-9]\d*)?\s*$/i.test(textBeforeCursor);
42413
+ return isInsideFunctionArguments && followsReference ? "," : "";
42414
+ }
42415
+ function findTableForPos(view, pos) {
42416
+ const $pos = view.state.doc.resolve(pos);
42417
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
42418
+ const node = $pos.node(depth);
42419
+ if (node.type.name === "table") {
42420
+ return {
42421
+ node,
42422
+ pos: $pos.before(depth),
42423
+ start: $pos.start(depth)
42424
+ };
42425
+ }
42426
+ }
42427
+ return null;
42428
+ }
42429
+ function getCellRelativePosFromDomPos2(map, tableStart, domPos) {
42430
+ const relativeDomPos = domPos - tableStart;
42431
+ const seen = /* @__PURE__ */ new Set();
42432
+ for (const relativeCellPos of map.map) {
42433
+ if (seen.has(relativeCellPos)) continue;
42434
+ seen.add(relativeCellPos);
42435
+ if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
42436
+ return relativeCellPos;
42437
+ }
42438
+ }
42439
+ return null;
42440
+ }
42441
+ function getFormulaTableCellInfo(view, target, tablePos) {
42442
+ const element = resolveEventElement(target);
42443
+ const cell = element?.closest?.("th,td");
42444
+ if (!(cell instanceof HTMLTableCellElement)) return null;
42445
+ const domPos = view.posAtDOM(cell, 0);
42446
+ const tableInfo = findTableForPos(view, domPos);
42447
+ if (!tableInfo || tableInfo.pos !== tablePos) return null;
42448
+ const map = TableMap5.get(tableInfo.node);
42449
+ const relativeCellPos = getCellRelativePosFromDomPos2(map, tableInfo.start, domPos);
42450
+ if (relativeCellPos == null) return null;
42451
+ const rect = map.findCell(relativeCellPos);
42452
+ return {
42453
+ cell,
42454
+ label: `${indexToColumnName(rect.left)}${rect.top + 1}`,
42455
+ rect
42456
+ };
42457
+ }
42458
+ function normalizeFormulaRangeLabel(fromLabel, toLabel) {
42459
+ return fromLabel === toLabel ? fromLabel : `${fromLabel}:${toLabel}`;
42460
+ }
42461
+ function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42462
+ if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell) return pickState;
42463
+ if (nextLabel === pickState.currentLabel) {
42464
+ return {
42465
+ ...pickState,
42466
+ currentCell
42467
+ };
42468
+ }
42469
+ let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42470
+ const nextTo = pickState.insertedFrom + nextLabel.length;
42471
+ tr = tr.setSelection(TextSelection5.create(tr.doc, nextTo));
42472
+ view.dispatch(tr);
42473
+ return {
42474
+ ...pickState,
42475
+ insertedTo: nextTo,
42476
+ currentLabel: nextLabel,
42477
+ currentCell
42478
+ };
42479
+ }
42480
+ function beginFormulaRangePick(view, event) {
42481
+ if (event.button !== 0) return null;
42482
+ const formulaCell = getFormulaEditingTableContext(view);
42483
+ if (!formulaCell) return null;
42484
+ const picked = getFormulaTableCellInfo(view, event.target, formulaCell.tablePos);
42485
+ if (!picked || picked.cell === formulaCell.cellDom) return null;
42486
+ const { from, to } = view.state.selection;
42487
+ const prefix = from === to ? getReferenceInsertionPrefix(view, formulaCell.cellContentStart, from) : "";
42488
+ const insertedText = `${prefix}${picked.label}`;
42489
+ const insertedFrom = from + prefix.length;
42490
+ let tr = view.state.tr.insertText(insertedText, from, to);
42491
+ tr = tr.setSelection(TextSelection5.create(tr.doc, from + insertedText.length));
42492
+ view.dispatch(tr);
42493
+ view.focus();
42494
+ event.preventDefault();
42495
+ event.stopPropagation();
42496
+ return {
42497
+ anchorLabel: picked.label,
42498
+ anchorCell: picked.cell,
42499
+ tablePos: formulaCell.tablePos,
42500
+ insertedFrom,
42501
+ insertedTo: insertedFrom + picked.label.length,
42502
+ currentLabel: picked.label,
42503
+ currentCell: picked.cell
42504
+ };
42505
+ }
42506
+ function updateFormulaRangePick(view, pickState, event) {
42507
+ const picked = getFormulaTableCellInfo(view, event.target, pickState.tablePos);
42508
+ if (!picked) return pickState;
42509
+ event.preventDefault();
42510
+ event.stopPropagation();
42511
+ return replacePickedLabel(
42512
+ view,
42513
+ pickState,
42514
+ normalizeFormulaRangeLabel(pickState.anchorLabel, picked.label),
42515
+ picked.cell
42516
+ );
42517
+ }
42518
+ function getFormulaRangePickHighlight(container, pickState) {
42519
+ if (!container.contains(pickState.anchorCell) || !container.contains(pickState.currentCell)) return null;
42520
+ const containerRect = container.getBoundingClientRect();
42521
+ const anchorRect = pickState.anchorCell.getBoundingClientRect();
42522
+ const currentRect = pickState.currentCell.getBoundingClientRect();
42523
+ const left = Math.min(anchorRect.left, currentRect.left) - containerRect.left + container.scrollLeft;
42524
+ const top = Math.min(anchorRect.top, currentRect.top) - containerRect.top + container.scrollTop;
42525
+ const right = Math.max(anchorRect.right, currentRect.right) - containerRect.left + container.scrollLeft;
42526
+ const bottom = Math.max(anchorRect.bottom, currentRect.bottom) - containerRect.top + container.scrollTop;
42527
+ return {
42528
+ left,
42529
+ top,
42530
+ width: Math.max(0, right - left),
42531
+ height: Math.max(0, bottom - top)
42532
+ };
42533
+ }
42534
+
42535
+ // src/components/UEditor/use-formula-coordinate-overlay.ts
42536
+ function setPosition(element, left, top, width, height) {
42537
+ element.style.left = `${left}px`;
42538
+ element.style.top = `${top}px`;
42539
+ if (width != null) element.style.width = `${Math.max(0, width)}px`;
42540
+ if (height != null) element.style.height = `${Math.max(0, height)}px`;
42541
+ }
42542
+ function createCoordinateLabel(kind, label) {
42543
+ const element = document.createElement("span");
42544
+ element.dataset.ueditorFormulaCoordinate = kind;
42545
+ element.textContent = label;
42546
+ element.className = "pointer-events-none absolute z-30 flex items-center justify-center rounded-[3px] border border-primary/35 bg-primary/90 px-1 font-mono text-[10px] font-semibold leading-none text-primary-foreground shadow-sm";
42547
+ return element;
42548
+ }
42549
+ function createReferenceHighlight(label) {
42550
+ const element = document.createElement("span");
42551
+ element.dataset.ueditorFormulaReference = label;
42552
+ element.className = "pointer-events-none absolute z-[21] rounded-[2px] border-2 border-emerald-500 bg-emerald-500/15";
42553
+ return element;
42554
+ }
42555
+ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42556
+ const overlayRef = useRef37(null);
42557
+ useEffect39(() => {
42558
+ if (!editor) return void 0;
42559
+ const overlay = overlayRef.current;
42560
+ const container = containerRef.current;
42561
+ if (!overlay || !container) return void 0;
42562
+ let animationFrame = null;
42563
+ let activeTable = null;
42564
+ let activeTablePos = null;
42565
+ let hoverLabel = null;
42566
+ const clearOverlay = () => {
42567
+ activeTable = null;
42568
+ activeTablePos = null;
42569
+ hoverLabel = null;
42570
+ overlay.replaceChildren();
42571
+ overlay.style.display = "none";
42572
+ };
42573
+ const syncOverlay = () => {
42574
+ animationFrame = null;
42575
+ if (editor.isDestroyed) return;
42576
+ const context = getFormulaEditingTableContext(editor.view);
42577
+ if (!context) {
42578
+ clearOverlay();
42579
+ return;
42580
+ }
42581
+ const containerRect = container.getBoundingClientRect();
42582
+ const tableRect = context.tableDom.getBoundingClientRect();
42583
+ const tableLeft = tableRect.left - containerRect.left + container.scrollLeft;
42584
+ const tableTop = tableRect.top - containerRect.top + container.scrollTop;
42585
+ const map = TableMap6.get(context.tableNode);
42586
+ const columnSegments = Array(map.width);
42587
+ const rowSegments = Array(map.height);
42588
+ const cellInfos = [];
42589
+ for (const cell of context.tableDom.querySelectorAll("th,td")) {
42590
+ if (!(cell instanceof HTMLTableCellElement)) continue;
42591
+ const info = getFormulaTableCellInfo(editor.view, cell, context.tablePos);
42592
+ if (!info) continue;
42593
+ cellInfos.push(info);
42594
+ const cellRect = cell.getBoundingClientRect();
42595
+ const columnSpan = Math.max(1, info.rect.right - info.rect.left);
42596
+ const rowSpan = Math.max(1, info.rect.bottom - info.rect.top);
42597
+ const columnWidth = cellRect.width / columnSpan;
42598
+ const rowHeight = cellRect.height / rowSpan;
42599
+ for (let column = info.rect.left; column < info.rect.right; column += 1) {
42600
+ if (!columnSegments[column] || columnSpan < columnSegments[column].span) {
42601
+ columnSegments[column] = {
42602
+ start: cellRect.left - containerRect.left + container.scrollLeft + (column - info.rect.left) * columnWidth,
42603
+ size: columnWidth,
42604
+ span: columnSpan
42605
+ };
42606
+ }
42607
+ }
42608
+ for (let row = info.rect.top; row < info.rect.bottom; row += 1) {
42609
+ if (!rowSegments[row] || rowSpan < rowSegments[row].span) {
42610
+ rowSegments[row] = {
42611
+ start: cellRect.top - containerRect.top + container.scrollTop + (row - info.rect.top) * rowHeight,
42612
+ size: rowHeight,
42613
+ span: rowSpan
42614
+ };
42615
+ }
42616
+ }
42617
+ }
42618
+ const fallbackColumnWidth = map.width > 0 ? tableRect.width / map.width : 0;
42619
+ const fallbackRowHeight = map.height > 0 ? tableRect.height / map.height : 0;
42620
+ const children = [];
42621
+ const actions = document.createElement("div");
42622
+ actions.dataset.ueditorFormulaActions = "";
42623
+ actions.className = "pointer-events-auto absolute z-50 flex items-center gap-1 rounded-md border border-border bg-background p-1 shadow-md";
42624
+ const formulaCellRect = context.cellDom.getBoundingClientRect();
42625
+ setPosition(
42626
+ actions,
42627
+ formulaCellRect.left - containerRect.left + container.scrollLeft,
42628
+ formulaCellRect.bottom - containerRect.top + container.scrollTop + 4
42629
+ );
42630
+ const applyButton = document.createElement("button");
42631
+ applyButton.type = "button";
42632
+ applyButton.dataset.ueditorFormulaApply = "";
42633
+ applyButton.disabled = isDraftTableFormula(context.formula);
42634
+ applyButton.textContent = `${applyButton.disabled ? "" : "\u2713 "}${labels.apply} (Enter)`;
42635
+ applyButton.className = "rounded bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-45";
42636
+ const cancelButton = document.createElement("button");
42637
+ cancelButton.type = "button";
42638
+ cancelButton.dataset.ueditorFormulaCancel = "";
42639
+ cancelButton.textContent = `${labels.cancel} (Esc)`;
42640
+ cancelButton.className = "rounded px-2.5 py-1 text-xs font-medium text-foreground hover:bg-muted";
42641
+ const preserveSelection = (event) => {
42642
+ event.preventDefault();
42643
+ event.stopPropagation();
42644
+ };
42645
+ applyButton.addEventListener("mousedown", preserveSelection);
42646
+ cancelButton.addEventListener("mousedown", preserveSelection);
42647
+ applyButton.addEventListener("click", (event) => {
42648
+ event.preventDefault();
42649
+ event.stopPropagation();
42650
+ if (!applyButton.disabled) recalculateActiveTableFormulas(editor);
42651
+ });
42652
+ cancelButton.addEventListener("click", (event) => {
42653
+ event.preventDefault();
42654
+ event.stopPropagation();
42655
+ cancelFormulaEditing(editor.view);
42656
+ });
42657
+ actions.append(applyButton, cancelButton);
42658
+ children.push(actions);
42659
+ for (let column = 0; column < map.width; column += 1) {
42660
+ const segment = columnSegments[column] ?? {
42661
+ start: tableLeft + column * fallbackColumnWidth,
42662
+ size: fallbackColumnWidth,
42663
+ span: map.width
42664
+ };
42665
+ const label = createCoordinateLabel("column", indexToColumnName(column));
42666
+ setPosition(label, segment.start, Math.max(0, tableTop - 22), segment.size, 20);
42667
+ children.push(label);
42668
+ }
42669
+ for (let row = 0; row < map.height; row += 1) {
42670
+ const segment = rowSegments[row] ?? {
42671
+ start: tableTop + row * fallbackRowHeight,
42672
+ size: fallbackRowHeight,
42673
+ span: map.height
42674
+ };
42675
+ const label = createCoordinateLabel("row", String(row + 1));
42676
+ setPosition(label, Math.max(0, tableLeft - 28), segment.start, 26, segment.size);
42677
+ children.push(label);
42678
+ }
42679
+ const references = new Set(getTableFormulaReferences(context.formula));
42680
+ for (const info of cellInfos) {
42681
+ if (!info || !references.has(info.label)) continue;
42682
+ const cellRect = info.cell.getBoundingClientRect();
42683
+ const highlight = createReferenceHighlight(info.label);
42684
+ setPosition(
42685
+ highlight,
42686
+ cellRect.left - containerRect.left + container.scrollLeft + 2,
42687
+ cellRect.top - containerRect.top + container.scrollTop + 2,
42688
+ cellRect.width - 4,
42689
+ cellRect.height - 4
42690
+ );
42691
+ children.push(highlight);
42692
+ }
42693
+ hoverLabel = document.createElement("span");
42694
+ hoverLabel.dataset.ueditorFormulaHoverLabel = "";
42695
+ hoverLabel.className = "pointer-events-none absolute z-40 hidden rounded bg-foreground px-1.5 py-1 font-mono text-[10px] font-semibold leading-none text-background shadow-md";
42696
+ children.push(hoverLabel);
42697
+ overlay.replaceChildren(...children);
42698
+ overlay.style.display = "block";
42699
+ activeTable = context.tableDom;
42700
+ activeTablePos = context.tablePos;
42701
+ };
42702
+ const scheduleSync = () => {
42703
+ if (animationFrame != null) return;
42704
+ animationFrame = window.requestAnimationFrame(syncOverlay);
42705
+ };
42706
+ const handleMouseMove2 = (event) => {
42707
+ if (!activeTable || activeTablePos == null || !hoverLabel) return;
42708
+ const target = event.target instanceof Element ? event.target.closest("th,td") : null;
42709
+ if (!(target instanceof HTMLTableCellElement) || target.closest("table") !== activeTable) {
42710
+ hoverLabel.style.display = "none";
42711
+ return;
42712
+ }
42713
+ const info = getFormulaTableCellInfo(editor.view, target, activeTablePos);
42714
+ if (!info) {
42715
+ hoverLabel.style.display = "none";
42716
+ return;
42717
+ }
42718
+ const containerRect = container.getBoundingClientRect();
42719
+ const cellRect = target.getBoundingClientRect();
42720
+ hoverLabel.textContent = info.label;
42721
+ hoverLabel.style.display = "block";
42722
+ setPosition(
42723
+ hoverLabel,
42724
+ cellRect.left - containerRect.left + container.scrollLeft + 4,
42725
+ cellRect.top - containerRect.top + container.scrollTop + 4
42726
+ );
42727
+ };
42728
+ const handleMouseLeave2 = () => {
42729
+ if (hoverLabel) hoverLabel.style.display = "none";
42730
+ };
42731
+ editor.on("selectionUpdate", scheduleSync);
42732
+ editor.on("update", scheduleSync);
42733
+ editor.on("focus", scheduleSync);
42734
+ editor.on("blur", scheduleSync);
42735
+ editor.view.dom.addEventListener("mousemove", handleMouseMove2);
42736
+ editor.view.dom.addEventListener("mouseleave", handleMouseLeave2);
42737
+ container.addEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42738
+ window.addEventListener("resize", scheduleSync);
42739
+ scheduleSync();
42740
+ return () => {
42741
+ editor.off("selectionUpdate", scheduleSync);
42742
+ editor.off("update", scheduleSync);
42743
+ editor.off("focus", scheduleSync);
42744
+ editor.off("blur", scheduleSync);
42745
+ editor.view.dom.removeEventListener("mousemove", handleMouseMove2);
42746
+ editor.view.dom.removeEventListener("mouseleave", handleMouseLeave2);
42747
+ container.removeEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42748
+ window.removeEventListener("resize", scheduleSync);
42749
+ if (animationFrame != null) window.cancelAnimationFrame(animationFrame);
42750
+ clearOverlay();
42751
+ };
42752
+ }, [containerRef, editor, labels.apply, labels.cancel]);
42753
+ return overlayRef;
42754
+ }
42755
+
41964
42756
  // src/components/UEditor/menu-bar.tsx
41965
- import React86, { useMemo as useMemo26, useRef as useRef37, useState as useState51 } from "react";
42757
+ import React86, { useMemo as useMemo26, useRef as useRef38, useState as useState51 } from "react";
41966
42758
  import { useEditorState as useEditorState3 } from "@tiptap/react";
41967
42759
  import {
41968
42760
  AlignCenter as AlignCenter4,
@@ -42586,7 +43378,7 @@ var MenuBar = ({
42586
43378
  editor,
42587
43379
  selector: ({ transactionNumber }) => transactionNumber
42588
43380
  });
42589
- const fileInputRef = useRef37(null);
43381
+ const fileInputRef = useRef38(null);
42590
43382
  const [showImageInput, setShowImageInput] = useState51(false);
42591
43383
  const [showLinkInput, setShowLinkInput] = useState51(false);
42592
43384
  const [isInsertMenuOpen, setIsInsertMenuOpen] = useState51(false);
@@ -42875,149 +43667,156 @@ var MenuBar = ({
42875
43667
  ] });
42876
43668
  };
42877
43669
 
42878
- // src/components/UEditor/table-formula-range-picker.ts
42879
- import { TextSelection as TextSelection4 } from "@tiptap/pm/state";
42880
- import { TableMap as TableMap5 } from "@tiptap/pm/tables";
42881
- function getCellText2(cellNode) {
42882
- return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
42883
- }
42884
- function findSelectionFormulaCell(view) {
42885
- const { $from } = view.state.selection;
42886
- for (let depth = $from.depth; depth > 0; depth -= 1) {
42887
- const node = $from.node(depth);
42888
- if (node.type.name !== "tableCell" && node.type.name !== "tableHeader") continue;
42889
- if (!getCellText2(node).startsWith("=")) return null;
42890
- const cellPos = $from.before(depth);
42891
- const cellDom = view.nodeDOM(cellPos);
42892
- const tableDepth = depth - 2;
42893
- const tableNode = tableDepth > 0 ? $from.node(tableDepth) : null;
42894
- if (!tableNode || tableNode.type.name !== "table") return null;
42895
- return {
42896
- cellDom: cellDom instanceof HTMLTableCellElement ? cellDom : null,
42897
- tablePos: $from.before(tableDepth)
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";
43674
+ import { jsx as jsx99, jsxs as jsxs82 } from "react/jsx-runtime";
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();
42898
43687
  };
42899
- }
42900
- return null;
42901
- }
42902
- function findTableForPos(view, pos) {
42903
- const $pos = view.state.doc.resolve(pos);
42904
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
42905
- const node = $pos.node(depth);
42906
- if (node.type.name === "table") {
42907
- return {
42908
- node,
42909
- pos: $pos.before(depth),
42910
- start: $pos.start(depth)
42911
- };
42912
- }
42913
- }
42914
- return null;
42915
- }
42916
- function getCellRelativePosFromDomPos2(map, tableStart, domPos) {
42917
- const relativeDomPos = domPos - tableStart;
42918
- const seen = /* @__PURE__ */ new Set();
42919
- for (const relativeCellPos of map.map) {
42920
- if (seen.has(relativeCellPos)) continue;
42921
- seen.add(relativeCellPos);
42922
- if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
42923
- return relativeCellPos;
42924
- }
42925
- }
42926
- return null;
42927
- }
42928
- function getPickedCellLabel(view, target, tablePos) {
42929
- const element = resolveEventElement(target);
42930
- const cell = element?.closest?.("th,td");
42931
- if (!(cell instanceof HTMLTableCellElement)) return null;
42932
- const domPos = view.posAtDOM(cell, 0);
42933
- const tableInfo = findTableForPos(view, domPos);
42934
- if (!tableInfo || tableInfo.pos !== tablePos) return null;
42935
- const map = TableMap5.get(tableInfo.node);
42936
- const relativeCellPos = getCellRelativePosFromDomPos2(map, tableInfo.start, domPos);
42937
- if (relativeCellPos == null) return null;
42938
- const rect = map.findCell(relativeCellPos);
42939
- return {
42940
- cell,
42941
- label: `${indexToColumnName(rect.left)}${rect.top + 1}`
42942
- };
42943
- }
42944
- function normalizeFormulaRangeLabel(fromLabel, toLabel) {
42945
- return fromLabel === toLabel ? fromLabel : `${fromLabel}:${toLabel}`;
42946
- }
42947
- function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42948
- if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell) return pickState;
42949
- if (nextLabel === pickState.currentLabel) {
42950
- return {
42951
- ...pickState,
42952
- currentCell
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();
42953
43693
  };
42954
- }
42955
- let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42956
- const nextTo = pickState.insertedFrom + nextLabel.length;
42957
- tr = tr.setSelection(TextSelection4.create(tr.doc, nextTo));
42958
- view.dispatch(tr);
42959
- return {
42960
- ...pickState,
42961
- insertedTo: nextTo,
42962
- currentLabel: nextLabel,
42963
- currentCell
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
+ });
42964
43708
  };
42965
- }
42966
- function beginFormulaRangePick(view, event) {
42967
- if (event.button !== 0) return null;
42968
- const formulaCell = findSelectionFormulaCell(view);
42969
- if (!formulaCell) return null;
42970
- const picked = getPickedCellLabel(view, event.target, formulaCell.tablePos);
42971
- if (!picked || picked.cell === formulaCell.cellDom) return null;
42972
- const { from, to } = view.state.selection;
42973
- let tr = view.state.tr.insertText(picked.label, from, to);
42974
- tr = tr.setSelection(TextSelection4.create(tr.doc, from + picked.label.length));
42975
- view.dispatch(tr);
42976
- view.focus();
42977
- event.preventDefault();
42978
- event.stopPropagation();
42979
- return {
42980
- anchorLabel: picked.label,
42981
- anchorCell: picked.cell,
42982
- tablePos: formulaCell.tablePos,
42983
- insertedFrom: from,
42984
- insertedTo: from + picked.label.length,
42985
- currentLabel: picked.label,
42986
- currentCell: picked.cell
43709
+ const applyDraft = () => {
43710
+ const normalized = normalizeFormulaInput(draft);
43711
+ if (normalized) {
43712
+ setSelectedTableCellFormula(editor, normalized);
43713
+ } else {
43714
+ clearSelectedTableCellFormula(editor);
43715
+ }
43716
+ setDraftState(null);
42987
43717
  };
42988
- }
42989
- function updateFormulaRangePick(view, pickState, event) {
42990
- const picked = getPickedCellLabel(view, event.target, pickState.tablePos);
42991
- if (!picked) return pickState;
42992
- event.preventDefault();
42993
- event.stopPropagation();
42994
- return replacePickedLabel(
42995
- view,
42996
- pickState,
42997
- normalizeFormulaRangeLabel(pickState.anchorLabel, picked.label),
42998
- picked.cell
42999
- );
43000
- }
43001
- function getFormulaRangePickHighlight(container, pickState) {
43002
- if (!container.contains(pickState.anchorCell) || !container.contains(pickState.currentCell)) return null;
43003
- const containerRect = container.getBoundingClientRect();
43004
- const anchorRect = pickState.anchorCell.getBoundingClientRect();
43005
- const currentRect = pickState.currentCell.getBoundingClientRect();
43006
- const left = Math.min(anchorRect.left, currentRect.left) - containerRect.left + container.scrollLeft;
43007
- const top = Math.min(anchorRect.top, currentRect.top) - containerRect.top + container.scrollTop;
43008
- const right = Math.max(anchorRect.right, currentRect.right) - containerRect.left + container.scrollLeft;
43009
- const bottom = Math.max(anchorRect.bottom, currentRect.bottom) - containerRect.top + container.scrollTop;
43010
- return {
43011
- left,
43012
- top,
43013
- width: Math.max(0, right - left),
43014
- height: Math.max(0, bottom - top)
43718
+ const cancelDraft = () => {
43719
+ setDraftState(null);
43720
+ window.setTimeout(() => {
43721
+ if (!editor.isDestroyed) editor.commands.focus();
43722
+ }, 0);
43015
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
+ );
43016
43815
  }
43017
43816
 
43018
43817
  // src/components/UEditor/UEditor.tsx
43019
- import { jsx as jsx99, jsxs as jsxs82 } from "react/jsx-runtime";
43020
- var UEditor = React87.forwardRef(({
43818
+ import { jsx as jsx100, jsxs as jsxs83 } from "react/jsx-runtime";
43819
+ var UEditor = React88.forwardRef(({
43021
43820
  content = "",
43022
43821
  onChange,
43023
43822
  onHtmlChange,
@@ -43035,6 +43834,7 @@ var UEditor = React87.forwardRef(({
43035
43834
  autofocus = false,
43036
43835
  showToolbar = true,
43037
43836
  showBubbleMenu = true,
43837
+ showFloatingMenu = false,
43038
43838
  showCharacterCount = true,
43039
43839
  showFooter = true,
43040
43840
  maxCharacters,
@@ -43058,13 +43858,14 @@ var UEditor = React87.forwardRef(({
43058
43858
  }, ref) => {
43059
43859
  const t = useSmartTranslations("UEditor");
43060
43860
  const effectivePlaceholder = placeholder ?? t("placeholder");
43061
- const inFlightPrepareRef = useRef38(null);
43062
- const lastAppliedContentRef = useRef38(content ?? "");
43063
- const scheduledFormulaRecalculateRef = useRef38(false);
43064
- const formulaRangePickRef = useRef38(null);
43065
- const formulaRangeSurfaceRef = useRef38(null);
43066
- const [formulaRangeHighlight, setFormulaRangeHighlight] = React87.useState(null);
43067
- 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) => {
43068
43869
  if (editor2.isDestroyed || scheduledFormulaRecalculateRef.current) return;
43069
43870
  if (!options?.force && isEditingTableFormulaText(editor2)) return;
43070
43871
  scheduledFormulaRecalculateRef.current = true;
@@ -43104,7 +43905,7 @@ var UEditor = React87.forwardRef(({
43104
43905
  ],
43105
43906
  [effectivePlaceholder, t, maxCharacters, uploadImage, resolvedUploadFile, imageInsertMode, maxImageFileSize, allowedImageMimeTypes, fallbackToDataUrl, editable, fetchMetadata, extraExtensions]
43106
43907
  );
43107
- const syncFormulaRangeHighlight = React87.useCallback((pickState) => {
43908
+ const syncFormulaRangeHighlight = React88.useCallback((pickState) => {
43108
43909
  const container = formulaRangeSurfaceRef.current;
43109
43910
  setFormulaRangeHighlight(container && pickState ? getFormulaRangePickHighlight(container, pickState) : null);
43110
43911
  }, []);
@@ -43149,6 +43950,29 @@ var UEditor = React87.forwardRef(({
43149
43950
  },
43150
43951
  keydown: (_view, event) => {
43151
43952
  if (!(event instanceof KeyboardEvent)) return false;
43953
+ const formulaContext = getFormulaEditingTableContext(_view);
43954
+ if (formulaContext && event.key === "Enter") {
43955
+ event.preventDefault();
43956
+ event.stopPropagation();
43957
+ const activeEditor2 = editorInstanceRef.current;
43958
+ if (activeEditor2 && !isDraftTableFormula(formulaContext.formula)) {
43959
+ recalculateActiveTableFormulas(activeEditor2);
43960
+ }
43961
+ return true;
43962
+ }
43963
+ if (formulaContext && event.key === "Escape") {
43964
+ event.preventDefault();
43965
+ event.stopPropagation();
43966
+ cancelFormulaEditing(_view);
43967
+ return true;
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
+ }
43152
43976
  if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
43153
43977
  event.stopPropagation();
43154
43978
  }
@@ -43159,7 +43983,7 @@ var UEditor = React87.forwardRef(({
43159
43983
  if (event.button !== 0) return false;
43160
43984
  const target = resolveEventElement(event.target);
43161
43985
  const anchor = target?.closest?.("a[href]");
43162
- const href = anchor?.getAttribute("href") ?? "";
43986
+ const href = sanitizeUEditorUrl(anchor?.getAttribute("href") ?? "", "link");
43163
43987
  if (!href) return false;
43164
43988
  if (editable) return false;
43165
43989
  if (!view.state.selection.empty) return false;
@@ -43185,16 +44009,28 @@ var UEditor = React87.forwardRef(({
43185
44009
  onSelectionUpdate: ({ editor: editor2 }) => {
43186
44010
  scheduleFormulaRecalculate(editor2);
43187
44011
  },
43188
- 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;
43189
44015
  scheduleFormulaRecalculate(editor2, { force: true });
43190
44016
  }
43191
44017
  });
44018
+ useEffect41(() => {
44019
+ editorInstanceRef.current = editor;
44020
+ return () => {
44021
+ if (editorInstanceRef.current === editor) editorInstanceRef.current = null;
44022
+ };
44023
+ }, [editor]);
43192
44024
  const {
43193
44025
  editorContentRef,
43194
44026
  tableColumnGuideRef,
43195
44027
  tableRowGuideRef,
43196
44028
  activeTableCellHighlightRef
43197
44029
  } = useUEditorTableInteractions(editor, editable);
44030
+ const formulaCoordinateOverlayRef = useFormulaCoordinateOverlay(editor, editorContentRef, {
44031
+ apply: t("tableMenu.apply"),
44032
+ cancel: t("imageInput.cancelBtn")
44033
+ });
43198
44034
  useImperativeHandle4(
43199
44035
  ref,
43200
44036
  () => ({
@@ -43220,7 +44056,7 @@ var UEditor = React87.forwardRef(({
43220
44056
  }),
43221
44057
  [content, editor, uploadImageForSave, uploadFileForSave, uploadImageConcurrency]
43222
44058
  );
43223
- useEffect39(() => {
44059
+ useEffect41(() => {
43224
44060
  if (!editor) return;
43225
44061
  queueMicrotask(() => {
43226
44062
  if (!editor.isDestroyed) {
@@ -43228,7 +44064,7 @@ var UEditor = React87.forwardRef(({
43228
44064
  }
43229
44065
  });
43230
44066
  }, [editor]);
43231
- useEffect39(() => {
44067
+ useEffect41(() => {
43232
44068
  if (!editor) return;
43233
44069
  const nextContent = content ?? "";
43234
44070
  if (lastAppliedContentRef.current === nextContent) return;
@@ -43247,7 +44083,7 @@ var UEditor = React87.forwardRef(({
43247
44083
  }
43248
44084
  }, [content, editor]);
43249
44085
  if (!editor) {
43250
- return /* @__PURE__ */ jsx99(
44086
+ return /* @__PURE__ */ jsx100(
43251
44087
  "div",
43252
44088
  {
43253
44089
  className: cn("w-full border bg-background flex items-center justify-center text-muted-foreground", className),
@@ -43256,7 +44092,7 @@ var UEditor = React87.forwardRef(({
43256
44092
  }
43257
44093
  );
43258
44094
  }
43259
- return /* @__PURE__ */ jsxs82(
44095
+ return /* @__PURE__ */ jsxs83(
43260
44096
  "div",
43261
44097
  {
43262
44098
  className: cn(
@@ -43272,7 +44108,7 @@ var UEditor = React87.forwardRef(({
43272
44108
  className
43273
44109
  ),
43274
44110
  children: [
43275
- editable && showMenuBar && /* @__PURE__ */ jsx99(
44111
+ editable && showMenuBar && /* @__PURE__ */ jsx100(
43276
44112
  MenuBar,
43277
44113
  {
43278
44114
  editor,
@@ -43287,7 +44123,7 @@ var UEditor = React87.forwardRef(({
43287
44123
  onPreview
43288
44124
  }
43289
44125
  ),
43290
- editable && showToolbar && /* @__PURE__ */ jsx99(
44126
+ editable && showToolbar && /* @__PURE__ */ jsx100(
43291
44127
  EditorToolbar,
43292
44128
  {
43293
44129
  editor,
@@ -43302,7 +44138,8 @@ var UEditor = React87.forwardRef(({
43302
44138
  letterSpacings
43303
44139
  }
43304
44140
  ),
43305
- editable && showBubbleMenu && /* @__PURE__ */ jsx99(
44141
+ editable && /* @__PURE__ */ jsx100(TableFormulaBar, { editor }),
44142
+ editable && showBubbleMenu && /* @__PURE__ */ jsx100(
43306
44143
  CustomBubbleMenu,
43307
44144
  {
43308
44145
  editor,
@@ -43310,7 +44147,8 @@ var UEditor = React87.forwardRef(({
43310
44147
  lineHeights
43311
44148
  }
43312
44149
  ),
43313
- /* @__PURE__ */ jsxs82(
44150
+ editable && showFloatingMenu && /* @__PURE__ */ jsx100(CustomFloatingMenu, { editor }),
44151
+ /* @__PURE__ */ jsxs83(
43314
44152
  "div",
43315
44153
  {
43316
44154
  ref: (node) => {
@@ -43323,7 +44161,7 @@ var UEditor = React87.forwardRef(({
43323
44161
  maxHeight
43324
44162
  },
43325
44163
  children: [
43326
- /* @__PURE__ */ jsx99(
44164
+ /* @__PURE__ */ jsx100(
43327
44165
  "span",
43328
44166
  {
43329
44167
  ref: tableColumnGuideRef,
@@ -43331,7 +44169,7 @@ var UEditor = React87.forwardRef(({
43331
44169
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43332
44170
  }
43333
44171
  ),
43334
- /* @__PURE__ */ jsx99(
44172
+ /* @__PURE__ */ jsx100(
43335
44173
  "span",
43336
44174
  {
43337
44175
  ref: tableRowGuideRef,
@@ -43339,7 +44177,7 @@ var UEditor = React87.forwardRef(({
43339
44177
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43340
44178
  }
43341
44179
  ),
43342
- /* @__PURE__ */ jsx99(
44180
+ /* @__PURE__ */ jsx100(
43343
44181
  "span",
43344
44182
  {
43345
44183
  ref: activeTableCellHighlightRef,
@@ -43348,7 +44186,15 @@ var UEditor = React87.forwardRef(({
43348
44186
  className: "pointer-events-none hidden absolute z-20 rounded-[2px] border-2 border-primary bg-primary/10"
43349
44187
  }
43350
44188
  ),
43351
- formulaRangeHighlight && /* @__PURE__ */ jsx99(
44189
+ /* @__PURE__ */ jsx100(
44190
+ "div",
44191
+ {
44192
+ ref: formulaCoordinateOverlayRef,
44193
+ "data-ueditor-formula-coordinate-overlay": "",
44194
+ className: "pointer-events-none absolute inset-0 z-[21] hidden overflow-visible"
44195
+ }
44196
+ ),
44197
+ formulaRangeHighlight && /* @__PURE__ */ jsx100(
43352
44198
  "span",
43353
44199
  {
43354
44200
  "aria-hidden": "true",
@@ -43362,8 +44208,8 @@ var UEditor = React87.forwardRef(({
43362
44208
  }
43363
44209
  }
43364
44210
  ),
43365
- editable && /* @__PURE__ */ jsx99(TableControls, { editor, containerRef: editorContentRef }),
43366
- /* @__PURE__ */ jsx99(
44211
+ editable && /* @__PURE__ */ jsx100(TableControls, { editor, containerRef: editorContentRef }),
44212
+ /* @__PURE__ */ jsx100(
43367
44213
  EditorContent,
43368
44214
  {
43369
44215
  editor,
@@ -43373,7 +44219,7 @@ var UEditor = React87.forwardRef(({
43373
44219
  ]
43374
44220
  }
43375
44221
  ),
43376
- showFooter && showCharacterCount && /* @__PURE__ */ jsx99(CharacterCountDisplay, { editor, maxCharacters })
44222
+ showFooter && showCharacterCount && /* @__PURE__ */ jsx100(CharacterCountDisplay, { editor, maxCharacters })
43377
44223
  ]
43378
44224
  }
43379
44225
  );