@underverse-ui/underverse 1.0.63 → 1.0.65

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.63",
3
+ "version": "1.0.65",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 217,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -890,7 +890,15 @@ var en_default = {
890
890
  heading1: "Heading 1",
891
891
  heading2: "Heading 2",
892
892
  heading3: "Heading 3",
893
- emoji: "Insert Emoji"
893
+ emoji: "Insert Emoji",
894
+ imageLayoutBlock: "Image Block",
895
+ imageLayoutLeft: "Wrap Text Right",
896
+ imageLayoutRight: "Wrap Text Left",
897
+ imageWidthSm: "Image Width Small",
898
+ imageWidthMd: "Image Width Medium",
899
+ imageWidthLg: "Image Width Large",
900
+ imageResetSize: "Reset Image Size",
901
+ imageDelete: "Delete Image"
894
902
  },
895
903
  slashCommand: {
896
904
  basicBlocks: "Basic Blocks",
@@ -1114,7 +1122,15 @@ var vi_default = {
1114
1122
  heading1: "Ti\xEAu \u0111\u1EC1 1",
1115
1123
  heading2: "Ti\xEAu \u0111\u1EC1 2",
1116
1124
  heading3: "Ti\xEAu \u0111\u1EC1 3",
1117
- emoji: "Ch\xE8n Emoji"
1125
+ emoji: "Ch\xE8n Emoji",
1126
+ imageLayoutBlock: "\u1EA2nh D\u1EA1ng Kh\u1ED1i",
1127
+ imageLayoutLeft: "\u1EA2nh Tr\xE1i, Ch\u1EEF Ph\u1EA3i",
1128
+ imageLayoutRight: "\u1EA2nh Ph\u1EA3i, Ch\u1EEF Tr\xE1i",
1129
+ imageWidthSm: "\u1EA2nh Nh\u1ECF",
1130
+ imageWidthMd: "\u1EA2nh V\u1EEBa",
1131
+ imageWidthLg: "\u1EA2nh L\u1EDBn",
1132
+ imageResetSize: "\u0110\u1EB7t L\u1EA1i K\xEDch Th\u01B0\u1EDBc \u1EA2nh",
1133
+ imageDelete: "X\xF3a \u1EA2nh"
1118
1134
  },
1119
1135
  slashCommand: {
1120
1136
  basicBlocks: "Kh\u1ED1i c\u01A1 b\u1EA3n",
@@ -1338,7 +1354,15 @@ var ko_default = {
1338
1354
  heading1: "\uC81C\uBAA9 1",
1339
1355
  heading2: "\uC81C\uBAA9 2",
1340
1356
  heading3: "\uC81C\uBAA9 3",
1341
- emoji: "\uC774\uBAA8\uC9C0 \uC0BD\uC785"
1357
+ emoji: "\uC774\uBAA8\uC9C0 \uC0BD\uC785",
1358
+ imageLayoutBlock: "\uC774\uBBF8\uC9C0 \uBE14\uB85D",
1359
+ imageLayoutLeft: "\uC67C\uCABD \uC774\uBBF8\uC9C0, \uC624\uB978\uCABD \uD14D\uC2A4\uD2B8",
1360
+ imageLayoutRight: "\uC624\uB978\uCABD \uC774\uBBF8\uC9C0, \uC67C\uCABD \uD14D\uC2A4\uD2B8",
1361
+ imageWidthSm: "\uC791\uC740 \uC774\uBBF8\uC9C0",
1362
+ imageWidthMd: "\uC911\uAC04 \uC774\uBBF8\uC9C0",
1363
+ imageWidthLg: "\uD070 \uC774\uBBF8\uC9C0",
1364
+ imageResetSize: "\uC774\uBBF8\uC9C0 \uD06C\uAE30 \uCD08\uAE30\uD654",
1365
+ imageDelete: "\uC774\uBBF8\uC9C0 \uC0AD\uC81C"
1342
1366
  },
1343
1367
  slashCommand: {
1344
1368
  basicBlocks: "\uAE30\uBCF8 \uBE14\uB85D",
@@ -1561,7 +1585,15 @@ var ja_default = {
1561
1585
  heading1: "\u898B\u51FA\u3057 1",
1562
1586
  heading2: "\u898B\u51FA\u3057 2",
1563
1587
  heading3: "\u898B\u51FA\u3057 3",
1564
- emoji: "\u7D75\u6587\u5B57\u3092\u633F\u5165"
1588
+ emoji: "\u7D75\u6587\u5B57\u3092\u633F\u5165",
1589
+ imageLayoutBlock: "\u753B\u50CF\u30D6\u30ED\u30C3\u30AF",
1590
+ imageLayoutLeft: "\u753B\u50CF\u3092\u5DE6\u3001\u6587\u5B57\u3092\u53F3\u306B\u56DE\u308A\u8FBC\u307F",
1591
+ imageLayoutRight: "\u753B\u50CF\u3092\u53F3\u3001\u6587\u5B57\u3092\u5DE6\u306B\u56DE\u308A\u8FBC\u307F",
1592
+ imageWidthSm: "\u753B\u50CF \u5C0F",
1593
+ imageWidthMd: "\u753B\u50CF \u4E2D",
1594
+ imageWidthLg: "\u753B\u50CF \u5927",
1595
+ imageResetSize: "\u753B\u50CF\u30B5\u30A4\u30BA\u3092\u30EA\u30BB\u30C3\u30C8",
1596
+ imageDelete: "\u753B\u50CF\u3092\u524A\u9664"
1565
1597
  },
1566
1598
  slashCommand: {
1567
1599
  basicBlocks: "\u57FA\u672C\u30D6\u30ED\u30C3\u30AF",
@@ -16384,12 +16416,12 @@ function Carousel({
16384
16416
  mainScale: 1.12,
16385
16417
  sideScale: 0.82,
16386
16418
  farScale: 0.72,
16387
- sideOpacity: 0.72,
16388
- farOpacity: 0.24,
16419
+ sideOpacity: 0.84,
16420
+ farOpacity: 0.44,
16389
16421
  sideOffset: 22,
16390
16422
  rotate: 20,
16391
16423
  depthStep: 120,
16392
- blur: 2.6
16424
+ blur: 1.6
16393
16425
  };
16394
16426
  }
16395
16427
  if (effectPreset === "gallery") {
@@ -16468,12 +16500,12 @@ function Carousel({
16468
16500
  mainScale: 1.04,
16469
16501
  sideScale: effectiveAnimation === "stack" ? 0.93 : 0.88,
16470
16502
  farScale: effectiveAnimation === "stack" ? 0.86 : 0.76,
16471
- sideOpacity: effectiveAnimation === "stack" ? 0.74 : 0.78,
16472
- farOpacity: effectiveAnimation === "stack" ? 0.42 : 0.38,
16503
+ sideOpacity: effectiveAnimation === "stack" ? 0.8 : 0.86,
16504
+ farOpacity: effectiveAnimation === "stack" ? 0.5 : 0.48,
16473
16505
  sideOffset: effectiveAnimation === "stack" ? 20 : 28,
16474
16506
  rotate: 24,
16475
16507
  depthStep: effectiveAnimation === "stack" ? 60 : 90,
16476
- blur: 1.5,
16508
+ blur: 1.1,
16477
16509
  stackOffset: 20,
16478
16510
  stackLift: 12,
16479
16511
  ...presetEffectOptions,
@@ -16626,7 +16658,7 @@ function Carousel({
16626
16658
  opacity: distance === 0 ? 1 : distance === 1 || distance === -1 ? mergedEffectOptions.sideOpacity : mergedEffectOptions.farOpacity,
16627
16659
  transform: `translate3d(${xOffset2}px, ${yOffset}px, -${absDistance * mergedEffectOptions.depthStep}px) scale(${scale2})`,
16628
16660
  filter: distance === 0 ? "blur(0px)" : `blur(${Math.min(absDistance, 2) * mergedEffectOptions.blur}px)`,
16629
- pointerEvents: distance === 0 ? "auto" : "none"
16661
+ pointerEvents: "auto"
16630
16662
  };
16631
16663
  }
16632
16664
  const xOffset = distance * mergedEffectOptions.sideOffset;
@@ -16636,7 +16668,7 @@ function Carousel({
16636
16668
  opacity: distance === 0 ? 1 : distance === 1 || distance === -1 ? mergedEffectOptions.sideOpacity : mergedEffectOptions.farOpacity,
16637
16669
  transform: `translate3d(${xOffset}%, 0, -${absDistance * mergedEffectOptions.depthStep}px) rotateY(${rotateY}deg) scale(${scale})`,
16638
16670
  filter: distance === 0 ? "blur(0px)" : `blur(${Math.min(absDistance, 2) * mergedEffectOptions.blur}px)`,
16639
- pointerEvents: distance === 0 ? "auto" : "none"
16671
+ pointerEvents: "auto"
16640
16672
  };
16641
16673
  },
16642
16674
  [effectiveAnimation, getLoopDistance, mergedEffectOptions]
@@ -16688,10 +16720,12 @@ function Carousel({
16688
16720
  effectiveAnimation === "fade" && (idx === currentIndex ? "opacity-100 z-10" : "opacity-0 pointer-events-none z-0"),
16689
16721
  effectiveAnimation === "scale" && (idx === currentIndex ? "opacity-100 scale-100 z-10" : "opacity-0 scale-95 pointer-events-none z-0"),
16690
16722
  isDeckAnimation && "w-full max-w-[78%] md:max-w-[72%] transition-[opacity,transform] duration-500 ease-out",
16723
+ isDeckAnimation && idx !== currentIndex && "cursor-pointer",
16691
16724
  effectiveAnimation !== "slide" && "transition-[opacity,transform] duration-500 ease-in-out",
16692
16725
  slideClassName
16693
16726
  ),
16694
16727
  style: effectiveAnimation === "slide" ? { [isHorizontal ? "width" : "height"]: `${slideWidth}%` } : isDeckAnimation ? getDeckSlideStyles(idx) : void 0,
16728
+ onClick: isDeckAnimation && idx !== currentIndex ? () => scrollTo(idx) : void 0,
16695
16729
  role: "group",
16696
16730
  "aria-roledescription": "slide",
16697
16731
  "aria-label": `${idx + 1} of ${totalSlides}`,
@@ -22535,6 +22569,8 @@ var import_react44 = require("@tiptap/react");
22535
22569
  var import_jsx_runtime73 = require("react/jsx-runtime");
22536
22570
  var MIN_IMAGE_SIZE_PX = 40;
22537
22571
  var AXIS_LOCK_THRESHOLD_PX = 4;
22572
+ var IMAGE_LAYOUTS = /* @__PURE__ */ new Set(["block", "left", "right"]);
22573
+ var IMAGE_WIDTH_PRESETS = /* @__PURE__ */ new Set(["sm", "md", "lg"]);
22538
22574
  function toNullableNumber(value) {
22539
22575
  if (typeof value === "number" && Number.isFinite(value)) return value;
22540
22576
  if (typeof value === "string") {
@@ -22546,6 +22582,33 @@ function toNullableNumber(value) {
22546
22582
  function clamp8(value, min, max) {
22547
22583
  return Math.min(max, Math.max(min, value));
22548
22584
  }
22585
+ function parseImageLayout(value) {
22586
+ if (typeof value === "string" && IMAGE_LAYOUTS.has(value)) {
22587
+ return value;
22588
+ }
22589
+ return "block";
22590
+ }
22591
+ function parseImageWidthPreset(value) {
22592
+ if (typeof value === "string" && IMAGE_WIDTH_PRESETS.has(value)) {
22593
+ return value;
22594
+ }
22595
+ return null;
22596
+ }
22597
+ function getImageLayoutStyles(layout) {
22598
+ if (layout === "left") {
22599
+ return {
22600
+ "data-image-layout": "left",
22601
+ style: "float:left;display:block;margin:0.25rem 1rem 0.75rem 0;"
22602
+ };
22603
+ }
22604
+ if (layout === "right") {
22605
+ return {
22606
+ "data-image-layout": "right",
22607
+ style: "float:right;display:block;margin:0.25rem 0 0.75rem 1rem;"
22608
+ };
22609
+ }
22610
+ return {};
22611
+ }
22549
22612
  function ResizableImageNodeView(props) {
22550
22613
  const { node, selected, updateAttributes, editor, getPos } = props;
22551
22614
  const wrapperRef = (0, import_react43.useRef)(null);
@@ -22555,6 +22618,8 @@ function ResizableImageNodeView(props) {
22555
22618
  const widthAttr = toNullableNumber(node.attrs["width"]);
22556
22619
  const heightAttr = toNullableNumber(node.attrs["height"]);
22557
22620
  const textAlign = String(node.attrs["textAlign"] ?? "");
22621
+ const imageLayout = parseImageLayout(node.attrs["imageLayout"]);
22622
+ const preserveAspectByDefault = imageLayout === "left" || imageLayout === "right";
22558
22623
  const dragStateRef = (0, import_react43.useRef)(null);
22559
22624
  (0, import_react43.useEffect)(() => {
22560
22625
  const img = imgRef.current;
@@ -22603,7 +22668,8 @@ function ResizableImageNodeView(props) {
22603
22668
  const dy = event.clientY - drag.startY;
22604
22669
  let nextW = drag.startW;
22605
22670
  let nextH = drag.startH;
22606
- if (event.ctrlKey) {
22671
+ const shouldPreserveAspect = preserveAspectByDefault ? !event.ctrlKey : event.ctrlKey;
22672
+ if (shouldPreserveAspect) {
22607
22673
  if (Math.abs(dx) >= Math.abs(dy)) {
22608
22674
  nextW = clamp8(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
22609
22675
  nextH = clamp8(nextW / drag.aspect, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
@@ -22630,7 +22696,8 @@ function ResizableImageNodeView(props) {
22630
22696
  if (!drag) return;
22631
22697
  updateAttributes({
22632
22698
  width: Math.round(drag.lastW),
22633
- height: Math.round(drag.lastH)
22699
+ height: Math.round(drag.lastH),
22700
+ imageWidthPreset: null
22634
22701
  });
22635
22702
  };
22636
22703
  const onResizePointerUp = (event) => {
@@ -22648,14 +22715,15 @@ function ResizableImageNodeView(props) {
22648
22715
  finishResize();
22649
22716
  };
22650
22717
  const showHandle = selected || isHovered || isResizing;
22651
- const wrapperAlignClass = textAlign === "center" ? "mx-auto" : textAlign === "right" ? "ml-auto" : textAlign === "justify" ? "mx-auto" : "";
22652
- const wrapperWidthClass = "w-fit";
22718
+ const wrapperAlignClass = imageLayout === "block" ? textAlign === "center" ? "mx-auto" : textAlign === "right" ? "ml-auto" : textAlign === "justify" ? "mx-auto" : "" : "";
22719
+ const wrapperLayoutClass = imageLayout === "left" ? "float-left mr-4 mb-3 mt-1 clear-none max-w-[min(45%,20rem)]" : imageLayout === "right" ? "float-right ml-4 mb-3 mt-1 clear-none max-w-[min(45%,20rem)]" : "w-fit";
22653
22720
  return /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(
22654
22721
  import_react44.NodeViewWrapper,
22655
22722
  {
22656
22723
  as: "div",
22657
22724
  ref: wrapperRef,
22658
- className: ["relative block align-middle max-w-full my-4", wrapperWidthClass, wrapperAlignClass].filter(Boolean).join(" "),
22725
+ "data-image-layout": imageLayout,
22726
+ className: ["relative block align-middle max-w-full my-4", wrapperLayoutClass, wrapperAlignClass].filter(Boolean).join(" "),
22659
22727
  onMouseEnter: () => setIsHovered(true),
22660
22728
  onMouseLeave: () => setIsHovered(false),
22661
22729
  onClick: (e) => {
@@ -22725,6 +22793,25 @@ var ResizableImage = import_extension_image.default.extend({
22725
22793
  return Number.isFinite(parsed) ? parsed : null;
22726
22794
  },
22727
22795
  renderHTML: (attrs) => typeof attrs.height === "number" ? { height: attrs.height } : {}
22796
+ },
22797
+ imageLayout: {
22798
+ default: "block",
22799
+ parseHTML: (element) => {
22800
+ const explicit = element.getAttribute("data-image-layout");
22801
+ if (explicit) return parseImageLayout(explicit);
22802
+ const floatValue = element.style.float;
22803
+ if (floatValue === "left" || floatValue === "right") return floatValue;
22804
+ return "block";
22805
+ },
22806
+ renderHTML: (attrs) => getImageLayoutStyles(parseImageLayout(attrs.imageLayout))
22807
+ },
22808
+ imageWidthPreset: {
22809
+ default: null,
22810
+ parseHTML: (element) => parseImageWidthPreset(element.getAttribute("data-image-size")),
22811
+ renderHTML: (attrs) => {
22812
+ const preset = parseImageWidthPreset(attrs.imageWidthPreset);
22813
+ return preset ? { "data-image-size": preset } : {};
22814
+ }
22728
22815
  }
22729
22816
  };
22730
22817
  },
@@ -22953,6 +23040,96 @@ var EditorColorPalette = ({
22953
23040
  )) })
22954
23041
  ] });
22955
23042
 
23043
+ // src/components/UEditor/image-commands.ts
23044
+ var import_state4 = require("@tiptap/pm/state");
23045
+ var IMAGE_WIDTHS_BY_LAYOUT = {
23046
+ block: {
23047
+ sm: 180,
23048
+ md: 280,
23049
+ lg: 380
23050
+ },
23051
+ wrap: {
23052
+ sm: 140,
23053
+ md: 200,
23054
+ lg: 260
23055
+ }
23056
+ };
23057
+ function isSelectedImage(editor) {
23058
+ const { selection } = editor.state;
23059
+ return selection instanceof import_state4.NodeSelection && selection.node.type.name === "image";
23060
+ }
23061
+ function applyImageLayout(editor, layout) {
23062
+ const { state, view } = editor;
23063
+ const { selection, schema } = state;
23064
+ if (!(selection instanceof import_state4.NodeSelection) || selection.node.type.name !== "image") {
23065
+ editor.chain().focus().updateAttributes("image", { imageLayout: layout }).run();
23066
+ return;
23067
+ }
23068
+ let transaction = state.tr.setNodeMarkup(selection.from, void 0, {
23069
+ ...selection.node.attrs,
23070
+ imageLayout: layout
23071
+ });
23072
+ if (layout !== "block") {
23073
+ const nextPos = transaction.mapping.map(selection.to);
23074
+ const nextNode = transaction.doc.nodeAt(nextPos);
23075
+ if (!nextNode || nextNode.type.name !== "paragraph") {
23076
+ const paragraph = schema.nodes.paragraph?.create();
23077
+ if (paragraph) {
23078
+ transaction = transaction.insert(nextPos, paragraph);
23079
+ }
23080
+ }
23081
+ const resolvedPos = transaction.doc.resolve(Math.min(nextPos + 1, transaction.doc.content.size));
23082
+ transaction = transaction.setSelection(import_state4.TextSelection.near(resolvedPos));
23083
+ } else {
23084
+ const resolvedPos = transaction.doc.resolve(selection.from);
23085
+ transaction = transaction.setSelection(import_state4.NodeSelection.create(transaction.doc, resolvedPos.pos));
23086
+ }
23087
+ view.dispatch(transaction.scrollIntoView());
23088
+ view.focus();
23089
+ }
23090
+ function applyImageWidthPreset(editor, preset) {
23091
+ const attrs = editor.getAttributes("image");
23092
+ const mode = attrs.imageLayout === "left" || attrs.imageLayout === "right" ? "wrap" : "block";
23093
+ const width = IMAGE_WIDTHS_BY_LAYOUT[mode][preset];
23094
+ if (!isSelectedImage(editor)) {
23095
+ editor.chain().focus().updateAttributes("image", { width, imageWidthPreset: preset }).run();
23096
+ return;
23097
+ }
23098
+ const { state, view } = editor;
23099
+ const selection = state.selection;
23100
+ const transaction = state.tr.setNodeMarkup(selection.from, void 0, {
23101
+ ...selection.node.attrs,
23102
+ width,
23103
+ imageWidthPreset: preset
23104
+ });
23105
+ view.dispatch(transaction.scrollIntoView());
23106
+ view.focus();
23107
+ }
23108
+ function resetImageSize(editor) {
23109
+ if (!isSelectedImage(editor)) {
23110
+ editor.chain().focus().updateAttributes("image", {
23111
+ width: null,
23112
+ height: null,
23113
+ imageWidthPreset: null
23114
+ }).run();
23115
+ return;
23116
+ }
23117
+ const { state, view } = editor;
23118
+ const selection = state.selection;
23119
+ const transaction = state.tr.setNodeMarkup(selection.from, void 0, {
23120
+ ...selection.node.attrs,
23121
+ width: null,
23122
+ height: null,
23123
+ imageWidthPreset: null
23124
+ });
23125
+ view.dispatch(transaction.scrollIntoView());
23126
+ view.focus();
23127
+ }
23128
+ function deleteSelectedImage(editor) {
23129
+ if (!isSelectedImage(editor)) return;
23130
+ editor.chain().focus().deleteSelection().run();
23131
+ }
23132
+
22956
23133
  // src/components/UEditor/inputs.tsx
22957
23134
  var import_react46 = require("react");
22958
23135
  var import_lucide_react42 = require("lucide-react");
@@ -23289,6 +23466,10 @@ var EditorToolbar = ({
23289
23466
  const fileInputRef = (0, import_react48.useRef)(null);
23290
23467
  const [isUploadingImage, setIsUploadingImage] = (0, import_react48.useState)(false);
23291
23468
  const [imageUploadError, setImageUploadError] = (0, import_react48.useState)(null);
23469
+ const isImageSelected = editor.isActive("image");
23470
+ const imageAttrs = editor.getAttributes("image");
23471
+ const imageLayout = imageAttrs.imageLayout === "left" || imageAttrs.imageLayout === "right" ? imageAttrs.imageLayout : "block";
23472
+ const imageWidthPreset = imageAttrs.imageWidthPreset === "sm" || imageAttrs.imageWidthPreset === "md" || imageAttrs.imageWidthPreset === "lg" ? imageAttrs.imageWidthPreset : null;
23292
23473
  const insertImageFiles = async (files) => {
23293
23474
  if (files.length === 0) return;
23294
23475
  setIsUploadingImage(true);
@@ -23621,6 +23802,85 @@ var EditorToolbar = ({
23621
23802
  void insertImageFiles(files);
23622
23803
  }
23623
23804
  }
23805
+ ),
23806
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("div", { className: "my-1 border-t" }),
23807
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23808
+ DropdownMenuItem,
23809
+ {
23810
+ icon: import_lucide_react44.AlignCenter,
23811
+ label: t("toolbar.imageLayoutBlock"),
23812
+ onClick: () => applyImageLayout(editor, "block"),
23813
+ active: isImageSelected && imageLayout === "block",
23814
+ disabled: !isImageSelected
23815
+ }
23816
+ ),
23817
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23818
+ DropdownMenuItem,
23819
+ {
23820
+ icon: import_lucide_react44.AlignLeft,
23821
+ label: t("toolbar.imageLayoutLeft"),
23822
+ onClick: () => applyImageLayout(editor, "left"),
23823
+ active: isImageSelected && imageLayout === "left",
23824
+ disabled: !isImageSelected
23825
+ }
23826
+ ),
23827
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23828
+ DropdownMenuItem,
23829
+ {
23830
+ icon: import_lucide_react44.AlignRight,
23831
+ label: t("toolbar.imageLayoutRight"),
23832
+ onClick: () => applyImageLayout(editor, "right"),
23833
+ active: isImageSelected && imageLayout === "right",
23834
+ disabled: !isImageSelected
23835
+ }
23836
+ ),
23837
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("div", { className: "my-1 border-t" }),
23838
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23839
+ DropdownMenuItem,
23840
+ {
23841
+ label: t("toolbar.imageWidthSm"),
23842
+ onClick: () => applyImageWidthPreset(editor, "sm"),
23843
+ active: isImageSelected && imageWidthPreset === "sm",
23844
+ disabled: !isImageSelected
23845
+ }
23846
+ ),
23847
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23848
+ DropdownMenuItem,
23849
+ {
23850
+ label: t("toolbar.imageWidthMd"),
23851
+ onClick: () => applyImageWidthPreset(editor, "md"),
23852
+ active: isImageSelected && imageWidthPreset === "md",
23853
+ disabled: !isImageSelected
23854
+ }
23855
+ ),
23856
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23857
+ DropdownMenuItem,
23858
+ {
23859
+ label: t("toolbar.imageWidthLg"),
23860
+ onClick: () => applyImageWidthPreset(editor, "lg"),
23861
+ active: isImageSelected && imageWidthPreset === "lg",
23862
+ disabled: !isImageSelected
23863
+ }
23864
+ ),
23865
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("div", { className: "my-1 border-t" }),
23866
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23867
+ DropdownMenuItem,
23868
+ {
23869
+ icon: import_lucide_react44.RotateCcw,
23870
+ label: t("toolbar.imageResetSize"),
23871
+ onClick: () => resetImageSize(editor),
23872
+ disabled: !isImageSelected
23873
+ }
23874
+ ),
23875
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
23876
+ DropdownMenuItem,
23877
+ {
23878
+ icon: import_lucide_react44.Trash2,
23879
+ label: t("toolbar.imageDelete"),
23880
+ onClick: () => deleteSelectedImage(editor),
23881
+ disabled: !isImageSelected,
23882
+ destructive: true
23883
+ }
23624
23884
  )
23625
23885
  ] })
23626
23886
  }
@@ -23933,6 +24193,10 @@ var BubbleMenuContent = ({
23933
24193
  const { textColors, highlightColors } = useEditorColors();
23934
24194
  const [showLinkInput, setShowLinkInput] = (0, import_react49.useState)(false);
23935
24195
  const [showEditorColorPalette, setShowEditorColorPalette] = (0, import_react49.useState)(false);
24196
+ const isImageSelected = editor.isActive("image");
24197
+ const imageAttrs = editor.getAttributes("image");
24198
+ const imageLayout = imageAttrs.imageLayout === "left" || imageAttrs.imageLayout === "right" ? imageAttrs.imageLayout : "block";
24199
+ const imageWidthPreset = imageAttrs.imageWidthPreset === "sm" || imageAttrs.imageWidthPreset === "md" || imageAttrs.imageWidthPreset === "lg" ? imageAttrs.imageWidthPreset : null;
23936
24200
  (0, import_react49.useEffect)(() => {
23937
24201
  onKeepOpenChange?.(showLinkInput);
23938
24202
  }, [onKeepOpenChange, showLinkInput]);
@@ -24007,6 +24271,20 @@ var BubbleMenuContent = ({
24007
24271
  ) })
24008
24272
  ] });
24009
24273
  }
24274
+ if (isImageSelected) {
24275
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "flex items-center gap-0.5 p-1", children: [
24276
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => applyImageLayout(editor, "block"), active: imageLayout === "block", title: t("toolbar.imageLayoutBlock"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.AlignCenter, { className: "w-4 h-4" }) }),
24277
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => applyImageLayout(editor, "left"), active: imageLayout === "left", title: t("toolbar.imageLayoutLeft"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.AlignLeft, { className: "w-4 h-4" }) }),
24278
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => applyImageLayout(editor, "right"), active: imageLayout === "right", title: t("toolbar.imageLayoutRight"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.AlignRight, { className: "w-4 h-4" }) }),
24279
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "w-px h-6 bg-border/50 mx-1" }),
24280
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => applyImageWidthPreset(editor, "sm"), active: imageWidthPreset === "sm", title: t("toolbar.imageWidthSm"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-[10px] font-semibold", children: "S" }) }),
24281
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => applyImageWidthPreset(editor, "md"), active: imageWidthPreset === "md", title: t("toolbar.imageWidthMd"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-[10px] font-semibold", children: "M" }) }),
24282
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => applyImageWidthPreset(editor, "lg"), active: imageWidthPreset === "lg", title: t("toolbar.imageWidthLg"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-[10px] font-semibold", children: "L" }) }),
24283
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "w-px h-6 bg-border/50 mx-1" }),
24284
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => resetImageSize(editor), title: t("toolbar.imageResetSize"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.RotateCcw, { className: "w-4 h-4" }) }),
24285
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => deleteSelectedImage(editor), title: t("toolbar.imageDelete"), className: "text-destructive hover:text-destructive", children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.Trash2, { className: "w-4 h-4" }) })
24286
+ ] });
24287
+ }
24010
24288
  return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "flex items-center gap-0.5 p-1", children: [
24011
24289
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => editor.chain().focus().toggleBold().run(), active: editor.isActive("bold"), title: t("toolbar.bold"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.Bold, { className: "w-4 h-4" }) }),
24012
24290
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(ToolbarButton, { onClick: () => editor.chain().focus().toggleItalic().run(), active: editor.isActive("italic"), title: t("toolbar.italic"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react45.Italic, { className: "w-4 h-4" }) }),
@@ -24059,19 +24337,28 @@ var BubbleMenuContent = ({
24059
24337
  ] });
