@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.cjs CHANGED
@@ -1086,6 +1086,11 @@ var en_default = {
1086
1086
  borderColor: "Border Color",
1087
1087
  clearBorder: "Clear Border",
1088
1088
  formula: "Formula",
1089
+ formulaBar: "Formula bar",
1090
+ formulaError: "Formula error",
1091
+ editFormula: "Edit formula",
1092
+ clearCell: "Clear cell",
1093
+ convertToValue: "Convert to value",
1089
1094
  numberFormat: "Number Format",
1090
1095
  formatText: "Text",
1091
1096
  formatDate: "Date",
@@ -1508,6 +1513,11 @@ var vi_default = {
1508
1513
  borderColor: "M\xE0u vi\u1EC1n",
1509
1514
  clearBorder: "X\xF3a vi\u1EC1n",
1510
1515
  formula: "C\xF4ng th\u1EE9c",
1516
+ formulaBar: "Thanh c\xF4ng th\u1EE9c",
1517
+ formulaError: "L\u1ED7i c\xF4ng th\u1EE9c",
1518
+ editFormula: "S\u1EEDa c\xF4ng th\u1EE9c",
1519
+ clearCell: "X\xF3a n\u1ED9i dung \xF4",
1520
+ convertToValue: "Chuy\u1EC3n th\xE0nh gi\xE1 tr\u1ECB",
1511
1521
  numberFormat: "\u0110\u1ECBnh d\u1EA1ng s\u1ED1",
1512
1522
  formatText: "Ch\u1EEF",
1513
1523
  formatDate: "Ng\xE0y",
@@ -1930,6 +1940,11 @@ var ko_default = {
1930
1940
  borderColor: "\uD14C\uB450\uB9AC \uC0C9\uC0C1",
1931
1941
  clearBorder: "\uD14C\uB450\uB9AC \uC9C0\uC6B0\uAE30",
1932
1942
  formula: "\uC218\uC2DD",
1943
+ formulaBar: "\uC218\uC2DD \uC785\uB825\uC904",
1944
+ formulaError: "\uC218\uC2DD \uC624\uB958",
1945
+ editFormula: "\uC218\uC2DD \uD3B8\uC9D1",
1946
+ clearCell: "\uC140 \uB0B4\uC6A9 \uC0AD\uC81C",
1947
+ convertToValue: "\uAC12\uC73C\uB85C \uBCC0\uD658",
1933
1948
  numberFormat: "\uD45C\uC2DC \uD615\uC2DD",
1934
1949
  formatText: "\uD14D\uC2A4\uD2B8",
1935
1950
  formatDate: "\uB0A0\uC9DC",
@@ -2352,6 +2367,11 @@ var ja_default = {
2352
2367
  borderColor: "\u67A0\u7DDA\u306E\u8272",
2353
2368
  clearBorder: "\u67A0\u7DDA\u3092\u30AF\u30EA\u30A2",
2354
2369
  formula: "\u6570\u5F0F",
2370
+ formulaBar: "\u6570\u5F0F\u30D0\u30FC",
2371
+ formulaError: "\u6570\u5F0F\u30A8\u30E9\u30FC",
2372
+ editFormula: "\u6570\u5F0F\u3092\u7DE8\u96C6",
2373
+ clearCell: "\u30BB\u30EB\u3092\u30AF\u30EA\u30A2",
2374
+ convertToValue: "\u5024\u306B\u5909\u63DB",
2355
2375
  numberFormat: "\u8868\u793A\u5F62\u5F0F",
2356
2376
  formatText: "\u30C6\u30AD\u30B9\u30C8",
2357
2377
  formatDate: "\u65E5\u4ED8",
@@ -27548,8 +27568,8 @@ function useLocale2() {
27548
27568
  }
27549
27569
 
27550
27570
  // src/components/UEditor/UEditor.tsx
27551
- var import_react79 = __toESM(require("react"), 1);
27552
- var import_react80 = require("@tiptap/react");
27571
+ var import_react82 = __toESM(require("react"), 1);
27572
+ var import_react83 = require("@tiptap/react");
27553
27573
 
27554
27574
  // src/components/UEditor/extensions.ts
27555
27575
  var import_core16 = require("@tiptap/core");
@@ -27711,6 +27731,54 @@ var import_react49 = require("@tiptap/react");
27711
27731
  var import_react47 = __toESM(require("react"), 1);
27712
27732
  var import_react48 = require("@tiptap/react");
27713
27733
  var import_lucide_react43 = require("lucide-react");
27734
+
27735
+ // src/components/UEditor/url-safety.ts
27736
+ var LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
27737
+ var IMAGE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
27738
+ var FILE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "blob:"]);
27739
+ function normalizeUrlInput(raw) {
27740
+ return raw.trim().replace(/[\u0000-\u001F\u007F\s]+/g, "");
27741
+ }
27742
+ function isProtocolRelativeUrl(value) {
27743
+ return value.startsWith("//");
27744
+ }
27745
+ function isRelativeUrl(value) {
27746
+ return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#");
27747
+ }
27748
+ function isDataImageUrl(value) {
27749
+ return /^data:image\/(?:png|jpe?g|gif|webp|svg\+xml|bmp|x-icon|avif);base64,/i.test(value);
27750
+ }
27751
+ function isDataFileUrl(value) {
27752
+ 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);
27753
+ }
27754
+ function isSafeUEditorUrl(raw, kind) {
27755
+ const value = normalizeUrlInput(raw);
27756
+ if (!value) return false;
27757
+ if (kind === "image" && isDataImageUrl(value)) return true;
27758
+ if (kind === "file" && isDataFileUrl(value)) return true;
27759
+ if (isProtocolRelativeUrl(value)) return false;
27760
+ if (isRelativeUrl(value)) return true;
27761
+ try {
27762
+ const parsed = new URL(value);
27763
+ if (kind === "image") return IMAGE_PROTOCOLS.has(parsed.protocol);
27764
+ if (kind === "file") return FILE_PROTOCOLS.has(parsed.protocol);
27765
+ return LINK_PROTOCOLS.has(parsed.protocol);
27766
+ } catch {
27767
+ return false;
27768
+ }
27769
+ }
27770
+ function sanitizeUEditorUrl(raw, kind) {
27771
+ const value = raw.trim();
27772
+ if (!value) return "";
27773
+ if (isSafeUEditorUrl(value, kind)) return normalizeUrlInput(value);
27774
+ if (kind === "link" && !isProtocolRelativeUrl(value) && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) {
27775
+ const withProtocol = `https://${value}`;
27776
+ return isSafeUEditorUrl(withProtocol, kind) ? withProtocol : "";
27777
+ }
27778
+ return "";
27779
+ }
27780
+
27781
+ // src/components/UEditor/BookmarkView.tsx
27714
27782
  var import_jsx_runtime80 = require("react/jsx-runtime");
27715
27783
  var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27716
27784
  const t = useSmartTranslations("UEditor");
@@ -27718,17 +27786,25 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27718
27786
  const [loading2, setLoading] = (0, import_react47.useState)(false);
27719
27787
  const [error, setError] = (0, import_react47.useState)(false);
27720
27788
  const [retryToken, setRetryToken] = (0, import_react47.useState)(0);
27721
- const fetchedRetryTokenRef = import_react47.default.useRef(-1);
27789
+ const lastFetchKeyRef = import_react47.default.useRef("");
27722
27790
  const fetchRequestIdRef = import_react47.default.useRef(0);
27791
+ const safeUrl = sanitizeUEditorUrl(String(url ?? ""), "link");
27792
+ const safeImage = sanitizeUEditorUrl(String(image ?? ""), "image");
27793
+ const clickToOpenNewTabMessage = t("bookmark.clickToOpenNewTab");
27794
+ const clickToOpenLinkMessage = t("bookmark.clickToOpenLink");
27795
+ const noDescriptionMessage = t("bookmark.noDescription");
27723
27796
  (0, import_react47.useEffect)(() => {
27724
27797
  if (title && retryToken === 0) return;
27725
- if (fetchedRetryTokenRef.current === retryToken) return;
27726
- fetchedRetryTokenRef.current = retryToken;
27798
+ if (!safeUrl) return;
27799
+ const fetchKey = `${safeUrl}\0${retryToken}`;
27800
+ if (lastFetchKeyRef.current === fetchKey) return;
27801
+ lastFetchKeyRef.current = fetchKey;
27727
27802
  const requestId = fetchRequestIdRef.current + 1;
27728
27803
  fetchRequestIdRef.current = requestId;
27804
+ let active = true;
27729
27805
  const safeUpdateAttributes = (attrs) => {
27730
27806
  Promise.resolve().then(() => {
27731
- if (fetchRequestIdRef.current === requestId && editor && !editor.isDestroyed) {
27807
+ if (active && fetchRequestIdRef.current === requestId && editor && !editor.isDestroyed) {
27732
27808
  updateAttributes(attrs);
27733
27809
  }
27734
27810
  });
@@ -27737,43 +27813,75 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27737
27813
  const fetchMetadata = extension?.options?.fetchMetadata;
27738
27814
  if (!fetchMetadata) {
27739
27815
  try {
27740
- const hostname = new URL(url).hostname;
27816
+ const hostname = new URL(safeUrl).hostname;
27741
27817
  safeUpdateAttributes({
27742
- title: hostname || url,
27743
- description: t("bookmark.clickToOpenNewTab"),
27818
+ title: hostname || safeUrl,
27819
+ description: clickToOpenNewTabMessage,
27744
27820
  publisher: hostname
27745
27821
  });
27746
27822
  } catch {
27747
27823
  safeUpdateAttributes({
27748
- title: url,
27749
- description: t("bookmark.clickToOpenLink"),
27824
+ title: safeUrl,
27825
+ description: clickToOpenLinkMessage,
27750
27826
  publisher: ""
27751
27827
  });
27752
27828
  }
27753
- return;
27829
+ return () => {
27830
+ active = false;
27831
+ };
27754
27832
  }
27755
- setLoading(true);
27756
- setError(false);
27757
- fetchMetadata(url).then((meta) => {
27758
- if (fetchRequestIdRef.current !== requestId) return;
27833
+ queueMicrotask(() => {
27834
+ if (!active) return;
27835
+ setLoading(true);
27836
+ setError(false);
27837
+ });
27838
+ fetchMetadata(safeUrl).then((meta) => {
27839
+ if (!active || fetchRequestIdRef.current !== requestId) return;
27840
+ const hostname = (() => {
27841
+ try {
27842
+ return new URL(safeUrl).hostname;
27843
+ } catch {
27844
+ return "";
27845
+ }
27846
+ })();
27759
27847
  safeUpdateAttributes({
27760
- title: meta.title || new URL(url).hostname || url,
27761
- description: meta.description || t("bookmark.noDescription"),
27762
- image: meta.image || "",
27763
- publisher: meta.publisher || new URL(url).hostname || ""
27848
+ title: meta.title || hostname || safeUrl,
27849
+ description: meta.description || noDescriptionMessage,
27850
+ image: meta.image ? sanitizeUEditorUrl(meta.image, "image") : "",
27851
+ publisher: meta.publisher || hostname
27764
27852
  });
27765
27853
  setLoading(false);
27766
27854
  }).catch(() => {
27767
- if (fetchRequestIdRef.current !== requestId) return;
27855
+ if (!active || fetchRequestIdRef.current !== requestId) return;
27768
27856
  setError(true);
27769
27857
  setLoading(false);
27770
27858
  });
27771
- return void 0;
27772
- }, [url, title, editor, updateAttributes, t, retryToken]);
27773
- const handleOpenLink = (e) => {
27774
- e.preventDefault();
27775
- e.stopPropagation();
27776
- window.open(url, "_blank", "noopener,noreferrer");
27859
+ return () => {
27860
+ active = false;
27861
+ };
27862
+ }, [
27863
+ safeUrl,
27864
+ title,
27865
+ editor,
27866
+ updateAttributes,
27867
+ retryToken,
27868
+ clickToOpenNewTabMessage,
27869
+ clickToOpenLinkMessage,
27870
+ noDescriptionMessage
27871
+ ]);
27872
+ const openLink = () => {
27873
+ if (safeUrl) window.open(safeUrl, "_blank", "noopener,noreferrer");
27874
+ };
27875
+ const handleOpenLink = (event) => {
27876
+ event.preventDefault();
27877
+ event.stopPropagation();
27878
+ openLink();
27879
+ };
27880
+ const handleKeyDown2 = (event) => {
27881
+ if (event.key !== "Enter") return;
27882
+ event.preventDefault();
27883
+ event.stopPropagation();
27884
+ openLink();
27777
27885
  };
27778
27886
  const handleRetryPreview = (event) => {
27779
27887
  event.preventDefault();
@@ -27782,7 +27890,7 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27782
27890
  };
27783
27891
  const displayPublisher = publisher || (() => {
27784
27892
  try {
27785
- return new URL(url).hostname;
27893
+ return safeUrl ? new URL(safeUrl).hostname : "";
27786
27894
  } catch {
27787
27895
  return "";
27788
27896
  }
@@ -27795,6 +27903,10 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27795
27903
  selected && "ring-2 ring-primary/45 border-primary/20 shadow-sm"
27796
27904
  ),
27797
27905
  onClick: handleOpenLink,
27906
+ onKeyDown: handleKeyDown2,
27907
+ role: "link",
27908
+ tabIndex: safeUrl ? 0 : -1,
27909
+ "aria-disabled": !safeUrl,
27798
27910
  children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "flex items-center justify-between p-4 gap-4 animate-pulse", children: [
27799
27911
  /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
27800
27912
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("div", { className: "h-4 w-1/3 bg-muted-foreground/20 rounded" }),
@@ -27809,11 +27921,11 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27809
27921
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react43.Globe, { className: "h-3 w-3 flex-shrink-0" }),
27810
27922
  displayPublisher
27811
27923
  ] }),
27812
- /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("h4", { className: "text-sm font-semibold text-foreground line-clamp-1 break-all", children: title || url }),
27924
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("h4", { className: "text-sm font-semibold text-foreground line-clamp-1 break-all", children: title || safeUrl || String(url ?? "") }),
27813
27925
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("p", { className: "text-xs text-muted-foreground line-clamp-2 leading-relaxed", children: description })
27814
27926
  ] }),
27815
27927
  /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("span", { className: "text-[11px] text-primary/80 font-mono mt-2 truncate flex items-center gap-1", children: [
27816
- url,
27928
+ safeUrl || String(url ?? ""),
27817
27929
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react43.ExternalLink, { className: "h-3 w-3 flex-shrink-0" })
27818
27930
  ] }),
27819
27931
  error && /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-2 text-xs text-destructive", children: [
@@ -27835,10 +27947,10 @@ var BookmarkView = ({ node, updateAttributes, selected, editor }) => {
27835
27947
  )
27836
27948
  ] })
27837
27949
  ] }),
