@underverse-ui/underverse 1.0.184 → 1.0.186

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_react80 = __toESM(require("react"), 1);
27552
- var import_react81 = 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;
@@ -30095,7 +30197,7 @@ var FormulaSuggestionList = (0, import_react62.forwardRef)((props, ref) => {
30095
30197
  setSelectedIndex((prev) => (prev + 1) % props.items.length);
30096
30198
  return true;
30097
30199
  }
30098
- if (event.key === "Enter" || event.key === "Tab") {
30200
+ if (event.key === "Tab") {
30099
30201
  const item = props.items[selectedIndex];
30100
30202
  if (item) {
30101
30203
  props.command(item);
@@ -30185,9 +30287,7 @@ var FormulaSuggestion = import_core9.Extension.create({
30185
30287
  if (!props.clientRect) {
30186
30288
  return;
30187
30289
  }
30188
- popup?.[0]?.setProps({
30189
- getReferenceClientRect: props.clientRect
30190
- });
30290
+ setTippyReferenceClientRect(getFirstTippyInstance(popup), props.clientRect);
30191
30291
  },
30192
30292
  onKeyDown(props) {
30193
30293
  const popupInstance = getFirstTippyInstance(popup);
@@ -31929,7 +32029,6 @@ function buildUEditorExtensions({
31929
32029
  import_extension_gapcursor.default,
31930
32030
  import_extension_link.default.configure({
31931
32031
  openOnClick: false,
31932
- protocols: ["http", "https", "mailto", "tel"],
31933
32032
  isAllowedUri: (url) => isSafeUEditorUrl(url ?? "", "link"),
31934
32033
  HTMLAttributes: {
31935
32034
  class: "text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer"
@@ -32981,7 +33080,7 @@ var EditorToolbar = ({
32981
33080
  const [showImageInput, setShowImageInput] = (0, import_react70.useState)(false);
32982
33081
  const [showLinkInput, setShowLinkInput] = (0, import_react70.useState)(false);
32983
33082
  const [isTableMenuOpen, setIsTableMenuOpen] = (0, import_react70.useState)(false);
32984
- const tableCommandAnchorPosRef = (0, import_react70.useRef)(null);
33083
+ const [preservedTableAnchorPos, setPreservedTableAnchorPos] = (0, import_react70.useState)(null);
32985
33084
  const fileInputRef = (0, import_react70.useRef)(null);
32986
33085
  const [isUploadingImage, setIsUploadingImage] = (0, import_react70.useState)(false);
32987
33086
  const [imageUploadError, setImageUploadError] = (0, import_react70.useState)(null);
@@ -32996,7 +33095,7 @@ var EditorToolbar = ({
32996
33095
  const tableAlignAttr = tableAttrs.tableAlign ?? tableAttrs.textAlign;
32997
33096
  const currentTableAlign = tableAlignAttr === "center" || tableAlignAttr === "right" ? tableAlignAttr : "left";
32998
33097
  const isTableSelected = tableInfo !== null;
32999
- const hasTableContext = isTableSelected || tableCommandAnchorPosRef.current !== null;
33098
+ const hasTableContext = isTableSelected || preservedTableAnchorPos !== null;
33000
33099
  const canMergeCells = hasTableContext && editor.can().mergeCells();
33001
33100
  const canSplitCell = hasTableContext && editor.can().splitCell();
33002
33101
  const currentCellVerticalAlign = normalizeStyleValue(editor.getAttributes("tableCell").verticalAlign || editor.getAttributes("tableHeader").verticalAlign) || "";
@@ -33021,7 +33120,7 @@ var EditorToolbar = ({
33021
33120
  const displayedFontFamilyValue = currentFontFamily || defaultFontFamilyValue;
33022
33121
  const displayedFontSizeLabel = currentFontSize ? currentFontSizeLabel : "13";
33023
33122
  const activeFontSize = currentFontSize || "13px";
33024
- const tableCommandAnchorPos = tableCommandAnchorPosRef.current ?? tableAnchorPos ?? void 0;
33123
+ const tableCommandAnchorPos = preservedTableAnchorPos ?? tableAnchorPos ?? void 0;
33025
33124
  const isMedium = variant === "medium";
33026
33125
  const isMediumFull = variant === "medium-full";
33027
33126
  const isFull = variant === "default" || variant === "full" || variant === "notion" || !variant;
@@ -33697,7 +33796,7 @@ var EditorToolbar = ({
33697
33796
  isOpen: isTableMenuOpen,
33698
33797
  onOpenChange: (open) => {
33699
33798
  setIsTableMenuOpen(open);
33700
- tableCommandAnchorPosRef.current = open ? getTableAnchorPos(editor) : null;
33799
+ setPreservedTableAnchorPos(open ? getTableAnchorPos(editor) : null);
33701
33800
  },
33702
33801
  trigger: /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(ToolbarButton, { onClick: () => {
33703
33802
  }, title: t("toolbar.table"), children: [
@@ -33714,6 +33813,7 @@ var EditorToolbar = ({
33714
33813
  onInsert: (rows, cols) => {
33715
33814
  editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
33716
33815
  setIsTableMenuOpen(false);
33816
+ setPreservedTableAnchorPos(null);
33717
33817
  }
33718
33818
  }
33719
33819
  ),
@@ -33904,6 +34004,7 @@ var import_react_dom8 = require("react-dom");
33904
34004
  var import_lucide_react54 = require("lucide-react");
33905
34005
 
33906
34006
  // src/components/UEditor/table-formula-commands.ts
34007
+ var import_state10 = require("@tiptap/pm/state");
33907
34008
  var import_tables5 = require("@tiptap/pm/tables");
33908
34009
 
33909
34010
  // src/components/UEditor/table-formula.ts
@@ -34467,6 +34568,75 @@ function getSelectionTableCellNode(editor) {
34467
34568
  }
34468
34569
  return null;
34469
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
+ }
34470
34640
  function getSelectionTableInfo(editor) {
34471
34641
  const { $from } = editor.state.selection;
34472
34642
  for (let depth = $from.depth; depth > 0; depth -= 1) {
@@ -34513,6 +34683,28 @@ function createCellDisplayContent(cellNode, displayValue) {
34513
34683
  }
34514
34684
  return [paragraphType.create(null, cellNode.type.schema.text(displayValue))];
34515
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
+ }
34516
34708
  function buildTableValueMap(tableNode) {
34517
34709
  const map = import_tables5.TableMap.get(tableNode);
34518
34710
  const values = /* @__PURE__ */ new Map();
@@ -34535,21 +34727,15 @@ function normalizeFormulaInput(formula) {
34535
34727
  function setSelectedTableCellFormula(editor, formula) {
34536
34728
  const normalized = normalizeFormulaInput(formula);
34537
34729
  const { state, view } = editor;
34730
+ const selectionAnchor = getSelectionTableCellAnchor(editor);
34538
34731
  if (!normalized) {
34539
- const clearedFormula = (0, import_tables5.setCellAttr)("formula", null)(state, view.dispatch.bind(view));
34540
- const clearedValue2 = (0, import_tables5.setCellAttr)("computedValue", null)(editor.state, view.dispatch.bind(view));
34541
- if (clearedFormula || clearedValue2) {
34542
- recalculateActiveTableFormulas(editor);
34543
- view.focus();
34544
- dispatchTableLayoutChange(editor);
34545
- return true;
34546
- }
34547
- return false;
34732
+ return clearSelectedTableCellFormula(editor);
34548
34733
  }
34549
34734
  const appliedFormula = (0, import_tables5.setCellAttr)("formula", normalized)(state, view.dispatch.bind(view));
34550
34735
  const clearedValue = (0, import_tables5.setCellAttr)("computedValue", null)(editor.state, view.dispatch.bind(view));
34551
34736
  if (appliedFormula || clearedValue) {
34552
34737
  recalculateActiveTableFormulas(editor);
34738
+ if (selectionAnchor) restoreSelectionAtTableCell(editor, selectionAnchor);
34553
34739
  view.focus();
34554
34740
  dispatchTableLayoutChange(editor);
34555
34741
  return true;
@@ -34557,7 +34743,10 @@ function setSelectedTableCellFormula(editor, formula) {
34557
34743
  return false;
34558
34744
  }
34559
34745
  function clearSelectedTableCellFormula(editor) {
34560
- return setSelectedTableCellFormula(editor, "");
34746
+ return removeSelectedTableCellFormula(editor, true);
34747
+ }
34748
+ function convertSelectedTableCellFormulaToValue(editor) {
34749
+ return removeSelectedTableCellFormula(editor, false);
34561
34750
  }
34562
34751
  function setSelectedTableCellNumberFormat(editor, numberFormat) {
34563
34752
  const value = numberFormat && numberFormat !== "text" ? numberFormat : null;
@@ -34757,6 +34946,58 @@ function recalculateAllTableFormulas(editor) {
34757
34946
 
34758
34947
  // src/components/UEditor/menus.tsx
34759
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
+ };
34760
35001
  function applyTableCellBackground(editor, color) {
34761
35002
  const value = color || null;
34762
35003
  const { state, view } = editor;
@@ -35495,7 +35736,8 @@ var LinkPreviewContent = ({ editor, onEdit }) => {
35495
35736
  const url = editor.getAttributes("link").href || "";
35496
35737
  const t = useSmartTranslations("UEditor");
35497
35738
  const handleOpen = () => {
35498
- window.open(url, "_blank", "noopener,noreferrer");
35739
+ const safeUrl = sanitizeUEditorUrl(url, "link");
35740
+ if (safeUrl) window.open(safeUrl, "_blank", "noopener,noreferrer");
35499
35741
  };
35500
35742
  const handleUnlink = () => {
35501
35743
  editor.chain().focus().unsetLink().run();
@@ -35525,16 +35767,19 @@ var CustomBubbleMenu = ({
35525
35767
  });
35526
35768
  const menuRef = (0, import_react72.useRef)(null);
35527
35769
  const keepOpenRef = (0, import_react72.useRef)(false);
35770
+ const [keepOpen, setKeepOpenState] = (0, import_react72.useState)(false);
35528
35771
  const showTimeoutRef = (0, import_react72.useRef)(null);
35529
35772
  const suppressShowUntilRef = (0, import_react72.useRef)(0);
35530
35773
  const setKeepOpen = (0, import_react72.useCallback)((next) => {
35531
35774
  keepOpenRef.current = next;
35775
+ setKeepOpenState(next);
35532
35776
  if (!next) setLinkInputOpen(false);
35533
35777
  if (next) setIsVisible(true);
35534
35778
  }, []);
35535
35779
  const closeBubbleMenu = (0, import_react72.useCallback)(() => {
35536
35780
  suppressShowUntilRef.current = Date.now() + 1e3;
35537
35781
  keepOpenRef.current = false;
35782
+ setKeepOpenState(false);
35538
35783
  setLinkInputOpen(false);
35539
35784
  setIsVisible(false);
35540
35785
  if (showTimeoutRef.current) {
@@ -35640,13 +35885,13 @@ var CustomBubbleMenu = ({
35640
35885
  onMouseDown: (e) => {
35641
35886
  const target = e.target;
35642
35887
  if (target?.closest?.("[data-ueditor-close-on-select]")) {
35643
- keepOpenRef.current = false;
35888
+ setKeepOpen(false);
35644
35889
  } else if (target?.closest?.("[data-ueditor-keep-open]")) {
35645
- keepOpenRef.current = true;
35890
+ setKeepOpen(true);
35646
35891
  }
35647
35892
  e.preventDefault();
35648
35893
  },
35649
- 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)(
35650
35895
  LinkPreviewContent,
35651
35896
  {
35652
35897
  editor,
@@ -35672,6 +35917,54 @@ var CustomBubbleMenu = ({
35672
35917
  document.body
35673
35918
  );
35674
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
+ };
35675
35968
 
35676
35969
  // src/components/UEditor/CharacterCount.tsx
35677
35970
  var import_jsx_runtime93 = require("react/jsx-runtime");
@@ -38747,7 +39040,7 @@ var Selection = class {
38747
39040
  found.
38748
39041
  */
38749
39042
  static findFrom($pos, dir, textOnly = false) {
38750
- 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);
38751
39044
  if (inner)
38752
39045
  return inner;
38753
39046
  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
@@ -38816,7 +39109,7 @@ var Selection = class {
38816
39109
  returns the bookmark for that.
38817
39110
  */
38818
39111
  getBookmark() {
38819
- return TextSelection3.between(this.$anchor, this.$head).getBookmark();
39112
+ return TextSelection4.between(this.$anchor, this.$head).getBookmark();
38820
39113
  }
38821
39114
  };
38822
39115
  Selection.prototype.visible = true;
@@ -38836,7 +39129,7 @@ function checkTextSelection($pos) {
38836
39129
  console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
38837
39130
  }
38838
39131
  }
38839
- var TextSelection3 = class _TextSelection extends Selection {
39132
+ var TextSelection4 = class _TextSelection extends Selection {
38840
39133
  /**
38841
39134
  Construct a text selection between the given points.
38842
39135
  */
@@ -38922,7 +39215,7 @@ var TextSelection3 = class _TextSelection extends Selection {
38922
39215
  return new _TextSelection($anchor, $head);
38923
39216
  }
38924
39217
  };
38925
- Selection.jsonID("text", TextSelection3);
39218
+ Selection.jsonID("text", TextSelection4);
38926
39219
  var TextBookmark = class _TextBookmark {
38927
39220
  constructor(anchor, head) {
38928
39221
  this.anchor = anchor;
@@ -38932,7 +39225,7 @@ var TextBookmark = class _TextBookmark {
38932
39225
  return new _TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
38933
39226
  }
38934
39227
  resolve(doc) {
38935
- return TextSelection3.between(doc.resolve(this.anchor), doc.resolve(this.head));
39228
+ return TextSelection4.between(doc.resolve(this.anchor), doc.resolve(this.head));
38936
39229
  }
38937
39230
  };
38938
39231
  var NodeSelection2 = class _NodeSelection extends Selection {
@@ -39051,7 +39344,7 @@ var AllBookmark = {
39051
39344
  };
39052
39345
  function findSelectionIn(doc, node, pos, index, dir, text = false) {
39053
39346
  if (node.inlineContent)
39054
- return TextSelection3.create(doc, pos);
39347
+ return TextSelection4.create(doc, pos);
39055
39348
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
39056
39349
  let child = node.child(i);
39057
39350
  if (!child.isAtom) {
@@ -39643,7 +39936,7 @@ var CellSelection = class CellSelection2 extends Selection {
39643
39936
  else if (tableChanged && this.isColSelection()) return CellSelection2.colSelection($anchorCell, $headCell);
39644
39937
  else return new CellSelection2($anchorCell, $headCell);
39645
39938
  }
39646
- return TextSelection3.between($anchorCell, $headCell);
39939
+ return TextSelection4.between($anchorCell, $headCell);
39647
39940
  }
39648
39941
  content() {
39649
39942
  const table = this.$anchorCell.node(-1);
@@ -40258,7 +40551,7 @@ function shiftArrow(axis, dir) {
40258
40551
  };
40259
40552
  }
40260
40553
  function atEndOfCell(view, axis, dir) {
40261
- if (!(view.state.selection instanceof TextSelection3)) return null;
40554
+ if (!(view.state.selection instanceof TextSelection4)) return null;
40262
40555
  const { $head } = view.state.selection;
40263
40556
  for (let d = $head.depth - 1; d >= 0; d--) {
40264
40557
  const parent = $head.node(d);
@@ -41378,7 +41671,7 @@ function TableControls({ editor, containerRef }) {
41378
41671
  destructive: true
41379
41672
  }
41380
41673
  ];
41381
- }, [layout, runAtActiveCell, t]);
41674
+ }, [editor, layout, runAtActiveCell, t]);
41382
41675
  const getRowHandleMenuItems = import_react74.default.useCallback((rowHandle) => [
41383
41676
  {
41384
41677
  label: t("tableMenu.addRowBefore"),
@@ -41594,6 +41887,42 @@ var UEDITOR_PROSEMIRROR_CLASS_NAME = cn(
41594
41887
  "[&_th]:px-2",
41595
41888
  "[&_th]:py-0",
41596
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",
41597
41926
  "[&_td[colwidth]]:min-w-0",
41598
41927
  "[&_th[colwidth]]:min-w-0",
41599
41928
  "[&_td[data-colwidth]]:min-w-0",
@@ -42099,7 +42428,7 @@ function useUEditorTableInteractions(editor, editable = true) {
42099
42428
  clearHoveredTableCell();
42100
42429
  clearAllTableResizeHover();
42101
42430
  };
42102
- }, [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]);
42103
42432
  return {
42104
42433
  editorContentRef,
42105
42434
  tableColumnGuideRef,
@@ -42113,7 +42442,7 @@ var import_react77 = require("react");
42113
42442
  var import_tables8 = require("@tiptap/pm/tables");
42114
42443
 
42115
42444
  // src/components/UEditor/table-formula-range-picker.ts
42116
- var import_state10 = require("@tiptap/pm/state");
42445
+ var import_state11 = require("@tiptap/pm/state");
42117
42446
  var import_tables7 = require("@tiptap/pm/tables");
42118
42447
  function getCellText2(cellNode) {
42119
42448
  return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
@@ -42132,6 +42461,9 @@ function getFormulaEditingTableContext(view) {
42132
42461
  if (!tableNode || tableNode.type.name !== "table") return null;
42133
42462
  const tableDom = cellDom.closest("table");
42134
42463
  if (!(tableDom instanceof HTMLTableElement)) return null;
42464
+ const tableMap = import_tables7.TableMap.get(tableNode);
42465
+ const tableStart = $from.start(tableDepth);
42466
+ const formulaCellRect = tableMap.findCell(cellPos - tableStart);
42135
42467
  return {
42136
42468
  cellContentEnd: $from.end(depth),
42137
42469
  cellContentStart: $from.start(depth),
@@ -42139,17 +42471,56 @@ function getFormulaEditingTableContext(view) {
42139
42471
  formula: getCellText2(node),
42140
42472
  tableDom,
42141
42473
  tableNode,
42142
- tablePos: $from.before(tableDepth)
42474
+ tablePos: $from.before(tableDepth),
42475
+ formulaCellLabel: `${indexToColumnName(formulaCellRect.left)}${formulaCellRect.top + 1}`
42143
42476
  };
42144
42477
  }
42145
42478
  return null;
42146
42479
  }
42480
+ function collectTableFormulaCells(tableNode) {
42481
+ const tableMap = import_tables7.TableMap.get(tableNode);
42482
+ const formulas = [];
42483
+ tableNode.forEach((rowNode, rowOffset) => {
42484
+ rowNode.forEach((cellNode, cellOffset) => {
42485
+ const formula = typeof cellNode.attrs.formula === "string" ? cellNode.attrs.formula.trim() : "";
42486
+ if (!formula) return;
42487
+ const rect = tableMap.findCell(rowOffset + 1 + cellOffset);
42488
+ formulas.push({
42489
+ label: `${indexToColumnName(rect.left)}${rect.top + 1}`,
42490
+ formula
42491
+ });
42492
+ });
42493
+ });
42494
+ return formulas;
42495
+ }
42496
+ function getFormulaCycleBlockedLabels(tableNode, formulaCellLabel) {
42497
+ const graph = buildTableFormulaDependencyGraph(collectTableFormulaCells(tableNode));
42498
+ const blocked = /* @__PURE__ */ new Set();
42499
+ const queue = [formulaCellLabel.toUpperCase()];
42500
+ for (let index = 0; index < queue.length; index += 1) {
42501
+ const label = queue[index];
42502
+ if (!label || blocked.has(label)) continue;
42503
+ blocked.add(label);
42504
+ for (const dependent of graph.dependents.get(label) ?? []) {
42505
+ if (!blocked.has(dependent)) queue.push(dependent);
42506
+ }
42507
+ }
42508
+ return blocked;
42509
+ }
42510
+ function getReferenceLabels(reference) {
42511
+ const range = parseTableCellRange(reference);
42512
+ return range ? getTableCellRangeLabels(range) : [reference.toUpperCase()];
42513
+ }
42514
+ function isBlockedFormulaReference(tableNode, formulaCellLabel, reference) {
42515
+ const blockedLabels = getFormulaCycleBlockedLabels(tableNode, formulaCellLabel);
42516
+ return getReferenceLabels(reference).some((label) => blockedLabels.has(label));
42517
+ }
42147
42518
  function cancelFormulaEditing(view) {
42148
42519
  const context = getFormulaEditingTableContext(view);
42149
42520
  if (!context) return false;
42150
42521
  let tr = view.state.tr.delete(context.cellContentStart, context.cellContentEnd);
42151
42522
  const selectionPos = Math.min(context.cellContentStart, tr.doc.content.size);
42152
- tr = tr.setSelection(import_state10.TextSelection.near(tr.doc.resolve(selectionPos)));
42523
+ tr = tr.setSelection(import_state11.TextSelection.near(tr.doc.resolve(selectionPos)));
42153
42524
  view.dispatch(tr);
42154
42525
  view.focus();
42155
42526
  return true;
@@ -42207,19 +42578,30 @@ function normalizeFormulaRangeLabel(fromLabel, toLabel) {
42207
42578
  return fromLabel === toLabel ? fromLabel : `${fromLabel}:${toLabel}`;
42208
42579
  }
42209
42580
  function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42210
- if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell) return pickState;
42581
+ const formulaContext = getFormulaEditingTableContext(view);
42582
+ const blocked = formulaContext ? isBlockedFormulaReference(formulaContext.tableNode, pickState.formulaCellLabel, nextLabel) : false;
42583
+ if (blocked) {
42584
+ return {
42585
+ ...pickState,
42586
+ blocked: true,
42587
+ currentCell
42588
+ };
42589
+ }
42590
+ if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell && !pickState.blocked) return pickState;
42211
42591
  if (nextLabel === pickState.currentLabel) {
42212
42592
  return {
42213
42593
  ...pickState,
42594
+ blocked: false,
42214
42595
  currentCell
42215
42596
  };
42216
42597
  }
42217
42598
  let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42218
42599
  const nextTo = pickState.insertedFrom + nextLabel.length;
42219
- tr = tr.setSelection(import_state10.TextSelection.create(tr.doc, nextTo));
42600
+ tr = tr.setSelection(import_state11.TextSelection.create(tr.doc, nextTo));
42220
42601
  view.dispatch(tr);
42221
42602
  return {
42222
42603
  ...pickState,
42604
+ blocked: false,
42223
42605
  insertedTo: nextTo,
42224
42606
  currentLabel: nextLabel,
42225
42607
  currentCell
@@ -42231,12 +42613,28 @@ function beginFormulaRangePick(view, event) {
42231
42613
  if (!formulaCell) return null;
42232
42614
  const picked = getFormulaTableCellInfo(view, event.target, formulaCell.tablePos);
42233
42615
  if (!picked || picked.cell === formulaCell.cellDom) return null;
42616
+ const blocked = isBlockedFormulaReference(formulaCell.tableNode, formulaCell.formulaCellLabel, picked.label);
42617
+ if (blocked) {
42618
+ event.preventDefault();
42619
+ event.stopPropagation();
42620
+ return {
42621
+ anchorLabel: picked.label,
42622
+ anchorCell: picked.cell,
42623
+ tablePos: formulaCell.tablePos,
42624
+ insertedFrom: view.state.selection.from,
42625
+ insertedTo: view.state.selection.from,
42626
+ currentLabel: "",
42627
+ currentCell: picked.cell,
42628
+ blocked: true,
42629
+ formulaCellLabel: formulaCell.formulaCellLabel
42630
+ };
42631
+ }
42234
42632
  const { from, to } = view.state.selection;
42235
42633
  const prefix = from === to ? getReferenceInsertionPrefix(view, formulaCell.cellContentStart, from) : "";
42236
42634
  const insertedText = `${prefix}${picked.label}`;
42237
42635
  const insertedFrom = from + prefix.length;
42238
42636
  let tr = view.state.tr.insertText(insertedText, from, to);
42239
- tr = tr.setSelection(import_state10.TextSelection.create(tr.doc, from + insertedText.length));
42637
+ tr = tr.setSelection(import_state11.TextSelection.create(tr.doc, from + insertedText.length));
42240
42638
  view.dispatch(tr);
42241
42639
  view.focus();
42242
42640
  event.preventDefault();
@@ -42248,7 +42646,9 @@ function beginFormulaRangePick(view, event) {
42248
42646
  insertedFrom,
42249
42647
  insertedTo: insertedFrom + picked.label.length,
42250
42648
  currentLabel: picked.label,
42251
- currentCell: picked.cell
42649
+ currentCell: picked.cell,
42650
+ blocked: false,
42651
+ formulaCellLabel: formulaCell.formulaCellLabel
42252
42652
  };
42253
42653
  }
42254
42654
  function updateFormulaRangePick(view, pickState, event) {
@@ -42276,7 +42676,8 @@ function getFormulaRangePickHighlight(container, pickState) {
42276
42676
  left,
42277
42677
  top,
42278
42678
  width: Math.max(0, right - left),
42279
- height: Math.max(0, bottom - top)
42679
+ height: Math.max(0, bottom - top),
42680
+ blocked: pickState.blocked
42280
42681
  };
42281
42682
  }
42282
42683
 
@@ -42300,6 +42701,12 @@ function createReferenceHighlight(label) {
42300
42701
  element.className = "pointer-events-none absolute z-[21] rounded-[2px] border-2 border-emerald-500 bg-emerald-500/15";
42301
42702
  return element;
42302
42703
  }
42704
+ function createBlockedReferenceHighlight(label) {
42705
+ const element = document.createElement("span");
42706
+ element.dataset.ueditorFormulaBlockedReference = label;
42707
+ element.className = "pointer-events-none absolute z-[22] rounded-[2px] border-2 border-destructive/80 bg-destructive/10";
42708
+ return element;
42709
+ }
42303
42710
  function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42304
42711
  const overlayRef = (0, import_react77.useRef)(null);
42305
42712
  (0, import_react77.useEffect)(() => {
@@ -42311,6 +42718,7 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42311
42718
  let activeTable = null;
42312
42719
  let activeTablePos = null;
42313
42720
  let hoverLabel = null;
42721
+ const scrollListenerOptions = { passive: true, capture: true };
42314
42722
  const clearOverlay = () => {
42315
42723
  activeTable = null;
42316
42724
  activeTablePos = null;
@@ -42370,10 +42778,12 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42370
42778
  actions.dataset.ueditorFormulaActions = "";
42371
42779
  actions.className = "pointer-events-auto absolute z-50 flex items-center gap-1 rounded-md border border-border bg-background p-1 shadow-md";
42372
42780
  const formulaCellRect = context.cellDom.getBoundingClientRect();
42781
+ const formulaActionsHeight = 34;
42782
+ const formulaActionsTop = formulaCellRect.bottom + 4 + formulaActionsHeight > tableRect.bottom ? Math.max(containerRect.top + 4, formulaCellRect.top - formulaActionsHeight - 4) : formulaCellRect.bottom + 4;
42373
42783
  setPosition(
42374
42784
  actions,
42375
42785
  formulaCellRect.left - containerRect.left + container.scrollLeft,
42376
- formulaCellRect.bottom - containerRect.top + container.scrollTop + 4
42786
+ formulaActionsTop - containerRect.top + container.scrollTop
42377
42787
  );
42378
42788
  const applyButton = document.createElement("button");
42379
42789
  applyButton.type = "button";
@@ -42425,10 +42835,12 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42425
42835
  children.push(label);
42426
42836
  }
42427
42837
  const references = new Set(getTableFormulaReferences(context.formula));
42838
+ const blockedReferences = getFormulaCycleBlockedLabels(context.tableNode, context.formulaCellLabel);
42428
42839
  for (const info of cellInfos) {
42429
- if (!info || !references.has(info.label)) continue;
42840
+ if (!info) continue;
42430
42841
  const cellRect = info.cell.getBoundingClientRect();
42431
- const highlight = createReferenceHighlight(info.label);
42842
+ const highlight = info.label !== context.formulaCellLabel && blockedReferences.has(info.label) ? createBlockedReferenceHighlight(info.label) : references.has(info.label) ? createReferenceHighlight(info.label) : null;
42843
+ if (!highlight) continue;
42432
42844
  setPosition(
42433
42845
  highlight,
42434
42846
  cellRect.left - containerRect.left + container.scrollLeft + 2,
@@ -42483,6 +42895,7 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42483
42895
  editor.view.dom.addEventListener("mousemove", handleMouseMove2);
42484
42896
  editor.view.dom.addEventListener("mouseleave", handleMouseLeave2);
42485
42897
  container.addEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42898
+ container.addEventListener("scroll", scheduleSync, scrollListenerOptions);
42486
42899
  window.addEventListener("resize", scheduleSync);
42487
42900
  scheduleSync();
42488
42901
  return () => {
@@ -42493,6 +42906,7 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42493
42906
  editor.view.dom.removeEventListener("mousemove", handleMouseMove2);
42494
42907
  editor.view.dom.removeEventListener("mouseleave", handleMouseLeave2);
42495
42908
  container.removeEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42909
+ container.removeEventListener("scroll", scheduleSync, scrollListenerOptions);
42496
42910
  window.removeEventListener("resize", scheduleSync);
42497
42911
  if (animationFrame != null) window.cancelAnimationFrame(animationFrame);
42498
42912
  clearOverlay();
@@ -43385,9 +43799,156 @@ var MenuBar = ({
43385
43799
  ] });
43386
43800
  };
43387
43801
 
43388
- // src/components/UEditor/UEditor.tsx
43802
+ // src/components/UEditor/table-formula-bar.tsx
43803
+ var import_react80 = require("react");
43804
+ var import_react81 = require("@tiptap/react");
43805
+ var import_lucide_react59 = require("lucide-react");
43389
43806
  var import_jsx_runtime100 = require("react/jsx-runtime");
43390
- var UEditor = import_react80.default.forwardRef(({
43807
+ function TableFormulaBar({ editor }) {
43808
+ const t = useSmartTranslations("UEditor");
43809
+ const inputRef = (0, import_react80.useRef)(null);
43810
+ const [draftState, setDraftState] = (0, import_react80.useState)(null);
43811
+ (0, import_react81.useEditorState)({
43812
+ editor,
43813
+ selector: ({ transactionNumber }) => transactionNumber
43814
+ });
43815
+ const selectedCell = getSelectedTableFormulaCell(editor);
43816
+ (0, import_react80.useEffect)(() => {
43817
+ const focusFormulaInput = () => {
43818
+ inputRef.current?.focus();
43819
+ };
43820
+ const handleDoubleClick = (event) => {
43821
+ const target = resolveEventElement(event.target);
43822
+ if (!target?.closest?.("td[data-formula],th[data-formula]")) return;
43823
+ event.preventDefault();
43824
+ focusFormulaInput();
43825
+ };
43826
+ editor.view.dom.addEventListener("dblclick", handleDoubleClick);
43827
+ return () => {
43828
+ editor.view.dom.removeEventListener("dblclick", handleDoubleClick);
43829
+ };
43830
+ }, [editor]);
43831
+ if (!selectedCell) return null;
43832
+ const selectedCellKey = `${selectedCell.tablePos}:${selectedCell.row}:${selectedCell.column}`;
43833
+ const draft = draftState?.cellKey === selectedCellKey && draftState.sourceFormula === selectedCell.formula ? draftState.value : selectedCell.formula;
43834
+ const setDraft = (value) => {
43835
+ setDraftState({
43836
+ cellKey: selectedCellKey,
43837
+ sourceFormula: selectedCell.formula,
43838
+ value
43839
+ });
43840
+ };
43841
+ const applyDraft = () => {
43842
+ const normalized = normalizeFormulaInput(draft);
43843
+ if (normalized) {
43844
+ setSelectedTableCellFormula(editor, normalized);
43845
+ } else {
43846
+ clearSelectedTableCellFormula(editor);
43847
+ }
43848
+ setDraftState(null);
43849
+ };
43850
+ const cancelDraft = () => {
43851
+ setDraftState(null);
43852
+ window.setTimeout(() => {
43853
+ if (!editor.isDestroyed) editor.commands.focus();
43854
+ }, 0);
43855
+ };
43856
+ return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
43857
+ "div",
43858
+ {
43859
+ "data-ueditor-formula-bar": "",
43860
+ role: "toolbar",
43861
+ "aria-label": t("tableMenu.formulaBar"),
43862
+ className: "flex min-h-10 items-center gap-2 border-b border-border/60 bg-muted/25 px-2 py-1.5",
43863
+ children: [
43864
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43865
+ "span",
43866
+ {
43867
+ "data-ueditor-formula-cell-label": "",
43868
+ 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",
43869
+ children: selectedCell.label
43870
+ }
43871
+ ),
43872
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Sigma, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-primary" }),
43873
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43874
+ "input",
43875
+ {
43876
+ ref: inputRef,
43877
+ "data-ueditor-formula-input": "",
43878
+ value: draft,
43879
+ onChange: (event) => setDraft(event.target.value),
43880
+ onKeyDown: (event) => {
43881
+ event.stopPropagation();
43882
+ if (event.key === "Enter") {
43883
+ event.preventDefault();
43884
+ applyDraft();
43885
+ } else if (event.key === "Escape") {
43886
+ event.preventDefault();
43887
+ cancelDraft();
43888
+ }
43889
+ },
43890
+ "aria-label": t("tableMenu.editFormula"),
43891
+ 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"
43892
+ }
43893
+ ),
43894
+ selectedCell.formulaState === "error" && /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
43895
+ "span",
43896
+ {
43897
+ role: "status",
43898
+ className: "hidden shrink-0 items-center gap-1 text-xs font-medium text-destructive sm:inline-flex",
43899
+ children: [
43900
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.AlertCircle, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
43901
+ t("tableMenu.formulaError")
43902
+ ]
43903
+ }
43904
+ ),
43905
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43906
+ "button",
43907
+ {
43908
+ type: "button",
43909
+ onClick: applyDraft,
43910
+ "aria-label": t("tableMenu.apply"),
43911
+ title: t("tableMenu.apply"),
43912
+ 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",
43913
+ children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Check, { "aria-hidden": "true", className: "h-4 w-4" })
43914
+ }
43915
+ ),
43916
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43917
+ "button",
43918
+ {
43919
+ type: "button",
43920
+ onClick: () => {
43921
+ convertSelectedTableCellFormulaToValue(editor);
43922
+ setDraftState(null);
43923
+ },
43924
+ "aria-label": t("tableMenu.convertToValue"),
43925
+ title: t("tableMenu.convertToValue"),
43926
+ 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",
43927
+ children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Hash, { "aria-hidden": "true", className: "h-4 w-4" })
43928
+ }
43929
+ ),
43930
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43931
+ "button",
43932
+ {
43933
+ type: "button",
43934
+ onClick: () => {
43935
+ clearSelectedTableCellFormula(editor);
43936
+ setDraftState(null);
43937
+ },
43938
+ "aria-label": t("tableMenu.clearCell"),
43939
+ title: t("tableMenu.clearCell"),
43940
+ 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",
43941
+ children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(import_lucide_react59.Trash2, { "aria-hidden": "true", className: "h-4 w-4" })
43942
+ }
43943
+ )
43944
+ ]
43945
+ }
43946
+ );
43947
+ }
43948
+
43949
+ // src/components/UEditor/UEditor.tsx
43950
+ var import_jsx_runtime101 = require("react/jsx-runtime");
43951
+ var UEditor = import_react82.default.forwardRef(({
43391
43952
  content = "",
43392
43953
  onChange,
43393
43954
  onHtmlChange,
@@ -43405,6 +43966,7 @@ var UEditor = import_react80.default.forwardRef(({
43405
43966
  autofocus = false,
43406
43967
  showToolbar = true,
43407
43968
  showBubbleMenu = true,
43969
+ showFloatingMenu = false,
43408
43970
  showCharacterCount = true,
43409
43971
  showFooter = true,
43410
43972
  maxCharacters,
@@ -43428,14 +43990,14 @@ var UEditor = import_react80.default.forwardRef(({
43428
43990
  }, ref) => {
43429
43991
  const t = useSmartTranslations("UEditor");
43430
43992
  const effectivePlaceholder = placeholder ?? t("placeholder");
43431
- const inFlightPrepareRef = (0, import_react80.useRef)(null);
43432
- const lastAppliedContentRef = (0, import_react80.useRef)(content ?? "");
43433
- const scheduledFormulaRecalculateRef = (0, import_react80.useRef)(false);
43434
- const editorInstanceRef = (0, import_react80.useRef)(null);
43435
- const formulaRangePickRef = (0, import_react80.useRef)(null);
43436
- const formulaRangeSurfaceRef = (0, import_react80.useRef)(null);
43437
- const [formulaRangeHighlight, setFormulaRangeHighlight] = import_react80.default.useState(null);
43438
- const scheduleFormulaRecalculate = import_react80.default.useCallback((editor2, options) => {
43993
+ const inFlightPrepareRef = (0, import_react82.useRef)(null);
43994
+ const lastAppliedContentRef = (0, import_react82.useRef)(content ?? "");
43995
+ const scheduledFormulaRecalculateRef = (0, import_react82.useRef)(false);
43996
+ const editorInstanceRef = (0, import_react82.useRef)(null);
43997
+ const formulaRangePickRef = (0, import_react82.useRef)(null);
43998
+ const formulaRangeSurfaceRef = (0, import_react82.useRef)(null);
43999
+ const [formulaRangeHighlight, setFormulaRangeHighlight] = import_react82.default.useState(null);
44000
+ const scheduleFormulaRecalculate = import_react82.default.useCallback((editor2, options) => {
43439
44001
  if (editor2.isDestroyed || scheduledFormulaRecalculateRef.current) return;
43440
44002
  if (!options?.force && isEditingTableFormulaText(editor2)) return;
43441
44003
  scheduledFormulaRecalculateRef.current = true;
@@ -43446,7 +44008,7 @@ var UEditor = import_react80.default.forwardRef(({
43446
44008
  }
43447
44009
  });
43448
44010
  }, []);
43449
- const resolvedUploadFile = (0, import_react80.useMemo)(() => {
44011
+ const resolvedUploadFile = (0, import_react82.useMemo)(() => {
43450
44012
  if (uploadFile) return uploadFile;
43451
44013
  if (uploadFileForSave) {
43452
44014
  return async (file) => {
@@ -43456,7 +44018,7 @@ var UEditor = import_react80.default.forwardRef(({
43456
44018
  }
43457
44019
  return uploadImage;
43458
44020
  }, [uploadFile, uploadFileForSave, uploadImage]);
43459
- const extensions = (0, import_react80.useMemo)(
44021
+ const extensions = (0, import_react82.useMemo)(
43460
44022
  () => [
43461
44023
  ...buildUEditorExtensions({
43462
44024
  placeholder: effectivePlaceholder,
@@ -43475,11 +44037,11 @@ var UEditor = import_react80.default.forwardRef(({
43475
44037
  ],
43476
44038
  [effectivePlaceholder, t, maxCharacters, uploadImage, resolvedUploadFile, imageInsertMode, maxImageFileSize, allowedImageMimeTypes, fallbackToDataUrl, editable, fetchMetadata, extraExtensions]
43477
44039
  );
43478
- const syncFormulaRangeHighlight = import_react80.default.useCallback((pickState) => {
44040
+ const syncFormulaRangeHighlight = import_react82.default.useCallback((pickState) => {
43479
44041
  const container = formulaRangeSurfaceRef.current;
43480
44042
  setFormulaRangeHighlight(container && pickState ? getFormulaRangePickHighlight(container, pickState) : null);
43481
44043
  }, []);
43482
- const editor = (0, import_react81.useEditor)({
44044
+ const editor = (0, import_react83.useEditor)({
43483
44045
  immediatelyRender: false,
43484
44046
  extensions,
43485
44047
  content,
@@ -43524,9 +44086,9 @@ var UEditor = import_react80.default.forwardRef(({
43524
44086
  if (formulaContext && event.key === "Enter") {
43525
44087
  event.preventDefault();
43526
44088
  event.stopPropagation();
43527
- const activeEditor = editorInstanceRef.current;
43528
- if (activeEditor && !isDraftTableFormula(formulaContext.formula)) {
43529
- recalculateActiveTableFormulas(activeEditor);
44089
+ const activeEditor2 = editorInstanceRef.current;
44090
+ if (activeEditor2 && !isDraftTableFormula(formulaContext.formula)) {
44091
+ recalculateActiveTableFormulas(activeEditor2);
43530
44092
  }
43531
44093
  return true;
43532
44094
  }
@@ -43536,6 +44098,13 @@ var UEditor = import_react80.default.forwardRef(({
43536
44098
  cancelFormulaEditing(_view);
43537
44099
  return true;
43538
44100
  }
44101
+ const activeEditor = editorInstanceRef.current;
44102
+ if (activeEditor && (event.key === "Backspace" || event.key === "Delete") && getSelectedTableFormulaCell(activeEditor)) {
44103
+ event.preventDefault();
44104
+ event.stopPropagation();
44105
+ clearSelectedTableCellFormula(activeEditor);
44106
+ return true;
44107
+ }
43539
44108
  if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
43540
44109
  event.stopPropagation();
43541
44110
  }
@@ -43546,7 +44115,7 @@ var UEditor = import_react80.default.forwardRef(({
43546
44115
  if (event.button !== 0) return false;
43547
44116
  const target = resolveEventElement(event.target);
43548
44117
  const anchor = target?.closest?.("a[href]");
43549
- const href = anchor?.getAttribute("href") ?? "";
44118
+ const href = sanitizeUEditorUrl(anchor?.getAttribute("href") ?? "", "link");
43550
44119
  if (!href) return false;
43551
44120
  if (editable) return false;
43552
44121
  if (!view.state.selection.empty) return false;
@@ -43572,11 +44141,13 @@ var UEditor = import_react80.default.forwardRef(({
43572
44141
  onSelectionUpdate: ({ editor: editor2 }) => {
43573
44142
  scheduleFormulaRecalculate(editor2);
43574
44143
  },
43575
- onBlur: ({ editor: editor2 }) => {
44144
+ onBlur: ({ editor: editor2, event }) => {
44145
+ const nextTarget = event.relatedTarget;
44146
+ if (nextTarget instanceof Element && nextTarget.closest("[data-ueditor-formula-bar]")) return;
43576
44147
  scheduleFormulaRecalculate(editor2, { force: true });
43577
44148
  }
43578
44149
  });
43579
- (0, import_react80.useEffect)(() => {
44150
+ (0, import_react82.useEffect)(() => {
43580
44151
  editorInstanceRef.current = editor;
43581
44152
  return () => {
43582
44153
  if (editorInstanceRef.current === editor) editorInstanceRef.current = null;
@@ -43592,7 +44163,7 @@ var UEditor = import_react80.default.forwardRef(({
43592
44163
  apply: t("tableMenu.apply"),
43593
44164
  cancel: t("imageInput.cancelBtn")
43594
44165
  });
43595
- (0, import_react80.useImperativeHandle)(
44166
+ (0, import_react82.useImperativeHandle)(
43596
44167
  ref,
43597
44168
  () => ({
43598
44169
  editor,
@@ -43617,7 +44188,7 @@ var UEditor = import_react80.default.forwardRef(({
43617
44188
  }),
43618
44189
  [content, editor, uploadImageForSave, uploadFileForSave, uploadImageConcurrency]
43619
44190
  );
43620
- (0, import_react80.useEffect)(() => {
44191
+ (0, import_react82.useEffect)(() => {
43621
44192
  if (!editor) return;
43622
44193
  queueMicrotask(() => {
43623
44194
  if (!editor.isDestroyed) {
@@ -43625,7 +44196,7 @@ var UEditor = import_react80.default.forwardRef(({
43625
44196
  }
43626
44197
  });
43627
44198
  }, [editor]);
43628
- (0, import_react80.useEffect)(() => {
44199
+ (0, import_react82.useEffect)(() => {
43629
44200
  if (!editor) return;
43630
44201
  const nextContent = content ?? "";
43631
44202
  if (lastAppliedContentRef.current === nextContent) return;
@@ -43644,7 +44215,7 @@ var UEditor = import_react80.default.forwardRef(({
43644
44215
  }
43645
44216
  }, [content, editor]);
43646
44217
  if (!editor) {
43647
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44218
+ return /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43648
44219
  "div",
43649
44220
  {
43650
44221
  className: cn("w-full border bg-background flex items-center justify-center text-muted-foreground", className),
@@ -43653,7 +44224,7 @@ var UEditor = import_react80.default.forwardRef(({
43653
44224
  }
43654
44225
  );
43655
44226
  }
43656
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
44227
+ return /* @__PURE__ */ (0, import_jsx_runtime101.jsxs)(
43657
44228
  "div",
43658
44229
  {
43659
44230
  className: cn(
@@ -43669,7 +44240,7 @@ var UEditor = import_react80.default.forwardRef(({
43669
44240
  className
43670
44241
  ),
43671
44242
  children: [
43672
- editable && showMenuBar && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44243
+ editable && showMenuBar && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43673
44244
  MenuBar,
43674
44245
  {
43675
44246
  editor,
@@ -43684,7 +44255,7 @@ var UEditor = import_react80.default.forwardRef(({
43684
44255
  onPreview
43685
44256
  }
43686
44257
  ),
43687
- editable && showToolbar && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44258
+ editable && showToolbar && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43688
44259
  EditorToolbar,
43689
44260
  {
43690
44261
  editor,
@@ -43699,7 +44270,8 @@ var UEditor = import_react80.default.forwardRef(({
43699
44270
  letterSpacings
43700
44271
  }
43701
44272
  ),
43702
- editable && showBubbleMenu && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44273
+ editable && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(TableFormulaBar, { editor }),
44274
+ editable && showBubbleMenu && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43703
44275
  CustomBubbleMenu,
43704
44276
  {
43705
44277
  editor,
@@ -43707,7 +44279,8 @@ var UEditor = import_react80.default.forwardRef(({
43707
44279
  lineHeights
43708
44280
  }
43709
44281
  ),
43710
- /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
44282
+ editable && showFloatingMenu && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(CustomFloatingMenu, { editor }),
44283
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsxs)(
43711
44284
  "div",
43712
44285
  {
43713
44286
  ref: (node) => {
@@ -43720,7 +44293,7 @@ var UEditor = import_react80.default.forwardRef(({
43720
44293
  maxHeight
43721
44294
  },
43722
44295
  children: [
43723
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44296
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43724
44297
  "span",
43725
44298
  {
43726
44299
  ref: tableColumnGuideRef,
@@ -43728,7 +44301,7 @@ var UEditor = import_react80.default.forwardRef(({
43728
44301
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43729
44302
  }
43730
44303
  ),
43731
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44304
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43732
44305
  "span",
43733
44306
  {
43734
44307
  ref: tableRowGuideRef,
@@ -43736,7 +44309,7 @@ var UEditor = import_react80.default.forwardRef(({
43736
44309
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
43737
44310
  }
43738
44311
  ),
43739
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44312
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43740
44313
  "span",
43741
44314
  {
43742
44315
  ref: activeTableCellHighlightRef,
@@ -43745,20 +44318,24 @@ var UEditor = import_react80.default.forwardRef(({
43745
44318
  className: "pointer-events-none hidden absolute z-20 rounded-[2px] border-2 border-primary bg-primary/10"
43746
44319
  }
43747
44320
  ),
43748
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44321
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43749
44322
  "div",
43750
44323
  {
43751
44324
  ref: formulaCoordinateOverlayRef,
43752
44325
  "data-ueditor-formula-coordinate-overlay": "",
43753
- className: "pointer-events-none absolute inset-0 z-[21] hidden overflow-visible"
44326
+ className: "pointer-events-none absolute inset-0 z-[35] hidden overflow-visible"
43754
44327
  }
43755
44328
  ),
43756
- formulaRangeHighlight && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
44329
+ formulaRangeHighlight && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
43757
44330
  "span",
43758
44331
  {
43759
44332
  "aria-hidden": "true",
43760
44333
  "data-ueditor-formula-range-highlight": "",
43761
- className: "pointer-events-none absolute z-20 rounded-[2px] border-2 border-primary bg-primary/10",
44334
+ "data-ueditor-formula-range-blocked": formulaRangeHighlight.blocked ? "true" : void 0,
44335
+ className: cn(
44336
+ "pointer-events-none absolute z-20 rounded-[2px] border-2",
44337
+ formulaRangeHighlight.blocked ? "border-destructive bg-destructive/15" : "border-primary bg-primary/10"
44338
+ ),
43762
44339
  style: {
43763
44340
  left: formulaRangeHighlight.left,
43764
44341
  top: formulaRangeHighlight.top,
@@ -43767,9 +44344,9 @@ var UEditor = import_react80.default.forwardRef(({
43767
44344
  }
43768
44345
  }
43769
44346
  ),
43770
- editable && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(TableControls, { editor, containerRef: editorContentRef }),
43771
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
43772
- import_react81.EditorContent,
44347
+ editable && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(TableControls, { editor, containerRef: editorContentRef }),
44348
+ /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
44349
+ import_react83.EditorContent,
43773
44350
  {
43774
44351
  editor,
43775
44352
  className: "min-h-full"
@@ -43778,7 +44355,7 @@ var UEditor = import_react80.default.forwardRef(({
43778
44355
  ]
43779
44356
  }
43780
44357
  ),
43781
- showFooter && showCharacterCount && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(CharacterCountDisplay, { editor, maxCharacters })
44358
+ showFooter && showCharacterCount && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(CharacterCountDisplay, { editor, maxCharacters })
43782
44359
  ]
43783
44360
  }
43784
44361
  );