24060
24338
  };
24061
24339
  var CustomBubbleMenu = ({ editor }) => {
24340
+ const SHOW_DELAY_MS = 180;
24062
24341
  const [isVisible, setIsVisible] = (0, import_react49.useState)(false);
24063
24342
  const [position, setPosition] = (0, import_react49.useState)({ top: 0, left: 0 });
24064
24343
  const menuRef = (0, import_react49.useRef)(null);
24065
24344
  const keepOpenRef = (0, import_react49.useRef)(false);
24345
+ const showTimeoutRef = (0, import_react49.useRef)(null);
24066
24346
  const setKeepOpen = (0, import_react49.useCallback)((next) => {
24067
24347
  keepOpenRef.current = next;
24068
24348
  if (next) setIsVisible(true);
24069
24349
  }, []);
24070
24350
  (0, import_react49.useEffect)(() => {
24351
+ const clearShowTimeout = () => {
24352
+ if (showTimeoutRef.current) {
24353
+ clearTimeout(showTimeoutRef.current);
24354
+ showTimeoutRef.current = null;
24355
+ }
24356
+ };
24071
24357
  const updatePosition = () => {
24072
24358
  const { state, view } = editor;
24073
24359
  const { from, to, empty } = state.selection;
24074
24360
  if (!keepOpenRef.current && (empty || !view.hasFocus())) {
24361
+ clearShowTimeout();
24075
24362
  setIsVisible(false);
24076
24363
  return;
24077
24364
  }
@@ -24080,15 +24367,28 @@ var CustomBubbleMenu = ({ editor }) => {
24080
24367
  const left = (start.left + end.left) / 2;
24081
24368
  const top = start.top - 10;
24082
24369
  setPosition({ top, left });
24083
- setIsVisible(true);
24370
+ if (keepOpenRef.current) {
24371
+ clearShowTimeout();
24372
+ setIsVisible(true);
24373
+ return;
24374
+ }
24375
+ clearShowTimeout();
24376
+ showTimeoutRef.current = setTimeout(() => {
24377
+ setIsVisible(true);
24378
+ showTimeoutRef.current = null;
24379
+ }, SHOW_DELAY_MS);
24084
24380
  };
24085
24381
  const handleBlur = () => {
24086
- if (!keepOpenRef.current) setIsVisible(false);
24382
+ if (!keepOpenRef.current) {
24383
+ clearShowTimeout();
24384
+ setIsVisible(false);
24385
+ }
24087
24386
  };
24088
24387
  editor.on("selectionUpdate", updatePosition);
24089
24388
  editor.on("focus", updatePosition);
24090
24389
  editor.on("blur", handleBlur);
24091
24390
  return () => {
24391
+ clearShowTimeout();
24092
24392
  editor.off("selectionUpdate", updatePosition);
24093
24393
  editor.off("focus", updatePosition);
24094
24394
  editor.off("blur", handleBlur);
@@ -24572,7 +24872,21 @@ var UEditor = import_react50.default.forwardRef(({
24572
24872
  "[&_blockquote]:rounded-r-lg",
24573
24873
  "[&_blockquote]:italic",
24574
24874
  "[&_blockquote]:text-muted-foreground",
24575
- "[&_blockquote_p]:my-0"
24875
+ "[&_blockquote_p]:my-0",
24876
+ "[&_[data-image-layout='left']+p]:mt-1",
24877
+ "[&_[data-image-layout='left']+p]:min-h-[5rem]",
24878
+ "[&_[data-image-layout='right']+p]:mt-1",
24879
+ "[&_[data-image-layout='right']+p]:min-h-[5rem]",
24880
+ "max-md:[&_[data-image-layout='left']]:float-none",
24881
+ "max-md:[&_[data-image-layout='left']]:mr-0",
24882
+ "max-md:[&_[data-image-layout='left']]:ml-0",
24883
+ "max-md:[&_[data-image-layout='left']]:max-w-full",
24884
+ "max-md:[&_[data-image-layout='right']]:float-none",
24885
+ "max-md:[&_[data-image-layout='right']]:mr-0",
24886
+ "max-md:[&_[data-image-layout='right']]:ml-0",
24887
+ "max-md:[&_[data-image-layout='right']]:max-w-full",
24888
+ "max-md:[&_[data-image-layout='left']+p]:min-h-0",
24889
+ "max-md:[&_[data-image-layout='right']+p]:min-h-0"
24576
24890
  )
24577
24891
  }
24578
24892
  },