27838
- image && /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("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__ */ (0, import_jsx_runtime80.jsx)(
27950
+ safeImage && /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("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__ */ (0, import_jsx_runtime80.jsx)(
27839
27951
  "img",
27840
27952
  {
27841
- src: image,
27953
+ src: safeImage,
27842
27954
  alt: title || "Link preview image",
27843
27955
  className: "absolute inset-0 w-full h-full object-cover transition-opacity duration-300",
27844
27956
  onError: (e) => {
@@ -27868,11 +27980,11 @@ var Bookmark = import_core2.Node.create({
27868
27980
  return {
27869
27981
  url: {
27870
27982
  default: "",
27871
- parseHTML: (element) => element.getAttribute("href") || element.getAttribute("data-url") || "",
27872
- renderHTML: (attributes) => ({
27873
- href: attributes.url,
27874
- "data-url": attributes.url
27875
- })
27983
+ parseHTML: (element) => sanitizeUEditorUrl(element.getAttribute("href") || element.getAttribute("data-url") || "", "link"),
27984
+ renderHTML: (attributes) => {
27985
+ const url = sanitizeUEditorUrl(String(attributes.url ?? ""), "link");
27986
+ return url ? { href: url, "data-url": url } : {};
27987
+ }
27876
27988
  },
27877
27989
  title: {
27878
27990
  default: "",
@@ -27890,10 +28002,11 @@ var Bookmark = import_core2.Node.create({
27890
28002
  },
27891
28003
  image: {
27892
28004
  default: "",
27893
- parseHTML: (element) => element.getAttribute("data-image") || "",
27894
- renderHTML: (attributes) => ({
27895
- "data-image": attributes.image
27896
- })
28005
+ parseHTML: (element) => sanitizeUEditorUrl(element.getAttribute("data-image") || "", "image"),
28006
+ renderHTML: (attributes) => {
28007
+ const image = sanitizeUEditorUrl(String(attributes.image ?? ""), "image");
28008
+ return image ? { "data-image": image } : {};
28009
+ }
27897
28010
  },
27898
28011
  publisher: {
27899
28012
  default: "",
@@ -27926,9 +28039,12 @@ var Bookmark = import_core2.Node.create({
27926
28039
  addCommands() {
27927
28040
  return {
27928
28041
  setBookmark: (attributes) => ({ commands }) => {
28042
+ const url = sanitizeUEditorUrl(attributes.url, "link");
28043
+ if (!url) return false;
28044
+ const image = attributes.image ? sanitizeUEditorUrl(attributes.image, "image") : "";
27929
28045
  return commands.insertContent({
27930
28046
  type: this.name,
27931
- attrs: attributes
28047
+ attrs: { ...attributes, url, image }
27932
28048
  });
27933
28049
  }
27934
28050
  };
@@ -27945,7 +28061,6 @@ var Bookmark = import_core2.Node.create({
27945
28061
  if (!text || !URL_REGEX.test(text)) return false;
27946
28062
  const { selection } = view.state;
27947
28063
  const parent = selection.$from.parent;
27948
- console.log("DEBUG handlePaste name:", parent.type.name, "size:", parent.content.size);
27949
28064
  const isEmptyParagraph = parent.type.name === "paragraph" && parent.content.size === 0;
27950
28065
  if (isEmptyParagraph) {
27951
28066
  event.preventDefault();
@@ -27973,15 +28088,15 @@ var import_react51 = require("@tiptap/react");
27973
28088
  var import_lucide_react44 = require("lucide-react");
27974
28089
  var import_jsx_runtime81 = require("react/jsx-runtime");
27975
28090
  function formatBytes(bytes, decimals = 1) {
27976
- if (!bytes || bytes <= 0) return "0 Bytes";
28091
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 Bytes";
27977
28092
  const k = 1024;
27978
28093
  const dm = decimals < 0 ? 0 : decimals;
27979
- const sizes = ["Bytes", "KB", "MB", "GB"];
27980
- const i = Math.floor(Math.log(bytes) / Math.log(k));
28094
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
28095
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
27981
28096
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
27982
28097
  }
27983
28098
  function getFileIcon2(type) {
27984
- const mime = (type || "").toLowerCase();
28099
+ const mime = String(type || "").toLowerCase();
27985
28100
  if (mime.includes("pdf")) return import_lucide_react44.FileText;
27986
28101
  if (mime.startsWith("image/")) return import_lucide_react44.Image;
27987
28102
  if (mime.startsWith("audio/")) return import_lucide_react44.Music;
@@ -28007,11 +28122,13 @@ function dataURLtoFile(dataurl, filename, mimeType) {
28007
28122
  }
28008
28123
  }
28009
28124
  return new File([bytes], filename, { type: mime });
28010
- } catch (e) {
28011
- console.error("Error converting data URI to file:", e);
28125
+ } catch {
28012
28126
  return null;
28013
28127
  }
28014
28128
  }
28129
+ function getErrorMessage(error) {
28130
+ return error instanceof Error && error.message ? error.message : "Upload failed";
28131
+ }
28015
28132
  var FileCardView = ({ node, selected, editor, updateAttributes }) => {
28016
28133
  const t = useSmartTranslations("UEditor");
28017
28134
  const { src, fileName, fileSize, fileType } = node.attrs;
@@ -28019,49 +28136,64 @@ var FileCardView = ({ node, selected, editor, updateAttributes }) => {
28019
28136
  const [uploadError, setUploadError] = (0, import_react50.useState)(null);
28020
28137
  const [retryToken, setRetryToken] = (0, import_react50.useState)(0);
28021
28138
  const uploadAttemptedRef = (0, import_react50.useRef)(false);
28139
+ const lastUploadSrcRef = (0, import_react50.useRef)("");
28140
+ const uploadRequestIdRef = (0, import_react50.useRef)(0);
28141
+ const safeSrc = sanitizeUEditorUrl(String(src ?? ""), "file");
28142
+ const extension = editor?.extensionManager?.extensions?.find((item) => item.name === "fileCard");
28143
+ const uploadFn = extension?.options?.upload;
28022
28144
  (0, import_react50.useEffect)(() => {
28023
- if (!src || !src.startsWith("data:")) {
28145
+ if (lastUploadSrcRef.current !== safeSrc) {
28146
+ lastUploadSrcRef.current = safeSrc;
28147
+ uploadAttemptedRef.current = false;
28148
+ }
28149
+ if (!safeSrc || !safeSrc.startsWith("data:") || !uploadFn) {
28024
28150
  return;
28025
28151
  }
28026
- const extension = editor?.extensionManager?.extensions?.find((e) => e.name === "fileCard");
28027
- const uploadFn = extension?.options?.upload;
28028
- if (!uploadFn || uploadAttemptedRef.current || !updateAttributes) {
28152
+ if (uploadAttemptedRef.current || !updateAttributes) {
28029
28153
  return;
28030
28154
  }
28031
28155
  uploadAttemptedRef.current = true;
28032
28156
  setUploadError(null);
28157
+ const requestId = uploadRequestIdRef.current + 1;
28158
+ uploadRequestIdRef.current = requestId;
28159
+ let active = true;
28033
28160
  const runUpload = async () => {
28034
28161
  try {
28035
- const file = dataURLtoFile(src, fileName || "file", fileType);
28162
+ const file = dataURLtoFile(safeSrc, fileName || "file", fileType);
28036
28163
  if (!file) {
28037
28164
  throw new Error("Failed to parse file data");
28038
28165
  }
28039
28166
  const resolvedUrl = await uploadFn(file);
28167
+ const nextSrc = sanitizeUEditorUrl(String(resolvedUrl ?? ""), "file");
28168
+ if (!nextSrc) throw new Error("Upload handler returned an unsafe URL");
28040
28169
  Promise.resolve().then(() => {
28041
- if (editor && !editor.isDestroyed) {
28042
- updateAttributes({ src: resolvedUrl });
28170
+ if (active && uploadRequestIdRef.current === requestId && editor && !editor.isDestroyed) {
28171
+ updateAttributes({ src: nextSrc });
28043
28172
  }
28044
28173
  });
28045
- } catch (err) {
28046
- console.error("File upload failed:", err);
28047
- setUploadError(err?.message || "Upload failed");
28174
+ } catch (error) {
28175
+ if (!active || uploadRequestIdRef.current !== requestId) return;
28176
+ setUploadError(getErrorMessage(error));
28048
28177
  uploadAttemptedRef.current = false;
28049
28178
  }
28050
28179
  };
28051
28180
  void runUpload();
28052
- }, [src, fileName, fileType, editor, updateAttributes, retryToken]);
28181
+ return () => {
28182
+ active = false;
28183
+ };
28184
+ }, [safeSrc, fileName, fileType, editor, updateAttributes, uploadFn, retryToken]);
28053
28185
  const handleDownload = (e) => {
28054
28186
  e.preventDefault();
28055
28187
  e.stopPropagation();
28056
- if (!src) return;
28188
+ if (!safeSrc) return;
28057
28189
  const link = document.createElement("a");
28058
- link.href = src;
28190
+ link.href = safeSrc;
28059
28191
  link.download = fileName || "download";
28060
28192
  document.body.appendChild(link);
28061
28193
  link.click();
28062
28194
  document.body.removeChild(link);
28063
28195
  };
28064
- const isUploading = (src || "").startsWith("data:");
28196
+ const hasInlineUpload = safeSrc.startsWith("data:") && Boolean(uploadFn);
28065
28197
  const handleRetryUpload = (event) => {
28066
28198
  event.preventDefault();
28067
28199
  event.stopPropagation();
@@ -28084,11 +28216,11 @@ var FileCardView = ({ node, selected, editor, updateAttributes }) => {
28084
28216
  /* @__PURE__ */ (0, import_jsx_runtime81.jsx)("div", { className: "text-sm font-semibold text-foreground truncate break-all", children: fileName || t("fileCard.unnamed") }),
28085
28217
  /* @__PURE__ */ (0, import_jsx_runtime81.jsxs)("div", { className: "text-xs text-muted-foreground flex items-center gap-2 mt-0.5", children: [
28086
28218
  fileSize ? /* @__PURE__ */ (0, import_jsx_runtime81.jsx)("span", { children: formatBytes(fileSize) }) : null,
28087
- isUploading && /* @__PURE__ */ (0, import_jsx_runtime81.jsx)("span", { className: cn(
28219
+ hasInlineUpload && /* @__PURE__ */ (0, import_jsx_runtime81.jsx)("span", { className: cn(
28088
28220
  "font-medium truncate max-w-50",
28089
28221
  uploadError ? "text-destructive" : "text-primary animate-pulse"
28090
28222
  ), children: uploadError ? `${t("fileCard.uploadFailed") || "T\u1EA3i l\xEAn th\u1EA5t b\u1EA1i"}: ${uploadError}` : t("fileCard.uploading") }),
28091
- isUploading && uploadError && /* @__PURE__ */ (0, import_jsx_runtime81.jsxs)(
28223
+ hasInlineUpload && uploadError && /* @__PURE__ */ (0, import_jsx_runtime81.jsxs)(
28092
28224
  "button",
28093
28225
  {
28094
28226
  type: "button",
@@ -28103,7 +28235,7 @@ var FileCardView = ({ node, selected, editor, updateAttributes }) => {
28103
28235
  ] })
28104
28236
  ] })
28105
28237
  ] }),
28106
- src && /* @__PURE__ */ (0, import_jsx_runtime81.jsx)(
28238
+ safeSrc && /* @__PURE__ */ (0, import_jsx_runtime81.jsx)(
28107
28239
  "button",
28108
28240
  {
28109
28241
  type: "button",
@@ -28132,11 +28264,11 @@ var FileCard = import_core3.Node.create({
28132
28264
  return {
28133
28265
  src: {
28134
28266
  default: "",
28135
- parseHTML: (element) => element.getAttribute("href") || element.getAttribute("data-src") || "",
28136
- renderHTML: (attributes) => ({
28137
- href: attributes.src,
28138
- "data-src": attributes.src
28139
- })
28267
+ parseHTML: (element) => sanitizeUEditorUrl(element.getAttribute("href") || element.getAttribute("data-src") || "", "file"),
28268
+ renderHTML: (attributes) => {
28269
+ const src = sanitizeUEditorUrl(String(attributes.src ?? ""), "file");
28270
+ return src ? { href: src, "data-src": src } : {};
28271
+ }
28140
28272
  },
28141
28273
  fileName: {
28142
28274
  default: "",
@@ -28148,13 +28280,15 @@ var FileCard = import_core3.Node.create({
28148
28280
  fileSize: {
28149
28281
  default: null,
28150
28282
  parseHTML: (element) => {
28151
- const val = element.getAttribute("data-file-size");
28152
- return val ? Number(val) : null;
28283
+ const rawValue = element.getAttribute("data-file-size");
28284
+ const value = rawValue ? Number(rawValue) : Number.NaN;
28285
+ return Number.isFinite(value) && value >= 0 ? value : null;
28153
28286
  },
28154
28287
  renderHTML: (attributes) => {
28155
- if (attributes.fileSize === null || attributes.fileSize === void 0) return {};
28288
+ const fileSize = Number(attributes.fileSize);
28289
+ if (!Number.isFinite(fileSize) || fileSize < 0) return {};
28156
28290
  return {
28157
- "data-file-size": attributes.fileSize
28291
+ "data-file-size": fileSize
28158
28292
  };
28159
28293
  }
28160
28294
  },
@@ -28189,9 +28323,16 @@ var FileCard = import_core3.Node.create({
28189
28323
  addCommands() {
28190
28324
  return {
28191
28325
  setFileCard: (attributes) => ({ commands }) => {
28326
+ const src = sanitizeUEditorUrl(attributes.src, "file");
28327
+ if (!src) return false;
28328
+ const fileSize = Number(attributes.fileSize);
28192
28329
  return commands.insertContent({
28193
28330
  type: this.name,
28194
- attrs: attributes
28331
+ attrs: {
28332
+ ...attributes,
28333
+ src,
28334
+ fileSize: Number.isFinite(fileSize) && fileSize >= 0 ? fileSize : null
28335
+ }
28195
28336
  });
28196
28337
  }
28197
28338
  };
@@ -28605,6 +28746,10 @@ function destroyTippyInstance(instance) {
28605
28746
  if (!instance || instance.state.isDestroyed) return;
28606
28747
  instance.destroy();
28607
28748
  }
28749
+ function setTippyReferenceClientRect(instance, clientRect) {
28750
+ if (!instance || instance.state.isDestroyed) return;
28751
+ instance.setProps({ getReferenceClientRect: clientRect });
28752
+ }
28608
28753
 
28609
28754
  // src/components/UEditor/slash-command.tsx
28610
28755
  var import_jsx_runtime84 = require("react/jsx-runtime");
@@ -28977,9 +29122,7 @@ var SlashCommand = import_core6.Extension.create({
28977
29122
  if (!props.clientRect) {
28978
29123
  return;
28979
29124
  }
28980
- popup?.[0]?.setProps({
28981
- getReferenceClientRect: props.clientRect
28982
- });
29125
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
28983
29126
  },
28984
29127
  onKeyDown(props) {
28985
29128
  if (!props || !props.event) return false;
@@ -29716,45 +29859,6 @@ function getClipboardTsvTableContent(dataTransfer) {
29716
29859
  );
29717
29860
  }
29718
29861
 
29719
- // src/components/UEditor/url-safety.ts
29720
- var LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
29721
- var IMAGE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
29722
- function normalizeUrlInput(raw) {
29723
- return raw.trim().replace(/[\u0000-\u001F\u007F\s]+/g, "");
29724
- }
29725
- function isProtocolRelativeUrl(value) {
29726
- return value.startsWith("//");
29727
- }
29728
- function isRelativeUrl(value) {
29729
- return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#");
29730
- }
29731
- function isDataImageUrl(value) {
29732
- return /^data:image\/(?:png|jpe?g|gif|webp|svg\+xml|bmp|x-icon|avif);base64,/i.test(value);
29733
- }
29734
- function isSafeUEditorUrl(raw, kind) {
29735
- const value = normalizeUrlInput(raw);
29736
- if (!value) return false;
29737
- if (kind === "image" && isDataImageUrl(value)) return true;
29738
- if (isRelativeUrl(value)) return true;
29739
- if (isProtocolRelativeUrl(value)) return false;
29740
- try {
29741
- const parsed = new URL(value);
29742
- return kind === "image" ? IMAGE_PROTOCOLS.has(parsed.protocol) : LINK_PROTOCOLS.has(parsed.protocol);
29743
- } catch {
29744
- return false;
29745
- }
29746
- }
29747
- function sanitizeUEditorUrl(raw, kind) {
29748
- const value = raw.trim();
29749
- if (!value) return "";
29750
- if (isSafeUEditorUrl(value, kind)) return normalizeUrlInput(value);
29751
- if (kind === "link" && !isProtocolRelativeUrl(value) && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) {
29752
- const withProtocol = `https://${value}`;
29753
- return isSafeUEditorUrl(withProtocol, kind) ? withProtocol : "";
29754
- }
29755
- return "";
29756
- }
29757
-
29758
29862
  // src/components/UEditor/clipboard-images.ts
29759
29863
  var DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE = 10 * 1024 * 1024;
29760
29864
  var DEFAULT_UEDITOR_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"];
@@ -29996,9 +30100,7 @@ var EmojiSuggestion = import_core8.Extension.create({
29996
30100
  if (!props.clientRect) {
29997
30101
  return;
29998
30102
  }
29999
- popup?.[0]?.setProps({
30000
- getReferenceClientRect: props.clientRect
30001
- });
30103
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
30002
30104
  },
30003
30105
  onKeyDown(props) {
30004
30106
  if (!props || !props.event) return false;
@@ -30067,11 +30169,17 @@ function isInTableCell(editor) {
30067
30169
  }
30068
30170
  function buildFormulaSuggestionItems({ query }) {
30069
30171
  const normalizedQuery = query.trim().toUpperCase();
30172
+ if (!isFormulaFunctionSuggestionQuery(normalizedQuery)) {
30173
+ return [];
30174
+ }
30070
30175
  if (!normalizedQuery) {
30071
30176
  return FORMULA_FUNCTIONS;
30072
30177
  }
30073
30178
  return FORMULA_FUNCTIONS.filter((item) => item.name.startsWith(normalizedQuery));
30074
30179
  }
30180
+ function isFormulaFunctionSuggestionQuery(query) {
30181
+ return /^[A-Z]*$/i.test(query.trim());
30182
+ }
30075
30183
  var FormulaSuggestionList = (0, import_react62.forwardRef)((props, ref) => {
30076
30184
  const t = useSmartTranslations("UEditor");
30077
30185
  const [selectedIndex, setSelectedIndex] = import_react62.default.useState(0);
@@ -30089,7 +30197,7 @@ var FormulaSuggestionList = (0, import_react62.forwardRef)((props, ref) => {
30089
30197
  setSelectedIndex((prev) => (prev + 1) % props.items.length);
30090
30198
  return true;
30091
30199
  }
30092
- if (event.key === "Enter" || event.key === "Tab") {
30200
+ if (event.key === "Tab") {
30093
30201
  const item = props.items[selectedIndex];
30094
30202
  if (item) {
30095
30203
  props.command(item);
@@ -30142,7 +30250,11 @@ var FormulaSuggestion = import_core9.Extension.create({
30142
30250
  editor: this.editor,
30143
30251
  char: "=",
30144
30252
  pluginKey: new import_state4.PluginKey("formulaSuggestion"),
30145
- allow: ({ editor }) => isInTableCell(editor),
30253
+ allow: ({ editor, range }) => {
30254
+ if (!isInTableCell(editor)) return false;
30255
+ const suggestionText = editor.state.doc.textBetween(range.from, range.to, "", "");
30256
+ return suggestionText.startsWith("=") && isFormulaFunctionSuggestionQuery(suggestionText.slice(1));
30257
+ },
30146
30258
  command: ({ editor, range, props }) => {
30147
30259
  insertFormulaFunction(editor, range, props);
30148
30260
  },
@@ -30175,9 +30287,7 @@ var FormulaSuggestion = import_core9.Extension.create({
30175
30287
  if (!props.clientRect) {
30176
30288
  return;
30177
30289
  }
30178
- popup?.[0]?.setProps({
30179
- getReferenceClientRect: props.clientRect
30180
- });
30290
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
30181
30291
  },
30182
30292
  onKeyDown(props) {
30183
30293
  const popupInstance = getFirstTippyInstance(popup);
@@ -31919,7 +32029,6 @@ function buildUEditorExtensions({
31919
32029
  import_extension_gapcursor.default,
31920
32030
  import_extension_link.default.configure({
31921
32031
  openOnClick: false,
31922
- protocols: ["http", "https", "mailto", "tel"],
31923
32032
  isAllowedUri: (url) => isSafeUEditorUrl(url ?? "", "link"),
31924
32033
  HTMLAttributes: {
31925
32034
  class: "text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer"
@@ -32971,7 +33080,7 @@ var EditorToolbar = ({
32971
33080
  const [showImageInput, setShowImageInput] = (0, import_react70.useState)(false);
32972
33081
  const [showLinkInput, setShowLinkInput] = (0, import_react70.useState)(false);
32973
33082
  const [isTableMenuOpen, setIsTableMenuOpen] = (0, import_react70.useState)(false);
32974
- const tableCommandAnchorPosRef = (0, import_react70.useRef)(null);
33083
+ const [preservedTableAnchorPos, setPreservedTableAnchorPos] = (0, import_react70.useState)(null);
32975
33084
  const fileInputRef = (0, import_react70.useRef)(null);
32976
33085
  const [isUploadingImage, setIsUploadingImage] = (0, import_react70.useState)(false);
32977
33086
  const [imageUploadError, setImageUploadError] = (0, import_react70.useState)(null);
@@ -32986,7 +33095,7 @@ var EditorToolbar = ({
32986
33095
  const tableAlignAttr = tableAttrs.tableAlign ?? tableAttrs.textAlign;
32987
33096
  const currentTableAlign = tableAlignAttr === "center" || tableAlignAttr === "right" ? tableAlignAttr : "left";
32988
33097
  const isTableSelected = tableInfo !== null;
32989
- const hasTableContext = isTableSelected || tableCommandAnchorPosRef.current !== null;
33098
+ const hasTableContext = isTableSelected || preservedTableAnchorPos !== null;
32990
33099
  const canMergeCells = hasTableContext && editor.can().mergeCells();
32991
33100
  const canSplitCell = hasTableContext && editor.can().splitCell();
32992
33101
  const currentCellVerticalAlign = normalizeStyleValue(editor.getAttributes("tableCell").verticalAlign || editor.getAttributes("tableHeader").verticalAlign) || "";
@@ -33011,7 +33120,7 @@ var EditorToolbar = ({
33011
33120
  const displayedFontFamilyValue = currentFontFamily || defaultFontFamilyValue;
33012
33121
  const displayedFontSizeLabel = currentFontSize ? currentFontSizeLabel : "13";
33013
33122
  const activeFontSize = currentFontSize || "13px";
33014
- const tableCommandAnchorPos = tableCommandAnchorPosRef.current ?? tableAnchorPos ?? void 0;
33123
+ const tableCommandAnchorPos = preservedTableAnchorPos ?? tableAnchorPos ?? void 0;
33015
33124
  const isMedium = variant === "medium";
33016
33125
  const isMediumFull = variant === "medium-full";
33017
33126
  const isFull = variant === "default" || variant === "full" || variant === "notion" || !variant;
@@ -33687,7 +33796,7 @@ var EditorToolbar = ({
33687
33796
  isOpen: isTableMenuOpen,
33688
33797
  onOpenChange: (open) => {
33689
33798
  setIsTableMenuOpen(open);
33690
- tableCommandAnchorPosRef.current = open ? getTableAnchorPos(editor) : null;
33799
+ setPreservedTableAnchorPos(open ? getTableAnchorPos(editor) : null);
33691
33800
  },
33692
33801
  trigger: /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(ToolbarButton, { onClick: () => {
33693
33802
  }, title: t("toolbar.table"), children: [
@@ -33704,6 +33813,7 @@ var EditorToolbar = ({
33704
33813
  onInsert: (rows, cols) => {
33705
33814
  editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
33706
33815
  setIsTableMenuOpen(false);
33816
+ setPreservedTableAnchorPos(null);
33707
33817
  }
33708
33818
  }
33709
33819
  ),
@@ -33894,6 +34004,7 @@ var import_react_dom8 = require("react-dom");
33894
34004
  var import_lucide_react54 = require("lucide-react");
33895
34005
 
33896
34006
  // src/components/UEditor/table-formula-commands.ts
34007
+ var import_state10 = require("@tiptap/pm/state");
33897
34008
  var import_tables5 = require("@tiptap/pm/tables");
33898
34009
 
33899
34010
  // src/components/UEditor/table-formula.ts
@@ -34071,51 +34182,98 @@ function buildTableFormulaDependencyGraph(cells) {
34071
34182
  return { dependencies, dependents, formulas };
34072
34183
  }
34073
34184
  function getTableFormulaCircularReferences(graph) {
34074
- const visiting = /* @__PURE__ */ new Set();
34075
- const visited = /* @__PURE__ */ new Set();
34076
34185
  const circular = /* @__PURE__ */ new Set();
34077
- const stack = [];
34078
- const visit = (label) => {
34079
- if (visiting.has(label)) {
34080
- const start = stack.indexOf(label);
34081
- for (const cycleLabel of start >= 0 ? stack.slice(start) : [label]) {
34082
- circular.add(cycleLabel);
34186
+ const visited = /* @__PURE__ */ new Set();
34187
+ const finishOrder = [];
34188
+ for (const startLabel of graph.formulas.keys()) {
34189
+ if (visited.has(startLabel)) continue;
34190
+ visited.add(startLabel);
34191
+ const stack = [
34192
+ {
34193
+ label: startLabel,
34194
+ references: Array.from(graph.dependencies.get(startLabel) ?? []).filter((ref) => graph.formulas.has(ref)),
34195
+ nextIndex: 0
34083
34196
  }
34084
- return;
34085
- }
34086
- if (visited.has(label)) return;
34087
- visiting.add(label);
34088
- stack.push(label);
34089
- for (const ref of graph.dependencies.get(label) ?? []) {
34090
- if (graph.formulas.has(ref)) {
34091
- visit(ref);
34197
+ ];
34198
+ while (stack.length > 0) {
34199
+ const frame = stack[stack.length - 1];
34200
+ if (!frame) break;
34201
+ const reference = frame.references[frame.nextIndex];
34202
+ if (reference) {
34203
+ frame.nextIndex += 1;
34204
+ if (!visited.has(reference)) {
34205
+ visited.add(reference);
34206
+ stack.push({
34207
+ label: reference,
34208
+ references: Array.from(graph.dependencies.get(reference) ?? []).filter((ref) => graph.formulas.has(ref)),
34209
+ nextIndex: 0
34210
+ });
34211
+ }
34212
+ continue;
34092
34213
  }
34214
+ stack.pop();
34215
+ finishOrder.push(frame.label);
34216
+ }
34217
+ }
34218
+ const assigned = /* @__PURE__ */ new Set();
34219
+ for (let index = finishOrder.length - 1; index >= 0; index -= 1) {
34220
+ const startLabel = finishOrder[index];
34221
+ if (!startLabel || assigned.has(startLabel)) continue;
34222
+ const component = [];
34223
+ const stack = [startLabel];
34224
+ assigned.add(startLabel);
34225
+ while (stack.length > 0) {
34226
+ const label = stack.pop();
34227
+ if (!label) continue;
34228
+ component.push(label);
34229
+ for (const dependent of graph.dependents.get(label) ?? []) {
34230
+ if (!graph.formulas.has(dependent) || assigned.has(dependent)) continue;
34231
+ assigned.add(dependent);
34232
+ stack.push(dependent);
34233
+ }
34234
+ }
34235
+ if (component.length > 1) {
34236
+ for (const label of component) circular.add(label);
34237
+ } else {
34238
+ const label = component[0];
34239
+ if (label && graph.dependencies.get(label)?.has(label)) circular.add(label);
34093
34240
  }
34094
- stack.pop();
34095
- visiting.delete(label);
34096
- visited.add(label);
34097
- };
34098
- for (const label of graph.formulas.keys()) {
34099
- visit(label);
34100
34241
  }
34101
34242
  return circular;
34102
34243
  }
34103
34244
  function getTableFormulaRecalculationOrder(graph) {
34104
34245
  const circular = getTableFormulaCircularReferences(graph);
34246
+ const visiting = /* @__PURE__ */ new Set();
34105
34247
  const visited = /* @__PURE__ */ new Set();
34106
34248
  const order = [];
34107
- const visit = (label) => {
34108
- if (visited.has(label) || circular.has(label)) return;
34109
- visited.add(label);
34110
- for (const ref of graph.dependencies.get(label) ?? []) {
34111
- if (graph.formulas.has(ref)) {
34112
- visit(ref);
34249
+ for (const startLabel of graph.formulas.keys()) {
34250
+ if (visited.has(startLabel) || circular.has(startLabel)) continue;
34251
+ const stack = [];
34252
+ const push = (label) => {
34253
+ visiting.add(label);
34254
+ stack.push({
34255
+ label,
34256
+ references: Array.from(graph.dependencies.get(label) ?? []).filter((ref) => graph.formulas.has(ref) && !circular.has(ref)),
34257
+ nextIndex: 0
34258
+ });
34259
+ };
34260
+ push(startLabel);
34261
+ while (stack.length > 0) {
34262
+ const frame = stack[stack.length - 1];
34263
+ if (!frame) break;
34264
+ const reference = frame.references[frame.nextIndex];
34265
+ if (reference) {
34266
+ frame.nextIndex += 1;
34267
+ if (!visited.has(reference) && !visiting.has(reference)) {
34268
+ push(reference);
34269
+ }
34270
+ continue;
34113
34271
  }
34272
+ stack.pop();
34273
+ visiting.delete(frame.label);
34274
+ visited.add(frame.label);
34275
+ order.push(frame.label);
34114
34276
  }
34115
- order.push(label);
34116
- };
34117
- for (const label of graph.formulas.keys()) {
34118
- visit(label);
34119
34277
  }
34120
34278
  return { order, circular };
34121
34279
  }
@@ -34174,9 +34332,11 @@ function tokenizeFormula(formula) {
34174
34332
  index += 1;
34175
34333
  continue;
34176
34334
  }
34177
- const numberMatch = formula.slice(index).match(/^\d+(?:\.\d+)?/);
34335
+ const numberMatch = formula.slice(index).match(/^(?:\d+(?:\.\d*)?|\.\d+)/);
34178
34336
  if (numberMatch?.[0]) {
34179
- tokens.push({ type: "number", value: Number.parseFloat(numberMatch[0]) });
34337
+ const value = Number.parseFloat(numberMatch[0]);
34338
+ if (!Number.isFinite(value)) return null;
34339
+ tokens.push({ type: "number", value });
34180
34340
  index += numberMatch[0].length;
34181
34341
  continue;
34182
34342
  }
@@ -34194,6 +34354,28 @@ function tokenizeFormula(formula) {
34194
34354
  }
34195
34355
  return tokens;
34196
34356
  }
34357
+ function toFiniteFormulaResult(value) {
34358
+ return Number.isFinite(value) ? { value, error: null } : { value: null, error: "invalid-formula" };
34359
+ }
34360
+ function parseTableCellNumericValue(value) {
34361
+ if (typeof value === "number") {
34362
+ return Number.isFinite(value) ? value : null;
34363
+ }
34364
+ let normalized = String(value ?? "").trim();
34365
+ if (!normalized || normalized.startsWith("#")) return null;
34366
+ const isPercent = normalized.endsWith("%");
34367
+ if (isPercent) normalized = normalized.slice(0, -1).trim();
34368
+ const currencyMatch = normalized.match(/^([+-]?)\$(.+)$/);
34369
+ if (currencyMatch) {
34370
+ normalized = `${currencyMatch[1] ?? ""}${currencyMatch[2] ?? ""}`.trim();
34371
+ }
34372
+ const groupedNumber = /^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?(?:[eE][+-]?\d+)?$/;
34373
+ const plainNumber = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
34374
+ if (!groupedNumber.test(normalized) && !plainNumber.test(normalized)) return null;
34375
+ const parsed = Number(normalized.replace(/,/g, ""));
34376
+ if (!Number.isFinite(parsed)) return null;
34377
+ return isPercent ? parsed / 100 : parsed;
34378
+ }
34197
34379
  var FormulaParser = class {
34198
34380
  constructor(tokens, getCellValue) {
34199
34381
  this.tokens = tokens;
@@ -34211,10 +34393,7 @@ var FormulaParser = class {
34211
34393
  this.index += 1;
34212
34394
  const right = this.parseTerm();
34213
34395
  if (right.error) return right;
34214
- left = {
34215
- value: operator.value === "+" ? left.value + right.value : left.value - right.value,
34216
- error: null
34217
- };
34396
+ left = toFiniteFormulaResult(operator.value === "+" ? left.value + right.value : left.value - right.value);
34218
34397
  }
34219
34398
  return left;
34220
34399
  }
@@ -34229,10 +34408,7 @@ var FormulaParser = class {
34229
34408
  if (operator.value === "/" && right.value === 0) {
34230
34409
  return { value: null, error: "division-by-zero" };
34231
34410
  }
34232
- left = {
34233
- value: operator.value === "*" ? left.value * right.value : left.value / right.value,
34234
- error: null
34235
- };
34411
+ left = toFiniteFormulaResult(operator.value === "*" ? left.value * right.value : left.value / right.value);
34236
34412
  }
34237
34413
  return left;
34238
34414
  }
@@ -34241,11 +34417,11 @@ var FormulaParser = class {
34241
34417
  if (!token) {
34242
34418
  return { value: null, error: "invalid-formula" };
34243
34419
  }
34244
- if (token.type === "operator" && token.value === "-") {
34420
+ if (token.type === "operator" && (token.value === "-" || token.value === "+")) {
34245
34421
  this.index += 1;
34246
34422
  const value = this.parseFactor();
34247
34423
  if (value.error) return value;
34248
- return { value: -value.value, error: null };
34424
+ return toFiniteFormulaResult(token.value === "-" ? -value.value : value.value);
34249
34425
  }
34250
34426
  if (token.type === "number") {
34251
34427
  this.index += 1;
@@ -34290,11 +34466,10 @@ var FormulaParser = class {
34290
34466
  if (cellValue2 != null) values.push(cellValue2);
34291
34467
  continue;
34292
34468
  }
34293
- const cellValue = this.readCellNumber(label);
34294
- if (cellValue.error) return cellValue;
34295
- values.push(cellValue.value);
34469
+ const cellValue = this.readOptionalCellNumber(label);
34470
+ if (cellValue != null) values.push(cellValue);
34296
34471
  }
34297
- } else if (name === "COUNT" && token.type === "cell") {
34472
+ } else if (token.type === "cell") {
34298
34473
  this.index += 1;
34299
34474
  const cellValue = this.readOptionalCellNumber(token.value);
34300
34475
  if (cellValue != null) values.push(cellValue);
@@ -34311,28 +34486,24 @@ var FormulaParser = class {
34311
34486
  }
34312
34487
  return { value: null, error: "invalid-formula" };
34313
34488
  }
34314
- if (values.length === 0 && name !== "COUNT") {
34315
- return { value: null, error: "invalid-formula" };
34489
+ if (name === "SUM") return toFiniteFormulaResult(values.reduce((sum, value) => sum + value, 0));
34490
+ if (name === "AVG") {
34491
+ return values.length > 0 ? toFiniteFormulaResult(values.reduce((sum, value) => sum + value, 0) / values.length) : { value: null, error: "division-by-zero" };
34316
34492
  }
34317
- if (name === "SUM") return { value: values.reduce((sum, value) => sum + value, 0), error: null };
34318
- if (name === "AVG") return { value: values.reduce((sum, value) => sum + value, 0) / values.length, error: null };
34319
- if (name === "MIN") return { value: Math.min(...values), error: null };
34320
- if (name === "MAX") return { value: Math.max(...values), error: null };
34493
+ if (name === "MIN") return toFiniteFormulaResult(values.length > 0 ? Math.min(...values) : 0);
34494
+ if (name === "MAX") return toFiniteFormulaResult(values.length > 0 ? Math.max(...values) : 0);
34321
34495
  if (name === "COUNT") return { value: values.length, error: null };
34322
34496
  return { value: null, error: "invalid-formula" };
34323
34497
  }
34324
34498
  readCellNumber(label) {
34325
- const value = this.getCellValue(label);
34326
- const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
34327
- if (!Number.isFinite(parsed)) {
34499
+ const parsed = parseTableCellNumericValue(this.getCellValue(label));
34500
+ if (parsed == null) {
34328
34501
  return { value: null, error: "invalid-reference" };
34329
34502
  }
34330
34503
  return { value: parsed, error: null };
34331
34504
  }
34332
34505
  readOptionalCellNumber(label) {
34333
- const value = this.getCellValue(label);
34334
- const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
34335
- return Number.isFinite(parsed) ? parsed : null;
34506
+ return parseTableCellNumericValue(this.getCellValue(label));
34336
34507
  }
34337
34508
  peekOperator(operators) {
34338
34509
  const token = this.tokens[this.index];
@@ -34397,6 +34568,75 @@ function getSelectionTableCellNode(editor) {
34397
34568
  }
34398
34569
  return null;
34399
34570
  }
34571
+ function getSelectionTableCellPos(editor) {
34572
+ const cellSelection = editor.state.selection;
34573
+ const anchorCellPos = cellSelection.$anchorCell?.pos;
34574
+ if (typeof anchorCellPos === "number") {
34575
+ const node = editor.state.doc.nodeAt(anchorCellPos);
34576
+ if (node?.type.name === "tableCell" || node?.type.name === "tableHeader") {
34577
+ return anchorCellPos;
34578
+ }
34579
+ }
34580
+ const { $from } = editor.state.selection;
34581
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
34582
+ const node = $from.node(depth);
34583
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
34584
+ return $from.before(depth);
34585
+ }
34586
+ }
34587
+ return null;
34588
+ }
34589
+ function getSelectionTableCellAnchor(editor) {
34590
+ const cellPos = getSelectionTableCellPos(editor);
34591
+ if (cellPos == null) return null;
34592
+ const node = editor.state.doc.nodeAt(cellPos);
34593
+ if (!node || node.type.name !== "tableCell" && node.type.name !== "tableHeader") return null;
34594
+ const $cell = editor.state.doc.resolve(Math.min(cellPos + 1, editor.state.doc.content.size));
34595
+ for (let depth = $cell.depth; depth > 0; depth -= 1) {
34596
+ const tableNode = $cell.node(depth);
34597
+ if (tableNode.type.name !== "table") continue;
34598
+ const tableStart = $cell.start(depth);
34599
+ const rect = safeFindCell2(import_tables5.TableMap.get(tableNode), cellPos - tableStart);
34600
+ if (!rect) return null;
34601
+ return {
34602
+ cellPos,
34603
+ column: rect.left,
34604
+ label: `${indexToColumnName(rect.left)}${rect.top + 1}`,
34605
+ node,
34606
+ row: rect.top,
34607
+ tablePos: tableStart - 1
34608
+ };
34609
+ }
34610
+ return null;
34611
+ }
34612
+ function restoreSelectionAtTableCell(editor, anchor) {
34613
+ const tableNode = editor.state.doc.nodeAt(anchor.tablePos);
34614
+ if (!tableNode || tableNode.type.name !== "table") return false;
34615
+ const map = import_tables5.TableMap.get(tableNode);
34616
+ if (anchor.row >= map.height || anchor.column >= map.width) return false;
34617
+ const relativeCellPos = map.positionAt(anchor.row, anchor.column, tableNode);
34618
+ const cellPos = anchor.tablePos + 1 + relativeCellPos;
34619
+ const cellNode = editor.state.doc.nodeAt(cellPos);
34620
+ if (!cellNode || cellNode.type.name !== "tableCell" && cellNode.type.name !== "tableHeader") return false;
34621
+ const selectionPos = Math.min(cellPos + 2, editor.state.doc.content.size);
34622
+ editor.view.dispatch(
34623
+ editor.state.tr.setSelection(import_state10.TextSelection.near(editor.state.doc.resolve(selectionPos))).setMeta("addToHistory", false)
34624
+ );
34625
+ return true;
34626
+ }
34627
+ function getSelectedTableFormulaCell(editor) {
34628
+ const selectedCell = getSelectionTableCellAnchor(editor);
34629
+ if (!selectedCell) return null;
34630
+ const formula = typeof selectedCell.node.attrs.formula === "string" ? selectedCell.node.attrs.formula.trim() : "";
34631
+ if (!formula) return null;
34632
+ const computedValue = typeof selectedCell.node.attrs.computedValue === "string" ? selectedCell.node.attrs.computedValue : "";
34633
+ return {
34634
+ ...selectedCell,
34635
+ computedValue,
34636
+ formula,
34637
+ formulaState: computedValue.startsWith("#") ? "error" : "computed"
34638
+ };
34639
+ }
34400
34640
  function getSelectionTableInfo(editor) {
34401
34641
  const { $from } = editor.state.selection;
34402
34642
  for (let depth = $from.depth; depth > 0; depth -= 1) {
@@ -34443,6 +34683,28 @@ function createCellDisplayContent(cellNode, displayValue) {
34443
34683
  }
34444
34684
  return [paragraphType.create(null, cellNode.type.schema.text(displayValue))];
34445
34685
  }
34686
+ function removeSelectedTableCellFormula(editor, clearContent) {
34687
+ const selectedCell = getSelectedTableFormulaCell(editor);
34688
+ if (!selectedCell) return false;
34689
+ const nextCell2 = selectedCell.node.type.create(
34690
+ {
34691
+ ...selectedCell.node.attrs,
34692
+ formula: null,
34693
+ computedValue: null
34694
+ },
34695
+ clearContent ? createCellDisplayContent(selectedCell.node, "") : selectedCell.node.content,
34696
+ selectedCell.node.marks
34697
+ );
34698
+ let transaction = editor.state.tr.replaceWith(selectedCell.cellPos, selectedCell.cellPos + selectedCell.node.nodeSize, nextCell2).setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true);
34699
+ const selectionPos = Math.min(selectedCell.cellPos + 2, transaction.doc.content.size);
34700
+ transaction = transaction.setSelection(import_state10.TextSelection.near(transaction.doc.resolve(selectionPos)));
34701
+ editor.view.dispatch(transaction);
34702
+ recalculateActiveTableFormulas(editor);
34703
+ restoreSelectionAtTableCell(editor, selectedCell);
34704
+ editor.view.focus();
34705
+ dispatchTableLayoutChange(editor);
34706
+ return true;
34707
+ }
34446
34708
  function buildTableValueMap(tableNode) {
34447
34709
  const map = import_tables5.TableMap.get(tableNode);
34448
34710
  const values = /* @__PURE__ */ new Map();
@@ -34465,21 +34727,15 @@ function normalizeFormulaInput(formula) {
34465
34727
  function setSelectedTableCellFormula(editor, formula) {
34466
34728
  const normalized = normalizeFormulaInput(formula);
34467
34729
  const { state, view } = editor;
34730
+ const selectionAnchor = getSelectionTableCellAnchor(editor);
34468
34731
  if (!normalized) {
34469
- const clearedFormula = (0, import_tables5.setCellAttr)("formula", null)(state, view.dispatch.bind(view));
34470
- const clearedValue2 = (0, import_tables5.setCellAttr)("computedValue", null)(editor.state, view.dispatch.bind(view));
34471
- if (clearedFormula || clearedValue2) {
34472
- recalculateActiveTableFormulas(editor);
34473
- view.focus();
34474
- dispatchTableLayoutChange(editor);
34475
- return true;
34476
- }
34477
- return false;
34732
+ return clearSelectedTableCellFormula(editor);
34478
34733
  }
34479
34734
  const appliedFormula = (0, import_tables5.setCellAttr)("formula", normalized)(state, view.dispatch.bind(view));
34480
34735
  const clearedValue = (0, import_tables5.setCellAttr)("computedValue", null)(editor.state, view.dispatch.bind(view));
34481
34736
  if (appliedFormula || clearedValue) {
34482
34737
  recalculateActiveTableFormulas(editor);
34738
+ if (selectionAnchor) restoreSelectionAtTableCell(editor, selectionAnchor);
34483
34739
  view.focus();
34484
34740
  dispatchTableLayoutChange(editor);
34485
34741
  return true;
@@ -34487,7 +34743,10 @@ function setSelectedTableCellFormula(editor, formula) {
34487
34743
  return false;
34488
34744
  }
34489
34745
  function clearSelectedTableCellFormula(editor) {
34490
- return setSelectedTableCellFormula(editor, "");
34746
+ return removeSelectedTableCellFormula(editor, true);
34747
+ }
34748
+ function convertSelectedTableCellFormulaToValue(editor) {
34749
+ return removeSelectedTableCellFormula(editor, false);
34491
34750
  }
34492
34751
  function setSelectedTableCellNumberFormat(editor, numberFormat) {
34493
34752
  const value = numberFormat && numberFormat !== "text" ? numberFormat : null;
@@ -34687,6 +34946,58 @@ function recalculateAllTableFormulas(editor) {
34687
34946
 
34688
34947
  // src/components/UEditor/menus.tsx
34689
34948
  var import_jsx_runtime92 = require("react/jsx-runtime");
34949
+ var FloatingSlashCommandMenu = ({ editor, onClose }) => {
34950
+ const t = useSmartTranslations("UEditor");
34951
+ const messages = (0, import_react72.useMemo)(() => buildSlashCommandMessages(t), [t]);
34952
+ const items = (0, import_react72.useMemo)(() => buildSlashCommandItems({ query: "", messages }), [messages]);
34953
+ const listRef = (0, import_react72.useRef)(null);
34954
+ (0, import_react72.useEffect)(() => {
34955
+ const handleKeyDown2 = (event) => {
34956
+ if (event.key === "Escape") {
34957
+ event.preventDefault();
34958
+ onClose();
34959
+ return;
34960
+ }
34961
+ const handled = listRef.current?.onKeyDown({ event }) ?? false;
34962
+ if (handled) {
34963
+ event.preventDefault();
34964
+ }
34965
+ };
34966
+ document.addEventListener("keydown", handleKeyDown2);
34967
+ return () => document.removeEventListener("keydown", handleKeyDown2);
34968
+ }, [onClose]);
34969
+ return /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
34970
+ SlashCommandList,
34971
+ {
34972
+ ref: listRef,
34973
+ items,
34974
+ messages,
34975
+ command: (item) => {
34976
+ item.command({ editor });
34977
+ onClose();
34978
+ }
34979
+ }
34980
+ );
34981
+ };
34982
+ var FloatingMenuContent = ({ editor }) => {
34983
+ const t = useSmartTranslations("UEditor");
34984
+ const [showCommands, setShowCommands] = (0, import_react72.useState)(false);
34985
+ if (showCommands) {
34986
+ return /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(FloatingSlashCommandMenu, { editor, onClose: () => setShowCommands(false) });
34987
+ }
34988
+ return /* @__PURE__ */ (0, import_jsx_runtime92.jsxs)(
34989
+ "button",
34990
+ {
34991
+ type: "button",
34992
+ onClick: () => setShowCommands(true),
34993
+ className: "flex items-center gap-1 px-2 py-1.5 rounded-lg hover:bg-accent transition-all group",
34994
+ children: [
34995
+ /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(import_lucide_react54.Plus, { className: "w-4 h-4 text-muted-foreground group-hover:text-foreground" }),
34996
+ /* @__PURE__ */ (0, import_jsx_runtime92.jsx)("span", { className: "text-sm text-muted-foreground group-hover:text-foreground", children: t("floatingMenu.addBlock") })
34997
+ ]
34998
+ }
34999
+ );
35000
+ };
34690
35001
  function applyTableCellBackground(editor, color) {
34691
35002
  const value = color || null;
34692
35003
  const { state, view } = editor;
@@ -35425,7 +35736,8 @@ var LinkPreviewContent = ({ editor, onEdit }) => {
35425
35736
  const url = editor.getAttributes("link").href || "";
35426
35737
  const t = useSmartTranslations("UEditor");
35427
35738
  const handleOpen = () => {
35428
- window.open(url, "_blank", "noopener,noreferrer");
35739
+ const safeUrl = sanitizeUEditorUrl(url, "link");
35740
+ if (safeUrl) window.open(safeUrl, "_blank", "noopener,noreferrer");
35429
35741
  };
35430
35742
  const handleUnlink = () => {
35431
35743
  editor.chain().focus().unsetLink().run();
@@ -35448,23 +35760,26 @@ var CustomBubbleMenu = ({
35448
35760
  const BUBBLE_MENU_ESTIMATED_HEIGHT = 44;
35449
35761
  const [isVisible, setIsVisible] = (0, import_react72.useState)(false);
35450
35762
  const [linkInputOpen, setLinkInputOpen] = (0, import_react72.useState)(false);
35451
- const [position, setPosition] = (0, import_react72.useState)({
35763
+ const [position, setPosition2] = (0, import_react72.useState)({
35452
35764
  top: 0,
35453
35765
  left: 0,
35454
35766
  placement: "top"
35455
35767
  });
35456
35768
  const menuRef = (0, import_react72.useRef)(null);
35457
35769
  const keepOpenRef = (0, import_react72.useRef)(false);
35770
+ const [keepOpen, setKeepOpenState] = (0, import_react72.useState)(false);
35458
35771
  const showTimeoutRef = (0, import_react72.useRef)(null);
35459
35772
  const suppressShowUntilRef = (0, import_react72.useRef)(0);
35460
35773
  const setKeepOpen = (0, import_react72.useCallback)((next) => {
35461
35774
  keepOpenRef.current = next;
35775
+ setKeepOpenState(next);
35462
35776
  if (!next) setLinkInputOpen(false);
35463
35777
  if (next) setIsVisible(true);
35464
35778
  }, []);
35465
35779
  const closeBubbleMenu = (0, import_react72.useCallback)(() => {
35466
35780
  suppressShowUntilRef.current = Date.now() + 1e3;
35467
35781
  keepOpenRef.current = false;
35782
+ setKeepOpenState(false);
35468
35783
  setLinkInputOpen(false);
35469
35784
  setIsVisible(false);
35470
35785
  if (showTimeoutRef.current) {
@@ -35520,7 +35835,7 @@ var CustomBubbleMenu = ({
35520
35835
  Math.max(viewportPadding, (start.left + end.left) / 2)
35521
35836
  );
35522
35837
  const top = placement === "top" ? Math.max(viewportPadding, selectionTop - BUBBLE_MENU_OFFSET) : Math.min(window.innerHeight - viewportPadding, selectionBottom + BUBBLE_MENU_OFFSET);
35523
- setPosition({ top, left, placement });
35838
+ setPosition2({ top, left, placement });
35524
35839
  if (keepOpenRef.current) {
35525
35840
  clearShowTimeout();
35526
35841
  setIsVisible(true);
@@ -35570,13 +35885,13 @@ var CustomBubbleMenu = ({
35570
35885
  onMouseDown: (e) => {
35571
35886
  const target = e.target;
35572
35887
  if (target?.closest?.("[data-ueditor-close-on-select]")) {
35573
- keepOpenRef.current = false;
35888
+ setKeepOpen(false);
35574
35889
  } else if (target?.closest?.("[data-ueditor-keep-open]")) {
35575
- keepOpenRef.current = true;
35890
+ setKeepOpen(true);
35576
35891
  }
35577
35892
  e.preventDefault();
35578
35893
  },
35579
- children: editor.isActive("link") && !keepOpenRef.current && !linkInputOpen ? /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
35894
+ children: editor.isActive("link") && !keepOpen && !linkInputOpen ? /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
35580
35895
  LinkPreviewContent,
35581
35896
  {
35582
35897
  editor,
@@ -35602,6 +35917,54 @@ var CustomBubbleMenu = ({
35602
35917
  document.body
35603
35918
  );
35604
35919
  };
35920
+ var CustomFloatingMenu = ({ editor }) => {
35921
+ const FLOATING_MENU_OFFSET = 16;
35922
+ const [isVisible, setIsVisible] = (0, import_react72.useState)(false);
35923
+ const [position, setPosition2] = (0, import_react72.useState)({ top: 0, left: 0 });
35924
+ (0, import_react72.useEffect)(() => {
35925
+ const updatePosition = () => {
35926
+ const { state, view } = editor;
35927
+ const { $from, empty } = state.selection;
35928
+ const isEmptyTextBlock = $from.parent.isTextblock && $from.parent.type.name === "paragraph" && $from.parent.textContent === "" && empty;
35929
+ if (!isEmptyTextBlock || !view.hasFocus()) {
35930
+ setIsVisible(false);
35931
+ return;
35932
+ }
35933
+ const coords = view.coordsAtPos($from.pos);
35934
+ setPosition2({ top: coords.top - FLOATING_MENU_OFFSET, left: coords.left });
35935
+ setIsVisible(true);
35936
+ };
35937
+ const handleBlur = () => setIsVisible(false);
35938
+ editor.on("selectionUpdate", updatePosition);
35939
+ editor.on("focus", updatePosition);
35940
+ editor.on("blur", handleBlur);
35941
+ editor.on("update", updatePosition);
35942
+ return () => {
35943
+ editor.off("selectionUpdate", updatePosition);
35944
+ editor.off("focus", updatePosition);
35945
+ editor.off("blur", handleBlur);
35946
+ editor.off("update", updatePosition);
35947
+ };
35948
+ }, [editor]);
35949
+ if (!isVisible) return null;
35950
+ return (0, import_react_dom8.createPortal)(
35951
+ /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
35952
+ "div",
35953
+ {
35954
+ "data-popover": true,
35955
+ 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",
35956
+ style: {
35957
+ top: `${position.top}px`,
35958
+ left: `${position.left}px`,
35959
+ transform: "translate(-50%, -100%)"
35960
+ },
35961
+ onMouseDown: (e) => e.preventDefault(),
35962
+ children: /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(FloatingMenuContent, { editor })
35963
+ }
35964
+ ),
35965
+ document.body
35966
+ );
35967
+ };
35605
35968
 
35606
35969
  // src/components/UEditor/CharacterCount.tsx
35607
35970
  var import_jsx_runtime93 = require("react/jsx-runtime");
@@ -38677,7 +39040,7 @@ var Selection = class {
38677
39040
  found.
38678
39041
  */
38679
39042
  static findFrom($pos, dir, textOnly = false) {
38680
- let inner = $pos.parent.inlineContent ? new TextSelection3($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
39043
+ let inner = $pos.parent.inlineContent ? new TextSelection4($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
38681
39044
  if (inner)
38682
39045
  return inner;
38683
39046
  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
@@ -38746,7 +39109,7 @@ var Selection = class {
38746
39109
  returns the bookmark for that.
38747
39110
  */
38748
39111
  getBookmark() {
38749
- return TextSelection3.between(this.$anchor, this.$head).getBookmark();
39112
+ return TextSelection4.between(this.$anchor, this.$head).getBookmark();
38750
39113
  }
38751
39114
  };
38752
39115
  Selection.prototype.visible = true;
@@ -38766,7 +39129,7 @@ function checkTextSelection($pos) {
38766
39129
  console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
38767
39130
  }
38768
39131
  }
38769
- var TextSelection3 = class _TextSelection extends Selection {
39132
+ var TextSelection4 = class _TextSelection extends Selection {
38770
39133
  /**
38771
39134
  Construct a text selection between the given points.
38772
39135
  */
@@ -38852,7 +39215,7 @@ var TextSelection3 = class _TextSelection extends Selection {
38852
39215
  return new _TextSelection($anchor, $head);
38853
39216
  }
38854
39217
  };
38855
- Selection.jsonID("text", TextSelection3);
39218
+ Selection.jsonID("text", TextSelection4);
38856
39219
  var TextBookmark = class _TextBookmark {
38857
39220
  constructor(anchor, head) {
38858
39221
  this.anchor = anchor;
@@ -38862,7 +39225,7 @@ var TextBookmark = class _TextBookmark {
38862
39225
  return new _TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
38863
39226
  }
38864
39227
  resolve(doc) {
38865
- return TextSelection3.between(doc.resolve(this.anchor), doc.resolve(this.head));
39228
+ return TextSelection4.between(doc.resolve(this.anchor), doc.resolve(this.head));
38866
39229
  }
38867
39230
  };
38868
39231
  var NodeSelection2 = class _NodeSelection extends Selection {
@@ -38981,7 +39344,7 @@ var AllBookmark = {
38981
39344
  };
38982
39345
  function findSelectionIn(doc, node, pos, index, dir, text = false) {
38983
39346
  if (node.inlineContent)
38984
- return TextSelection3.create(doc, pos);
39347
+ return TextSelection4.create(doc, pos);
38985
39348
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
38986
39349
  let child = node.child(i);
38987
39350
  if (!child.isAtom) {
@@ -39573,7 +39936,7 @@ var CellSelection = class CellSelection2 extends Selection {
39573
39936
  else if (tableChanged && this.isColSelection()) return CellSelection2.colSelection($anchorCell, $headCell);
39574
39937
  else return new CellSelection2($anchorCell, $headCell);
39575
39938
  }
39576
- return TextSelection3.between($anchorCell, $headCell);
39939
+ return TextSelection4.between($anchorCell, $headCell);
39577
39940
  }
39578
39941
  content() {
39579
39942
  const table = this.$anchorCell.node(-1);
@@ -40188,7 +40551,7 @@ function shiftArrow(axis, dir) {
40188
40551
  };
40189
40552
  }
40190
40553
  function atEndOfCell(view, axis, dir) {
40191
- if (!(view.state.selection instanceof TextSelection3)) return null;
40554
+ if (!(view.state.selection instanceof TextSelection4)) return null;
40192
40555
  const { $head } = view.state.selection;
40193
40556
  for (let d = $head.depth - 1; d >= 0; d--) {
40194
40557
  const parent = $head.node(d);
@@ -41308,7 +41671,7 @@ function TableControls({ editor, containerRef }) {
41308
41671
  destructive: true
41309
41672
  }
41310
41673
  ];
41311
- }, [layout, runAtActiveCell, t]);
41674
+ }, [editor, layout, runAtActiveCell, t]);
41312
41675
  const getRowHandleMenuItems = import_react74.default.useCallback((rowHandle) => [
41313
41676
  {
41314
41677
  label: t("tableMenu.addRowBefore"),
@@ -41524,6 +41887,42 @@ var UEDITOR_PROSEMIRROR_CLASS_NAME = cn(
41524
41887
  "[&_th]:px-2",
41525
41888
  "[&_th]:py-0",
41526
41889
  "[&_th_p]:my-0",
41890
+ "[&_td[data-formula]]:pr-7",
41891
+ "[&_th[data-formula]]:pr-7",
41892
+ "[&_td[data-formula]]:before:pointer-events-none",
41893
+ "[&_th[data-formula]]:before:pointer-events-none",
41894
+ "[&_td[data-formula]]:before:absolute",
41895
+ "[&_th[data-formula]]:before:absolute",
41896
+ "[&_td[data-formula]]:before:right-1",
41897
+ "[&_th[data-formula]]:before:right-1",
41898
+ "[&_td[data-formula]]:before:top-1",
41899
+ "[&_th[data-formula]]:before:top-1",
41900
+ "[&_td[data-formula]]:before:z-[1]",
41901
+ "[&_th[data-formula]]:before:z-[1]",
41902
+ "[&_td[data-formula]]:before:rounded-sm",
41903
+ "[&_th[data-formula]]:before:rounded-sm",
41904
+ "[&_td[data-formula]]:before:bg-primary/10",
41905
+ "[&_th[data-formula]]:before:bg-primary/10",
41906
+ "[&_td[data-formula]]:before:px-1",
41907
+ "[&_th[data-formula]]:before:px-1",
41908
+ "[&_td[data-formula]]:before:font-mono",
41909
+ "[&_th[data-formula]]:before:font-mono",
41910
+ "[&_td[data-formula]]:before:text-[9px]",
41911
+ "[&_th[data-formula]]:before:text-[9px]",
41912
+ "[&_td[data-formula]]:before:font-semibold",
41913
+ "[&_th[data-formula]]:before:font-semibold",
41914
+ "[&_td[data-formula]]:before:leading-4",
41915
+ "[&_th[data-formula]]:before:leading-4",
41916
+ "[&_td[data-formula]]:before:text-primary",
41917
+ "[&_th[data-formula]]:before:text-primary",
41918
+ "[&_td[data-formula]]:before:content-['fx']",
41919
+ "[&_th[data-formula]]:before:content-['fx']",
41920
+ "[&_td[data-formula-state='error']]:bg-destructive/5",
41921
+ "[&_th[data-formula-state='error']]:bg-destructive/5",
41922
+ "[&_td[data-formula-state='error']]:before:bg-destructive/10",
41923
+ "[&_th[data-formula-state='error']]:before:bg-destructive/10",
41924
+ "[&_td[data-formula-state='error']]:before:text-destructive",
41925
+ "[&_th[data-formula-state='error']]:before:text-destructive",
41527
41926
  "[&_td[colwidth]]:min-w-0",
41528
41927
  "[&_th[colwidth]]:min-w-0",
41529
41928
  "[&_td[data-colwidth]]:min-w-0",
@@ -42029,7 +42428,7 @@ function useUEditorTableInteractions(editor, editable = true) {
42029
42428
  clearHoveredTableCell();
42030
42429
  clearAllTableResizeHover();
42031
42430
  };
42032
- }, [beginResize, cancelResize, cleanupRowResize, clearActiveTableCell, clearAllTableResizeHover, clearHoveredTableCell, editable, editor, handleRowResizePointerMove, handleRowResizePointerUp, hideColumnGuide, hideRowGuide, isRowResizing, scheduleTableLayoutSync, showColumnGuide, showRowGuide, syncActiveRowResizeGuide, syncActiveTableCellFromSelection, updateActiveCellHighlight]);
42431
+ }, [beginResize, cancelResize, cleanupRowResize, clearActiveTableCell, clearAllTableResizeHover, clearHoveredTableCell, editable, editor, handleRowResizePointerMove, handleRowResizePointerUp, hideColumnGuide, hideRowGuide, isRowResizing, scheduleTableLayoutSync, setActiveTableCell, setHoveredTableCell, showColumnGuide, showRowGuide, syncActiveRowResizeGuide, syncActiveTableCellFromSelection, updateActiveCellHighlight]);
42033
42432
  return {
42034
42433
  editorContentRef,
42035
42434
  tableColumnGuideRef,
@@ -42038,9 +42437,402 @@ function useUEditorTableInteractions(editor, editable = true) {
42038
42437
  };
42039
42438
  }
42040
42439
 
42440
+ // src/components/UEditor/use-formula-coordinate-overlay.ts
42441
+ var import_react77 = require("react");
42442
+ var import_tables8 = require("@tiptap/pm/tables");
42443
+
42444
+ // src/components/UEditor/table-formula-range-picker.ts
42445
+ var import_state11 = require("@tiptap/pm/state");
42446
+ var import_tables7 = require("@tiptap/pm/tables");
42447
+ function getCellText2(cellNode) {
42448
+ return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
42449
+ }
42450
+ function getFormulaEditingTableContext(view) {
42451
+ const { $from } = view.state.selection;
42452
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
42453
+ const node = $from.node(depth);
42454
+ if (node.type.name !== "tableCell" && node.type.name !== "tableHeader") continue;
42455
+ if (!getCellText2(node).startsWith("=")) return null;
42456
+ const cellPos = $from.before(depth);
42457
+ const cellDom = view.nodeDOM(cellPos);
42458
+ if (!(cellDom instanceof HTMLTableCellElement)) return null;
42459
+ const tableDepth = depth - 2;
42460
+ const tableNode = tableDepth > 0 ? $from.node(tableDepth) : null;
42461
+ if (!tableNode || tableNode.type.name !== "table") return null;
42462
+ const tableDom = cellDom.closest("table");
42463
+ if (!(tableDom instanceof HTMLTableElement)) return null;
42464
+ return {
42465
+ cellContentEnd: $from.end(depth),
42466
+ cellContentStart: $from.start(depth),
42467
+ cellDom,
42468
+ formula: getCellText2(node),
42469
+ tableDom,
42470
+ tableNode,
42471
+ tablePos: $from.before(tableDepth)
42472
+ };
42473
+ }
42474
+ return null;
42475
+ }
42476
+ function cancelFormulaEditing(view) {
42477
+ const context = getFormulaEditingTableContext(view);
42478
+ if (!context) return false;
42479
+ let tr = view.state.tr.delete(context.cellContentStart, context.cellContentEnd);
42480
+ const selectionPos = Math.min(context.cellContentStart, tr.doc.content.size);
42481
+ tr = tr.setSelection(import_state11.TextSelection.near(tr.doc.resolve(selectionPos)));
42482
+ view.dispatch(tr);
42483
+ view.focus();
42484
+ return true;
42485
+ }
42486
+ function getReferenceInsertionPrefix(view, cellContentStart, insertionPos) {
42487
+ const textBeforeCursor = view.state.doc.textBetween(cellContentStart, insertionPos, "", "");
42488
+ const isInsideFunctionArguments = /[A-Z]+\([^)]*$/i.test(textBeforeCursor);
42489
+ const followsReference = /[A-Z]+[1-9]\d*(?::[A-Z]+[1-9]\d*)?\s*$/i.test(textBeforeCursor);
42490
+ return isInsideFunctionArguments && followsReference ? "," : "";
42491
+ }
42492
+ function findTableForPos(view, pos) {
42493
+ const $pos = view.state.doc.resolve(pos);
42494
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
42495
+ const node = $pos.node(depth);
42496
+ if (node.type.name === "table") {
42497
+ return {
42498
+ node,
42499
+ pos: $pos.before(depth),
42500
+ start: $pos.start(depth)
42501
+ };
42502
+ }
42503
+ }
42504
+ return null;
42505
+ }
42506
+ function getCellRelativePosFromDomPos2(map, tableStart, domPos) {
42507
+ const relativeDomPos = domPos - tableStart;
42508
+ const seen = /* @__PURE__ */ new Set();
42509
+ for (const relativeCellPos of map.map) {
42510
+ if (seen.has(relativeCellPos)) continue;
42511
+ seen.add(relativeCellPos);
42512
+ if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
42513
+ return relativeCellPos;
42514
+ }
42515
+ }
42516
+ return null;
42517
+ }
42518
+ function getFormulaTableCellInfo(view, target, tablePos) {
42519
+ const element = resolveEventElement(target);
42520
+ const cell = element?.closest?.("th,td");
42521
+ if (!(cell instanceof HTMLTableCellElement)) return null;
42522
+ const domPos = view.posAtDOM(cell, 0);
42523
+ const tableInfo = findTableForPos(view, domPos);
42524
+ if (!tableInfo || tableInfo.pos !== tablePos) return null;
42525
+ const map = import_tables7.TableMap.get(tableInfo.node);
42526
+ const relativeCellPos = getCellRelativePosFromDomPos2(map, tableInfo.start, domPos);
42527
+ if (relativeCellPos == null) return null;
42528
+ const rect = map.findCell(relativeCellPos);
42529
+ return {
42530
+ cell,
42531
+ label: `${indexToColumnName(rect.left)}${rect.top + 1}`,
42532
+ rect
42533
+ };
42534
+ }
42535
+ function normalizeFormulaRangeLabel(fromLabel, toLabel) {
42536
+ return fromLabel === toLabel ? fromLabel : `${fromLabel}:${toLabel}`;
42537
+ }
42538
+ function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42539
+ if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell) return pickState;
42540
+ if (nextLabel === pickState.currentLabel) {
42541
+ return {
42542
+ ...pickState,
42543
+ currentCell
42544
+ };
42545
+ }
42546
+ let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42547
+ const nextTo = pickState.insertedFrom + nextLabel.length;
42548
+ tr = tr.setSelection(import_state11.TextSelection.create(tr.doc, nextTo));
42549
+ view.dispatch(tr);
42550
+ return {
42551
+ ...pickState,
42552
+ insertedTo: nextTo,
42553
+ currentLabel: nextLabel,
42554
+ currentCell
42555
+ };
42556
+ }
42557
+ function beginFormulaRangePick(view, event) {
42558
+ if (event.button !== 0) return null;
42559
+ const formulaCell = getFormulaEditingTableContext(view);
42560
+ if (!formulaCell) return null;
42561
+ const picked = getFormulaTableCellInfo(view, event.target, formulaCell.tablePos);
42562
+ if (!picked || picked.cell === formulaCell.cellDom) return null;
42563
+ const { from, to } = view.state.selection;
42564
+ const prefix = from === to ? getReferenceInsertionPrefix(view, formulaCell.cellContentStart, from) : "";
42565
+ const insertedText = `${prefix}${picked.label}`;
42566
+ const insertedFrom = from + prefix.length;
42567
+ let tr = view.state.tr.insertText(insertedText, from, to);
42568
+ tr = tr.setSelection(import_state11.TextSelection.create(tr.doc, from + insertedText.length));
42569
+ view.dispatch(tr);
42570
+ view.focus();
42571
+ event.preventDefault();
42572
+ event.stopPropagation();
42573
+ return {
42574
+ anchorLabel: picked.label,
42575
+ anchorCell: picked.cell,
42576
+ tablePos: formulaCell.tablePos,
42577
+ insertedFrom,
42578
+ insertedTo: insertedFrom + picked.label.length,
42579
+ currentLabel: picked.label,
42580
+ currentCell: picked.cell
42581
+ };
42582
+ }
42583
+ function updateFormulaRangePick(view, pickState, event) {
42584
+ const picked = getFormulaTableCellInfo(view, event.target, pickState.tablePos);
42585
+ if (!picked) return pickState;
42586
+ event.preventDefault();
42587
+ event.stopPropagation();
42588
+ return replacePickedLabel(
42589
+ view,
42590
+ pickState,
42591
+ normalizeFormulaRangeLabel(pickState.anchorLabel, picked.label),
42592
+ picked.cell
42593
+ );
42594
+ }
42595
+ function getFormulaRangePickHighlight(container, pickState) {
42596
+ if (!container.contains(pickState.anchorCell) || !container.contains(pickState.currentCell)) return null;
42597
+ const containerRect = container.getBoundingClientRect();
42598
+ const anchorRect = pickState.anchorCell.getBoundingClientRect();
42599
+ const currentRect = pickState.currentCell.getBoundingClientRect();
42600
+ const left = Math.min(anchorRect.left, currentRect.left) - containerRect.left + container.scrollLeft;
42601
+ const top = Math.min(anchorRect.top, currentRect.top) - containerRect.top + container.scrollTop;
42602
+ const right = Math.max(anchorRect.right, currentRect.right) - containerRect.left + container.scrollLeft;
42603
+ const bottom = Math.max(anchorRect.bottom, currentRect.bottom) - containerRect.top + container.scrollTop;
42604
+ return {
42605
+ left,
42606
+ top,
42607
+ width: Math.max(0, right - left),
42608
+ height: Math.max(0, bottom - top)
42609
+ };
42610
+ }
42611
+
42612
+ // src/components/UEditor/use-formula-coordinate-overlay.ts
42613
+ function setPosition(element, left, top, width, height) {
42614
+ element.style.left = `${left}px`;
42615
+ element.style.top = `${top}px`;
42616
+ if (width != null) element.style.width = `${Math.max(0, width)}px`;
42617
+ if (height != null) element.style.height = `${Math.max(0, height)}px`;
42618
+ }
42619
+ function createCoordinateLabel(kind, label) {
42620
+ const element = document.createElement("span");
42621
+ element.dataset.ueditorFormulaCoordinate = kind;
42622
+ element.textContent = label;
42623
+ 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";
42624
+ return element;
42625
+ }
42626
+ function createReferenceHighlight(label) {
42627
+ const element = document.createElement("span");
42628
+ element.dataset.ueditorFormulaReference = label;
42629
+ element.className = "pointer-events-none absolute z-[21] rounded-[2px] border-2 border-emerald-500 bg-emerald-500/15";
42630
+ return element;
42631
+ }
42632
+ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42633
+ const overlayRef = (0, import_react77.useRef)(null);
42634
+ (0, import_react77.useEffect)(() => {
42635
+ if (!editor) return void 0;
42636
+ const overlay = overlayRef.current;
42637
+ const container = containerRef.current;
42638
+ if (!overlay || !container) return void 0;
42639
+ let animationFrame = null;
42640
+ let activeTable = null;
42641
+ let activeTablePos = null;
42642
+ let hoverLabel = null;
42643
+ const clearOverlay = () => {
42644
+ activeTable = null;
42645
+ activeTablePos = null;
42646
+ hoverLabel = null;
42647
+ overlay.replaceChildren();
42648
+ overlay.style.display = "none";
42649
+ };
42650
+ const syncOverlay = () => {
42651
+ animationFrame = null;
42652
+ if (editor.isDestroyed) return;
42653
+ const context = getFormulaEditingTableContext(editor.view);
42654
+ if (!context) {
42655
+ clearOverlay();
42656
+ return;
42657
+ }
42658
+ const containerRect = container.getBoundingClientRect();
42659
+ const tableRect = context.tableDom.getBoundingClientRect();
42660
+ const tableLeft = tableRect.left - containerRect.left + container.scrollLeft;
42661
+ const tableTop = tableRect.top - containerRect.top + container.scrollTop;
42662
+ const map = import_tables8.TableMap.get(context.tableNode);
42663
+ const columnSegments = Array(map.width);
42664
+ const rowSegments = Array(map.height);
42665
+ const cellInfos = [];
42666
+ for (const cell of context.tableDom.querySelectorAll("th,td")) {
42667
+ if (!(cell instanceof HTMLTableCellElement)) continue;
42668
+ const info = getFormulaTableCellInfo(editor.view, cell, context.tablePos);
42669
+ if (!info) continue;
42670
+ cellInfos.push(info);
42671
+ const cellRect = cell.getBoundingClientRect();
42672
+ const columnSpan = Math.max(1, info.rect.right - info.rect.left);
42673
+ const rowSpan = Math.max(1, info.rect.bottom - info.rect.top);
42674
+ const columnWidth = cellRect.width / columnSpan;
42675
+ const rowHeight = cellRect.height / rowSpan;
42676
+ for (let column = info.rect.left; column < info.rect.right; column += 1) {
42677
+ if (!columnSegments[column] || columnSpan < columnSegments[column].span) {
42678
+ columnSegments[column] = {
42679
+ start: cellRect.left - containerRect.left + container.scrollLeft + (column - info.rect.left) * columnWidth,
42680
+ size: columnWidth,
42681
+ span: columnSpan
42682
+ };
42683
+ }
42684
+ }
42685
+ for (let row = info.rect.top; row < info.rect.bottom; row += 1) {
42686
+ if (!rowSegments[row] || rowSpan < rowSegments[row].span) {
42687
+ rowSegments[row] = {
42688
+ start: cellRect.top - containerRect.top + container.scrollTop + (row - info.rect.top) * rowHeight,
42689
+ size: rowHeight,
42690
+ span: rowSpan
42691
+ };
42692
+ }
42693
+ }
42694
+ }
42695
+ const fallbackColumnWidth = map.width > 0 ? tableRect.width / map.width : 0;
42696
+ const fallbackRowHeight = map.height > 0 ? tableRect.height / map.height : 0;
42697
+ const children = [];
42698
+ const actions = document.createElement("div");
42699
+ actions.dataset.ueditorFormulaActions = "";
42700
+ actions.className = "pointer-events-auto absolute z-50 flex items-center gap-1 rounded-md border border-border bg-background p-1 shadow-md";
42701
+ const formulaCellRect = context.cellDom.getBoundingClientRect();
42702
+ setPosition(
42703
+ actions,
42704
+ formulaCellRect.left - containerRect.left + container.scrollLeft,
42705
+ formulaCellRect.bottom - containerRect.top + container.scrollTop + 4
42706
+ );
42707
+ const applyButton = document.createElement("button");
42708
+ applyButton.type = "button";
42709
+ applyButton.dataset.ueditorFormulaApply = "";
42710
+ applyButton.disabled = isDraftTableFormula(context.formula);
42711
+ applyButton.textContent = `${applyButton.disabled ? "" : "\u2713 "}${labels.apply} (Enter)`;
42712
+ 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";
42713
+ const cancelButton = document.createElement("button");
42714
+ cancelButton.type = "button";
42715
+ cancelButton.dataset.ueditorFormulaCancel = "";
42716
+ cancelButton.textContent = `${labels.cancel} (Esc)`;
42717
+ cancelButton.className = "rounded px-2.5 py-1 text-xs font-medium text-foreground hover:bg-muted";
42718
+ const preserveSelection = (event) => {
42719
+ event.preventDefault();
42720
+ event.stopPropagation();
42721
+ };
42722
+ applyButton.addEventListener("mousedown", preserveSelection);
42723
+ cancelButton.addEventListener("mousedown", preserveSelection);
42724
+ applyButton.addEventListener("click", (event) => {
42725
+ event.preventDefault();
42726
+ event.stopPropagation();
42727
+ if (!applyButton.disabled) recalculateActiveTableFormulas(editor);
42728
+ });
42729
+ cancelButton.addEventListener("click", (event) => {
42730
+ event.preventDefault();
42731
+ event.stopPropagation();
42732
+ cancelFormulaEditing(editor.view);
42733
+ });
42734
+ actions.append(applyButton, cancelButton);
42735
+ children.push(actions);
42736
+ for (let column = 0; column < map.width; column += 1) {
42737
+ const segment = columnSegments[column] ?? {
42738
+ start: tableLeft + column * fallbackColumnWidth,
42739
+ size: fallbackColumnWidth,
42740
+ span: map.width
42741
+ };
42742
+ const label = createCoordinateLabel("column", indexToColumnName(column));
42743
+ setPosition(label, segment.start, Math.max(0, tableTop - 22), segment.size, 20);
42744
+ children.push(label);
42745
+ }
42746
+ for (let row = 0; row < map.height; row += 1) {
42747
+ const segment = rowSegments[row] ?? {
42748
+ start: tableTop + row * fallbackRowHeight,
42749
+ size: fallbackRowHeight,
42750
+ span: map.height
42751
+ };
42752
+ const label = createCoordinateLabel("row", String(row + 1));
42753
+ setPosition(label, Math.max(0, tableLeft - 28), segment.start, 26, segment.size);
42754
+ children.push(label);
42755
+ }
42756
+ const references = new Set(getTableFormulaReferences(context.formula));
42757
+ for (const info of cellInfos) {
42758
+ if (!info || !references.has(info.label)) continue;
42759
+ const cellRect = info.cell.getBoundingClientRect();
42760
+ const highlight = createReferenceHighlight(info.label);
42761
+ setPosition(
42762
+ highlight,
42763
+ cellRect.left - containerRect.left + container.scrollLeft + 2,
42764
+ cellRect.top - containerRect.top + container.scrollTop + 2,
42765
+ cellRect.width - 4,
42766
+ cellRect.height - 4
42767
+ );
42768
+ children.push(highlight);
42769
+ }
42770
+ hoverLabel = document.createElement("span");
42771
+ hoverLabel.dataset.ueditorFormulaHoverLabel = "";
42772
+ 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";
42773
+ children.push(hoverLabel);
42774
+ overlay.replaceChildren(...children);
42775
+ overlay.style.display = "block";
42776
+ activeTable = context.tableDom;
42777
+ activeTablePos = context.tablePos;
42778
+ };
42779
+ const scheduleSync = () => {
42780
+ if (animationFrame != null) return;
42781
+ animationFrame = window.requestAnimationFrame(syncOverlay);
42782
+ };
42783
+ const handleMouseMove2 = (event) => {
42784
+ if (!activeTable || activeTablePos == null || !hoverLabel) return;
42785
+ const target = event.target instanceof Element ? event.target.closest("th,td") : null;
42786
+ if (!(target instanceof HTMLTableCellElement) || target.closest("table") !== activeTable) {
42787
+ hoverLabel.style.display = "none";
42788
+ return;
42789
+ }
42790
+ const info = getFormulaTableCellInfo(editor.view, target, activeTablePos);
42791
+ if (!info) {
42792
+ hoverLabel.style.display = "none";
42793
+ return;
42794
+ }
42795
+ const containerRect = container.getBoundingClientRect();
42796
+ const cellRect = target.getBoundingClientRect();
42797
+ hoverLabel.textContent = info.label;
42798
+ hoverLabel.style.display = "block";
42799
+ setPosition(
42800
+ hoverLabel,
42801
+ cellRect.left - containerRect.left + container.scrollLeft + 4,
42802
+ cellRect.top - containerRect.top + container.scrollTop + 4
42803
+ );
42804
+ };
42805
+ const handleMouseLeave2 = () => {
42806
+ if (hoverLabel) hoverLabel.style.display = "none";
42807
+ };
42808
+ editor.on("selectionUpdate", scheduleSync);
42809
+ editor.on("update", scheduleSync);
42810
+ editor.on("focus", scheduleSync);
42811
+ editor.on("blur", scheduleSync);
42812
+ editor.view.dom.addEventListener("mousemove", handleMouseMove2);
42813
+ editor.view.dom.addEventListener("mouseleave", handleMouseLeave2);
42814
+ container.addEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42815
+ window.addEventListener("resize", scheduleSync);
42816
+ scheduleSync();
42817
+ return () => {
42818
+ editor.off("selectionUpdate", scheduleSync);
42819
+ editor.off("update", scheduleSync);
42820
+ editor.off("focus", scheduleSync);
42821
+ editor.off("blur", scheduleSync);
42822
+ editor.view.dom.removeEventListener("mousemove", handleMouseMove2);
42823
+ editor.view.dom.removeEventListener("mouseleave", handleMouseLeave2);
42824
+ container.removeEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42825
+ window.removeEventListener("resize", scheduleSync);
42826
+ if (animationFrame != null) window.cancelAnimationFrame(animationFrame);
42827
+ clearOverlay();
42828
+ };
42829
+ }, [containerRef, editor, labels.apply, labels.cancel]);
42830
+ return overlayRef;
42831
+ }
42832
+
42041
42833
  // src/components/UEditor/menu-bar.tsx
42042
- var import_react77 = __toESM(require("react"), 1);
42043
- var import_react78 = require("@tiptap/react");
42834
+ var import_react78 = __toESM(require("react"), 1);
42835
+ var import_react79 = require("@tiptap/react");
42044
42836
  var import_lucide_react58 = require("lucide-react");
42045
42837
 
42046
42838
  // src/components/UEditor/preview-html.ts
@@ -42221,7 +43013,7 @@ function renderMenuItems(items) {
42221
43013
  case "sub":
42222
43014
  return /* @__PURE__ */ (0, import_jsx_runtime99.jsx)(DropdownMenuSub, { label: item.label, icon: item.icon, disabled: item.disabled, children: renderMenuItems(item.items) }, i);
42223
43015
  case "custom":
42224
- return /* @__PURE__ */ (0, import_jsx_runtime99.jsx)(import_react77.default.Fragment, { children: item.render() }, item.key);
43016
+ return /* @__PURE__ */ (0, import_jsx_runtime99.jsx)(import_react78.default.Fragment, { children: item.render() }, item.key);
42225
43017
  }
42226
43018
  });
42227
43019
  }
@@ -42597,7 +43389,7 @@ function buildTableMenuItems(t, editor, onInsertTable) {
42597
43389
  }
42598
43390
  ];
42599
43391
  }
42600
- var MenuBarTrigger = import_react77.default.forwardRef(
43392
+ var MenuBarTrigger = import_react78.default.forwardRef(
42601
43393
  ({ children, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime99.jsx)(
42602
43394
  "button",
42603
43395
  {
@@ -42629,18 +43421,18 @@ var MenuBar = ({
42629
43421
  onPreview
42630
43422
  }) => {
42631
43423
  const t = useSmartTranslations("UEditor");
42632
- (0, import_react78.useEditorState)({
43424
+ (0, import_react79.useEditorState)({
42633
43425
  editor,
42634
43426
  selector: ({ transactionNumber }) => transactionNumber
42635
43427
  });
42636
- const fileInputRef = (0, import_react77.useRef)(null);
42637
- const [showImageInput, setShowImageInput] = (0, import_react77.useState)(false);
42638
- const [showLinkInput, setShowLinkInput] = (0, import_react77.useState)(false);
42639
- const [isInsertMenuOpen, setIsInsertMenuOpen] = (0, import_react77.useState)(false);
42640
- const [showSourceDialog, setShowSourceDialog] = (0, import_react77.useState)(false);
42641
- const [sourceHtml, setSourceHtml] = (0, import_react77.useState)("");
42642
- const [showPreviewDialog, setShowPreviewDialog] = (0, import_react77.useState)(false);
42643
- const previewHtml = (0, import_react77.useMemo)(
43428
+ const fileInputRef = (0, import_react78.useRef)(null);
43429
+ const [showImageInput, setShowImageInput] = (0, import_react78.useState)(false);
43430
+ const [showLinkInput, setShowLinkInput] = (0, import_react78.useState)(false);
43431
+ const [isInsertMenuOpen, setIsInsertMenuOpen] = (0, import_react78.useState)(false);
43432
+ const [showSourceDialog, setShowSourceDialog] = (0, import_react78.useState)(false);
43433
+ const [sourceHtml, setSourceHtml] = (0, import_react78.useState)("");
43434
+ const [showPreviewDialog, setShowPreviewDialog] = (0, import_react78.useState)(false);
43435
+ const previewHtml = (0, import_react78.useMemo)(
42644
43436
  () => showPreviewDialog ? prepareUEditorPreviewHtml(editor.getHTML()) : "",
42645
43437
  [editor, showPreviewDialog]
42646
43438
  );
@@ -42922,149 +43714,156 @@ var MenuBar = ({
42922
43714
  ] });
42923
43715
  };
42924
43716
 
42925
- // src/components/UEditor/table-formula-range-picker.ts
42926
- var import_state10 = require("@tiptap/pm/state");
42927
- var import_tables7 = require("@tiptap/pm/tables");
42928
- function getCellText2(cellNode) {
42929
- return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
42930
- }
42931
- function findSelectionFormulaCell(view) {
42932
- const { $from } = view.state.selection;
42933
- for (let depth = $from.depth; depth > 0; depth -= 1) {
42934
- const node = $from.node(depth);
42935
- if (node.type.name !== "tableCell" && node.type.name !== "tableHeader") continue;
42936
- if (!getCellText2(node).startsWith("=")) return null;
42937
- const cellPos = $from.before(depth);
42938
- const cellDom = view.nodeDOM(cellPos);
42939
- const tableDepth = depth - 2;
42940
- const tableNode = tableDepth > 0 ? $from.node(tableDepth) : null;
42941
- if (!tableNode || tableNode.type.name !== "table") return null;
42942
- return {
42943
- cellDom: cellDom instanceof HTMLTableCellElement ? cellDom : null,
42944
- tablePos: $from.before(tableDepth)
43717
+ // src/components/UEditor/table-formula-bar.tsx
43718
+ var import_react80 = require("react");
43719
+ var import_react81 = require("@tiptap/react");
43720
+ var import_lucide_react59 = require("lucide-react");
43721
+ var import_jsx_runtime100 = require("react/jsx-runtime");
43722
+ function TableFormulaBar({ editor }) {
43723
+ const t = useSmartTranslations("UEditor");
43724
+ const inputRef = (0, import_react80.useRef)(null);
43725
+ const [draftState, setDraftState] = (0, import_react80.useState)(null);
43726
+ (0, import_react81.useEditorState)({
43727
+ editor,
43728
+ selector: ({ transactionNumber }) => transactionNumber
43729
+ });
43730
+ const selectedCell = getSelectedTableFormulaCell(editor);
43731
+ (0, import_react80.useEffect)(() => {
43732
+ const focusFormulaInput = () => {
43733
+ inputRef.current?.focus();
42945
43734
  };
42946
- }
42947
- return null;
42948
- }
42949
- function findTableForPos(view, pos) {
42950
- const $pos = view.state.doc.resolve(pos);
42951
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
42952
- const node = $pos.node(depth);
42953
- if (node.type.name === "table") {
42954
- return {
42955
- node,
42956
- pos: $pos.before(depth),
42957
- start: $pos.start(depth)
42958
- };
42959
- }
42960
- }
42961
- return null;
42962
- }
42963
- function getCellRelativePosFromDomPos2(map, tableStart, domPos) {
42964
- const relativeDomPos = domPos - tableStart;
42965
- const seen = /* @__PURE__ */ new Set();
42966
- for (const relativeCellPos of map.map) {
42967
- if (seen.has(relativeCellPos)) continue;
42968
- seen.add(relativeCellPos);
42969
- if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
42970
- return relativeCellPos;
42971
- }
42972
- }
42973
- return null;
42974
- }
42975
- function getPickedCellLabel(view, target, tablePos) {
42976
- const element = resolveEventElement(target);
42977
- const cell = element?.closest?.("th,td");
42978
- if (!(cell instanceof HTMLTableCellElement)) return null;
42979
- const domPos = view.posAtDOM(cell, 0);
42980
- const tableInfo = findTableForPos(view, domPos);
42981
- if (!tableInfo || tableInfo.pos !== tablePos) return null;
42982
- const map = import_tables7.TableMap.get(tableInfo.node);
42983
- const relativeCellPos = getCellRelativePosFromDomPos2(map, tableInfo.start, domPos);
42984
- if (relativeCellPos == null) return null;
42985
- const rect = map.findCell(relativeCellPos);
42986
- return {
42987
- cell,
42988
- label: `${indexToColumnName(rect.left)}${rect.top + 1}`
42989
- };
42990
- }
42991
- function normalizeFormulaRangeLabel(fromLabel, toLabel) {
42992
- return fromLabel === toLabel ? fromLabel : `${fromLabel}:${toLabel}`;
42993
- }
42994
- function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42995
- if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell) return pickState;
42996
- if (nextLabel === pickState.currentLabel) {
42997
- return {
42998
- ...pickState,
42999
- currentCell
43735
+ const handleDoubleClick = (event) => {
43736
+ const target = resolveEventElement(event.target);
43737
+ if (!target?.closest?.("td[data-formula],th[data-formula]")) return;
43738
+ event.preventDefault();
43739
+ focusFormulaInput();
43000
43740
  };
43001
- }
43002
- let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
43003
- const nextTo = pickState.insertedFrom + nextLabel.length;
43004
- tr = tr.setSelection(import_state10.TextSelection.create(tr.doc, nextTo));
43005
- view.dispatch(tr);
43006
- return {
43007
- ...pickState,
43008
- insertedTo: nextTo,
43009
- currentLabel: nextLabel,
43010
- currentCell
43741
+ editor.view.dom.addEventListener("dblclick", handleDoubleClick);
43742
+ return () => {
43743
+ editor.view.dom.removeEventListener("dblclick", handleDoubleClick);
43744
+ };
43745
+ }, [editor]);
43746
+ if (!selectedCell) return null;
43747
+ const selectedCellKey = `${selectedCell.tablePos}:${selectedCell.row}:${selectedCell.column}`;
43748
+ const draft = draftState?.cellKey === selectedCellKey && draftState.sourceFormula === selectedCell.formula ? draftState.value : selectedCell.formula;
43749
+ const setDraft = (value) => {
43750
+ setDraftState({
43751
+ cellKey: selectedCellKey,
43752
+ sourceFormula: selectedCell.formula,
43753
+ value
43754
+ });
43011
43755
  };
43012
- }
43013
- function beginFormulaRangePick(view, event) {
43014
- if (event.button !== 0) return null;
43015
- const formulaCell = findSelectionFormulaCell(view);
43016
- if (!formulaCell) return null;
43017
- const picked = getPickedCellLabel(view, event.target, formulaCell.tablePos);
43018
- if (!picked || picked.cell === formulaCell.cellDom) return null;
43019
- const { from, to } = view.state.selection;
43020
- let tr = view.state.tr.insertText(picked.label, from, to);
43021
- tr = tr.setSelection(import_state10.TextSelection.create(tr.doc, from + picked.label.length));
43022
- view.dispatch(tr);
43023
- view.focus();
43024
- event.preventDefault();
43025
- event.stopPropagation();
43026
- return {
43027
- anchorLabel: picked.label,
43028
- anchorCell: picked.cell,
43029
- tablePos: formulaCell.tablePos,
43030
- insertedFrom: from,
43031
- insertedTo: from + picked.label.length,
43032
- currentLabel: picked.label,
43033
- currentCell: picked.cell
43756
+ const applyDraft = () => {
43757
+ const normalized = normalizeFormulaInput(draft);
43758
+ if (normalized) {
43759
+ setSelectedTableCellFormula(editor, normalized);
43760
+ } else {
43761
+ clearSelectedTableCellFormula(editor);
43762
+ }
43763
+ setDraftState(null);
43034
43764
  };
43035
- }
43036
- function updateFormulaRangePick(view, pickState, event) {
43037
- const picked = getPickedCellLabel(view, event.target, pickState.tablePos);
43038
- if (!picked) return pickState;
43039
- event.preventDefault();
43040
- event.stopPropagation();
43041
- return replacePickedLabel(
43042
- view,
43043
- pickState,
43044
- normalizeFormulaRangeLabel(pickState.anchorLabel, picked.label),
43045
- picked.cell
43046
- );
43047
- }
43048
- function getFormulaRangePickHighlight(container, pickState) {
43049
- if (!container.contains(pickState.anchorCell) || !container.contains(pickState.currentCell)) return null;
43050
- const containerRect = container.getBoundingClientRect();
43051
- const anchorRect = pickState.anchorCell.getBoundingClientRect();
43052
- const currentRect = pickState.currentCell.getBoundingClientRect();
43053
- const left = Math.min(anchorRect.left, currentRect.left) - containerRect.left + container.scrollLeft;
43054
- const top = Math.min(anchorRect.top, currentRect.top) - containerRect.top + container.scrollTop;
43055
- const right = Math.max(anchorRect.right, currentRect.right) - containerRect.left + container.scrollLeft;
43056
- const bottom = Math.max(anchorRect.bottom, currentRect.bottom) - containerRect.top + container.scrollTop;
43057
- return {
43058
- left,
43059
- top,
43060
- width: Math.max(0, right - left),
43061
- height: Math.max(0, bottom - top)
43765
+ const cancelDraft = () => {
43766
+ setDraftState(null);
43767
+ window.setTimeout(() => {
43768
+ if (!editor.isDestroyed) editor.commands.focus();
43769
+ }, 0);
43062
43770
  };
43771
+ return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
43772
+ "div",
43773
+ {
43774
+ "data-ueditor-formula-bar": "",
43775
+ role: "toolbar",
43776
+ "aria-label": t("tableMenu.formulaBar"),
43777
+ className: "flex min-h-10 items-center gap-2 border-b border-border/60 bg-muted/25 px-2 py-1.5",
43778
+ children: [
43779
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43780
+ "span",
43781
+ {
43782
+ "data-ueditor-formula-cell-label": "",
43783
+ 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",
43784
+ children: selectedCell.label
43785
+ }
43786
+ ),
43787
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Sigma, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-primary" }),
43788
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43789
+ "input",
43790
+ {
43791
+ ref: inputRef,
43792
+ "data-ueditor-formula-input": "",
43793
+ value: draft,
43794
+ onChange: (event) => setDraft(event.target.value),
43795
+ onKeyDown: (event) => {
43796
+ event.stopPropagation();
43797
+ if (event.key === "Enter") {
43798
+ event.preventDefault();
43799
+ applyDraft();
43800
+ } else if (event.key === "Escape") {
43801
+ event.preventDefault();
43802
+ cancelDraft();
43803
+ }
43804
+ },
43805
+ "aria-label": t("tableMenu.editFormula"),
43806
+ 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"
43807
+ }
43808
+ ),
43809
+ selectedCell.formulaState === "error" && /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
43810
+ "span",
43811
+ {
43812
+ role: "status",
43813
+ className: "hidden shrink-0 items-center gap-1 text-xs font-medium text-destructive sm:inline-flex",
43814
+ children: [
43815
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.AlertCircle, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
43816
+ t("tableMenu.formulaError")
43817
+ ]
43818
+ }
43819
+ ),
43820
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43821
+ "button",
43822
+ {
43823
+ type: "button",
43824
+ onClick: applyDraft,
43825
+ "aria-label": t("tableMenu.apply"),
43826
+ title: t("tableMenu.apply"),
43827
+ 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",
43828
+ children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Check, { "aria-hidden": "true", className: "h-4 w-4" })
43829
+ }
43830
+ ),
43831
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43832
+ "button",
43833
+ {
43834
+ type: "button",
43835
+ onClick: () => {
43836
+ convertSelectedTableCellFormulaToValue(editor);
43837
+ setDraftState(null);
43838
+ },
43839
+ "aria-label": t("tableMenu.convertToValue"),
43840
+ title: t("tableMenu.convertToValue"),
43841
+ 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",
43842
+ children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Hash, { "aria-hidden": "true", className: "h-4 w-4" })
43843
+ }
43844
+ ),
43845
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43846
+ "button",
43847
+ {
43848
+ type: "button",
43849
+ onClick: () => {
43850
+ clearSelectedTableCellFormula(editor);
43851
+ setDraftState(null);
43852
+ },
43853
+ "aria-label": t("tableMenu.clearCell"),
43854
+ title: t("tableMenu.clearCell"),
43855
+ 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",
43856
+ children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Trash2, { "aria-hidden": "true", className: "h-4 w-4" })
43857
+ }
43858
+ )
43859
+ ]
43860
+ }
43861
+ );
43063
43862
  }
43064
43863
 
43065
43864
  // src/components/UEditor/UEditor.tsx
43066
- var import_jsx_runtime100 = require("react/jsx-runtime");
43067
- var UEditor = import_react79.default.forwardRef(({
43865
+ var import_jsx_runtime101 = require("react/jsx-runtime");
43866
+ var UEditor = import_react82.default.forwardRef(({
43068
43867
  content = "",
43069
43868
  onChange,
43070
43869
  onHtmlChange,
@@ -43082,6 +43881,7 @@ var UEditor = import_react79.default.forwardRef(({
43082
43881
  autofocus = false,
43083
43882
  showToolbar = true,
43084
43883
  showBubbleMenu = true,
43884
+ showFloatingMenu = false,
43085
43885
  showCharacterCount = true,
43086
43886
  showFooter = true,
43087
43887
  maxCharacters,
@@ -43105,13 +43905,14 @@ var UEditor = import_react79.default.forwardRef(({
43105
43905
  }, ref) => {
43106
43906
  const t = useSmartTranslations("UEditor");
43107
43907
  const effectivePlaceholder = placeholder ?? t("placeholder");
43108
- const inFlightPrepareRef = (0, import_react79.useRef)(null);
43109
- const lastAppliedContentRef = (0, import_react79.useRef)(content ?? "");
43110
- const scheduledFormulaRecalculateRef = (0, import_react79.useRef)(false);
43111
- const formulaRangePickRef = (0, import_react79.useRef)(null);
43112
- const formulaRangeSurfaceRef = (0, import_react79.useRef)(null);
43113
- const [formulaRangeHighlight, setFormulaRangeHighlight] = import_react79.default.useState(null);
43114
- const scheduleFormulaRecalculate = import_react79.default.useCallback((editor2, options) => {
43908
+ const inFlightPrepareRef = (0, import_react82.useRef)(null);
43909
+ const lastAppliedContentRef = (0, import_react82.useRef)(content ?? "");
43910
+ const scheduledFormulaRecalculateRef = (0, import_react82.useRef)(false);
43911
+ const editorInstanceRef = (0, import_react82.useRef)(null);
43912
+ const formulaRangePickRef = (0, import_react82.useRef)(null);
43913
+ const formulaRangeSurfaceRef = (0, import_react82.useRef)(null);
43914
+ const [formulaRangeHighlight, setFormulaRangeHighlight] = import_react82.default.useState(null);
43915
+ const scheduleFormulaRecalculate = import_react82.default.useCallback((editor2, options) => {
43115
43916
  if (editor2.isDestroyed || scheduledFormulaRecalculateRef.current) return;
43116
43917
  if (!options?.force && isEditingTableFormulaText(editor2)) return;
43117
43918
  scheduledFormulaRecalculateRef.current = true;
@@ -43122,7 +43923,7 @@ var UEditor = import_react79.default.forwardRef(({
43122
43923
  }
43123
43924
  });
43124
43925
  }, []);
43125
- const resolvedUploadFile = (0, import_react79.useMemo)(() => {
43926
+ const resolvedUploadFile = (0, import_react82.useMemo)(() => {
43126
43927
  if (uploadFile) return uploadFile;
43127
43928
  if (uploadFileForSave) {
43128
43929
  return async (file) => {
@@ -43132,7 +43933,7 @@ var UEditor = import_react79.default.forwardRef(({
43132
43933
  }
43133
43934
  return uploadImage;
43134
43935
  }, [uploadFile, uploadFileForSave, uploadImage]);
43135
- const extensions = (0, import_react79.useMemo)(
43936
+ const extensions = (0, import_react82.useMemo)(
43136
43937
  () => [
43137
43938
  ...buildUEditorExtensions({
43138
43939
  placeholder: effectivePlaceholder,
@@ -43151,11 +43952,11 @@ var UEditor = import_react79.default.forwardRef(({
43151
43952
  ],
43152
43953
  [effectivePlaceholder, t, maxCharacters, uploadImage, resolvedUploadFile, imageInsertMode, maxImageFileSize, allowedImageMimeTypes, fallbackToDataUrl, editable, fetchMetadata, extraExtensions]
43153
43954
  );
43154
- const syncFormulaRangeHighlight = import_react79.default.useCallback((pickState) => {
43955
+ const syncFormulaRangeHighlight = import_react82.default.useCallback((pickState) => {
43155
43956
  const container = formulaRangeSurfaceRef.current;
43156
43957
  setFormulaRangeHighlight(container && pickState ? getFormulaRangePickHighlight(container, pickState) : null);
43157
43958
  }, []);
43158
- const editor = (0, import_react80.useEditor)({
43959
+ const editor = (0, import_react83.useEditor)({
43159
43960
  immediatelyRender: false,
43160
43961
  extensions,
43161
43962
  content,
@@ -43196,6 +43997,29 @@ var UEditor = import_react79.default.forwardRef(({
43196
43997
  },
43197
43998
  keydown: (_view, event) => {
43198
43999
  if (!(event instanceof KeyboardEvent)) return false;
44000
+ const formulaContext = getFormulaEditingTableContext(_view);
44001
+ if (formulaContext && event.key === "Enter") {
44002
+ event.preventDefault();
44003
+ event.stopPropagation();
44004
+ const activeEditor2 = editorInstanceRef.current;
44005
+ if (activeEditor2 && !isDraftTableFormula(formulaContext.formula)) {
44006
+ recalculateActiveTableFormulas(activeEditor2);
44007
+ }
44008
+ return true;
44009
+ }
44010
+ if (formulaContext && event.key === "Escape") {
44011
+ event.preventDefault();
44012
+ event.stopPropagation();
44013
+ cancelFormulaEditing(_view);
44014
+ return true;
44015
+ }
44016
+ const activeEditor = editorInstanceRef.current;
44017
+ if (activeEditor && (event.key === "Backspace" || event.key === "Delete") && getSelectedTableFormulaCell(activeEditor)) {
44018
+ event.preventDefault();
44019
+ event.stopPropagation();
44020
+ clearSelectedTableCellFormula(activeEditor);
44021
+ return true;
44022
+ }
43199
44023
  if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
43200
44024
  event.stopPropagation();
43201
44025
  }
@@ -43206,7 +44030,7 @@ var UEditor = import_react79.default.forwardRef(({
43206
44030
  if (event.button !== 0) return false;
43207
44031
  const target = resolveEventElement(event.target);
43208
44032
  const anchor = target?.closest?.("a[href]");
43209
- const href = anchor?.getAttribute("href") ?? "";
44033
+ const href = sanitizeUEditorUrl(anchor?.getAttribute("href") ?? "", "link");
43210
44034
  if (!href) return false;
43211
44035
  if (editable) return false;
43212
44036
  if (!view.state.selection.empty) return false;
@@ -43232,17 +44056,29 @@ var UEditor = import_react79.default.forwardRef(({
43232
44056
  onSelectionUpdate: ({ editor: editor2 }) => {
43233
44057
  scheduleFormulaRecalculate(editor2);
43234
44058
  },
43235
- onBlur: ({ editor: editor2 }) => {
44059
+ onBlur: ({ editor: editor2, event }) => {
44060
+ const nextTarget = event.relatedTarget;
44061
+ if (nextTarget instanceof Element && nextTarget.closest("[data-ueditor-formula-bar]")) return;
43236
44062
  scheduleFormulaRecalculate(editor2, { force: true });
43237
44063
  }
43238
44064
  });
44065
+ (0, import_react82.useEffect)(() => {
44066
+ editorInstanceRef.current = editor;
44067
+ return () => {
44068
+ if (editorInstanceRef.current === editor) editorInstanceRef.current = null;
44069
+ };
44070
+ }, [editor]);
43239
44071
  const {
43240
44072
  editorContentRef,
43241
44073
  tableColumnGuideRef,
43242
44074
  tableRowGuideRef,
43243
44075
  activeTableCellHighlightRef
43244
44076
  } = useUEditorTableInteractions(editor, editable);
43245
- (0, import_react79.useImperativeHandle)(
44077
+ const formulaCoordinateOverlayRef = useFormulaCoordinateOverlay(editor, editorContentRef, {
44078
+ apply: t("tableMenu.apply"),
44079
+ cancel: t("imageInput.cancelBtn")
44080
+ });
44081
+ (0, import_react82.useImperativeHandle)(
43246
44082
  ref,
43247
44083
  () => ({
43248
44084
  editor,
@@ -43267,7 +44103,7 @@ var UEditor = import_react79.default.forwardRef(({
43267
44103
  }),
43268
44104
  [content, editor, uploadImageForSave, uploadFileForSave, uploadImageConcurrency]
43269
44105
  );
43270
- (0, import_react79.useEffect)(() => {
44106
+ (0, import_react82.useEffect)(() => {
43271
44107
  if (!editor) return;
43272
44108
  queueMicrotask(() => {
43273
44109
  if (!editor.isDestroyed) {
@@ -43275,7 +44111,7 @@ var UEditor = import_react79.default.forwardRef(({
43275
44111
  }
43276
44112
  });
43277
44113
  }, [editor]);
43278
- (0, import_react79.useEffect)(() => {
44114
+ (0, import_react82.useEffect)(() => {
43279
44115
  if (!editor) return;
43280
44116
  const nextContent = content ?? "";
43281
44117
  if (lastAppliedContentRef.current === nextContent) return;
@@ -43294,7 +44130,7 @@ var UEditor = import_react79.default.forwardRef(({
43294
44130
  }
43295
44131
  }, [content, editor]);
43296
44132
  if (!editor) {
43297
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44133
+ return /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43298
44134
  "div",
43299
44135
  {
43300
44136
  className: cn("w-full border bg-background flex items-center justify-center text-muted-foreground", className),
@@ -43303,7 +44139,7 @@ var UEditor = import_react79.default.forwardRef(({
43303
44139
  }
43304
44140
  );
43305
44141
  }
43306
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
44142
+ return /* @__PURE__ */ (0, import_jsx_runtime101.jsxs)(
43307
44143
  "div",
43308
44144
  {
43309
44145
  className: cn(
@@ -43319,7 +44155,7 @@ var UEditor = import_react79.default.forwardRef(({
43319
44155
  className
43320
44156
  ),
43321
44157
  children: [
43322
- editable && showMenuBar && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44158
+ editable && showMenuBar && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43323
44159
  MenuBar,
43324
44160
  {
43325
44161
  editor,
@@ -43334,7 +44170,7 @@ var UEditor = import_react79.default.forwardRef(({
43334
44170
  onPreview
43335
44171
  }
43336
44172
  ),
43337
- editable && showToolbar && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44173
+ editable && showToolbar && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43338
44174
  EditorToolbar,
43339
44175
  {
43340
44176
  editor,
@@ -43349,7 +44185,8 @@ var UEditor = import_react79.default.forwardRef(({
43349
44185
  letterSpacings
43350
44186
  }
43351
44187
  ),
43352
- editable && showBubbleMenu && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44188
+ editable && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(TableFormulaBar, { editor }),
44189
+ editable && showBubbleMenu && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43353
44190
  CustomBubbleMenu,
43354
44191
  {
43355
44192
  editor,
@@ -43357,7 +44194,8 @@ var UEditor = import_react79.default.forwardRef(({
43357
44194
  lineHeights
43358
44195
  }
43359
44196
  ),
43360
- /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
44197
+ editable && showFloatingMenu && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(CustomFloatingMenu, { editor }),
44198
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsxs)(
43361
44199
  "div",
43362
44200
  {
43363
44201
  ref: (node) => {
@@ -43370,7 +44208,7 @@ var UEditor = import_react79.default.forwardRef(({
43370
44208
  maxHeight
43371
44209
  },
43372
44210
  children: [
43373
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44211
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43374
44212
  "span",
43375
44213
  {
43376
44214
  ref: tableColumnGuideRef,
@@ -43378,7 +44216,7 @@ var UEditor = import_react79.default.forwardRef(({
43378
44216
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43379
44217
  }
43380
44218
  ),
43381
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44219
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43382
44220
  "span",
43383
44221
  {
43384
44222
  ref: tableRowGuideRef,
@@ -43386,7 +44224,7 @@ var UEditor = import_react79.default.forwardRef(({
43386
44224
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43387
44225
  }
43388
44226
  ),
43389
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44227
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43390
44228
  "span",
43391
44229
  {
43392
44230
  ref: activeTableCellHighlightRef,
@@ -43395,7 +44233,15 @@ var UEditor = import_react79.default.forwardRef(({
43395
44233
  className: "pointer-events-none hidden absolute z-20 rounded-[2px] border-2 border-primary bg-primary/10"
43396
44234
  }
43397
44235
  ),
43398
- formulaRangeHighlight && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44236
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
44237
+ "div",
44238
+ {
44239
+ ref: formulaCoordinateOverlayRef,
44240
+ "data-ueditor-formula-coordinate-overlay": "",
44241
+ className: "pointer-events-none absolute inset-0 z-[21] hidden overflow-visible"
44242
+ }
44243
+ ),
44244
+ formulaRangeHighlight && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43399
44245
  "span",
43400
44246
  {
43401
44247
  "aria-hidden": "true",
@@ -43409,9 +44255,9 @@ var UEditor = import_react79.default.forwardRef(({
43409
44255
  }
43410
44256
  }
43411
44257
  ),
43412
- editable && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(TableControls, { editor, containerRef: editorContentRef }),
43413
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43414
- import_react80.EditorContent,
44258
+ editable && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(TableControls, { editor, containerRef: editorContentRef }),
44259
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
44260
+ import_react83.EditorContent,
43415
44261
  {
43416
44262
  editor,
43417
44263
  className: "min-h-full"
@@ -43420,7 +44266,7 @@ var UEditor = import_react79.default.forwardRef(({
43420
44266
  ]
43421
44267
  }
43422
44268
  ),
43423
- showFooter && showCharacterCount && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(CharacterCountDisplay, { editor, maxCharacters })
44269
+ showFooter && showCharacterCount && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(CharacterCountDisplay, { editor, maxCharacters })
43424
44270
  ]
43425
44271
  }
43426
44272
  );