@notum-cz/strapi-plugin-tiptap-editor 1.2.0 → 1.2.1

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.
@@ -8,7 +8,7 @@ const ReactDOM = require("react-dom");
8
8
  const styled = require("styled-components");
9
9
  const admin = require("@strapi/strapi/admin");
10
10
  const icons = require("@strapi/icons");
11
- const index = require("./index-yXpX_VsO.js");
11
+ const index = require("./index-CTcKIvYv.js");
12
12
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
13
13
  const React__default = /* @__PURE__ */ _interopDefault(React);
14
14
  const ReactDOM__default = /* @__PURE__ */ _interopDefault(ReactDOM);
@@ -19909,19 +19909,53 @@ const TiptapInputStyles = styled__default.default.div`
19909
19909
  margin: 0.75em 0;
19910
19910
  }
19911
19911
 
19912
- /* Image alignment — margin-based, no float */
19913
- [data-align="center"] img {
19914
- margin-left: auto;
19915
- margin-right: auto;
19912
+ /* Image alignment — text-align on the node wrapper centres the inline-block image container */
19913
+ [data-align="center"] {
19914
+ text-align: center;
19916
19915
  }
19917
19916
 
19918
- [data-align="right"] img {
19919
- margin-left: auto;
19920
- margin-right: 0;
19917
+ [data-align="right"] {
19918
+ text-align: right;
19921
19919
  }
19922
19920
 
19923
19921
  /* data-align="left" and null: natural left flow, no rule needed */
19924
19922
 
19923
+ /* Inline-block wrapper for positioning the resize handle relative to the image */
19924
+ .image-wrapper {
19925
+ position: relative;
19926
+ display: inline-block;
19927
+ max-width: 100%;
19928
+ line-height: 0; /* prevent extra space below img */
19929
+ }
19930
+
19931
+ .image-wrapper img {
19932
+ display: block;
19933
+ max-width: 100%;
19934
+ height: auto;
19935
+ }
19936
+
19937
+ /* Resize handle — bottom-right corner */
19938
+ .image-resize-handle {
19939
+ position: absolute;
19940
+ right: -4px;
19941
+ bottom: -4px;
19942
+ width: 12px;
19943
+ height: 12px;
19944
+ background: #4945ff;
19945
+ border: 2px solid #fff;
19946
+ border-radius: 50%;
19947
+ cursor: nwse-resize;
19948
+ opacity: 0;
19949
+ transition: opacity 0.15s;
19950
+ z-index: 1;
19951
+ }
19952
+
19953
+ .image-wrapper:hover .image-resize-handle,
19954
+ .image-wrapper[data-selected] .image-resize-handle,
19955
+ .ProseMirror-selectednode .image-resize-handle {
19956
+ opacity: 1;
19957
+ }
19958
+
19925
19959
  // Source: https://tiptap.dev/docs/editor/extensions/nodes/table
19926
19960
 
19927
19961
  .ProseMirror {
@@ -20893,19 +20927,43 @@ function TextAlignRight(props) {
20893
20927
  }
20894
20928
  );
20895
20929
  }
20896
- function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode: deleteNode2 }) {
20930
+ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode: deleteNode2, selected, extension }) {
20897
20931
  const { formatMessage } = reactIntl.useIntl();
20898
20932
  const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
20899
20933
  const [altText, setAltText] = React.useState(node.attrs.alt ?? "");
20934
+ const imgRef = React.useRef(null);
20935
+ const isResizingRef = React.useRef(false);
20936
+ const resizeCleanupRef = React.useRef(null);
20937
+ const rawResize = extension.options.resize;
20938
+ const resizeEnabled = typeof rawResize === "object" && !!rawResize ? rawResize.enabled : !!rawResize;
20939
+ const resizeOpts = resizeEnabled && typeof rawResize === "object" ? rawResize : void 0;
20940
+ const preserveAspectRatio = resizeOpts?.alwaysPreserveAspectRatio ?? true;
20941
+ const minWidth = resizeOpts?.minWidth ?? 50;
20942
+ const minHeight = resizeOpts?.minHeight ?? 50;
20943
+ const currentWidth = node.attrs.width;
20944
+ const currentHeight = node.attrs.height;
20945
+ const [widthInput, setWidthInput] = React.useState(currentWidth ? String(currentWidth) : "");
20946
+ const [heightInput, setHeightInput] = React.useState(currentHeight ? String(currentHeight) : "");
20900
20947
  React.useEffect(() => {
20901
20948
  setAltText(node.attrs.alt ?? "");
20902
20949
  }, [node.attrs.alt]);
20950
+ React.useEffect(() => {
20951
+ setWidthInput(node.attrs.width ? String(node.attrs.width) : "");
20952
+ }, [node.attrs.width]);
20953
+ React.useEffect(() => {
20954
+ setHeightInput(node.attrs.height ? String(node.attrs.height) : "");
20955
+ }, [node.attrs.height]);
20903
20956
  React.useEffect(() => {
20904
20957
  if (!isPopoverOpen) return;
20905
20958
  const handleScroll = () => setIsPopoverOpen(false);
20906
20959
  document.addEventListener("scroll", handleScroll, true);
20907
20960
  return () => document.removeEventListener("scroll", handleScroll, true);
20908
20961
  }, [isPopoverOpen]);
20962
+ React.useEffect(() => {
20963
+ return () => {
20964
+ resizeCleanupRef.current?.();
20965
+ };
20966
+ }, []);
20909
20967
  const currentAlign = node.attrs["data-align"];
20910
20968
  function handleAlign(value) {
20911
20969
  const current = node.attrs["data-align"];
@@ -20920,15 +20978,117 @@ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode:
20920
20978
  e.currentTarget.blur();
20921
20979
  }
20922
20980
  }
20981
+ function getAspectRatio() {
20982
+ const img = imgRef.current;
20983
+ if (!img || !img.naturalWidth || !img.naturalHeight) return null;
20984
+ return img.naturalWidth / img.naturalHeight;
20985
+ }
20986
+ function handleWidthCommit() {
20987
+ if (widthInput === "") {
20988
+ updateAttributes2({ width: null, height: null });
20989
+ return;
20990
+ }
20991
+ const num = parseInt(widthInput, 10);
20992
+ if (!isNaN(num) && num >= minWidth) {
20993
+ const ratio = getAspectRatio();
20994
+ if (preserveAspectRatio && ratio) {
20995
+ const newHeight = Math.round(num / ratio);
20996
+ updateAttributes2({ width: num, height: newHeight });
20997
+ } else {
20998
+ updateAttributes2({ width: num });
20999
+ }
21000
+ } else {
21001
+ setWidthInput(node.attrs.width ? String(node.attrs.width) : "");
21002
+ }
21003
+ }
21004
+ function handleHeightCommit() {
21005
+ if (heightInput === "") {
21006
+ updateAttributes2({ height: null, width: null });
21007
+ return;
21008
+ }
21009
+ const num = parseInt(heightInput, 10);
21010
+ if (!isNaN(num) && num >= minHeight) {
21011
+ const ratio = getAspectRatio();
21012
+ if (preserveAspectRatio && ratio) {
21013
+ const newWidth = Math.round(num * ratio);
21014
+ updateAttributes2({ width: newWidth, height: num });
21015
+ } else {
21016
+ updateAttributes2({ height: num });
21017
+ }
21018
+ } else {
21019
+ setHeightInput(node.attrs.height ? String(node.attrs.height) : "");
21020
+ }
21021
+ }
21022
+ const handleResizeStart = React.useCallback(
21023
+ (e) => {
21024
+ e.preventDefault();
21025
+ e.stopPropagation();
21026
+ isResizingRef.current = true;
21027
+ const startX = e.clientX;
21028
+ const img = imgRef.current;
21029
+ if (!img) return;
21030
+ const startWidth = img.offsetWidth;
21031
+ const ratio = img.naturalWidth && img.naturalHeight ? img.naturalWidth / img.naturalHeight : null;
21032
+ function onMouseMove(moveEvent) {
21033
+ const diff = moveEvent.clientX - startX;
21034
+ const newWidth = Math.max(minWidth, startWidth + diff);
21035
+ if (img) {
21036
+ img.style.width = `${newWidth}px`;
21037
+ if (preserveAspectRatio && ratio) {
21038
+ img.style.height = `${Math.round(newWidth / ratio)}px`;
21039
+ }
21040
+ }
21041
+ }
21042
+ function cleanup() {
21043
+ document.removeEventListener("mousemove", onMouseMove);
21044
+ document.removeEventListener("mouseup", onMouseUp);
21045
+ resizeCleanupRef.current = null;
21046
+ }
21047
+ function onMouseUp() {
21048
+ cleanup();
21049
+ if (img) {
21050
+ updateAttributes2({
21051
+ width: Math.round(img.offsetWidth),
21052
+ height: Math.round(img.offsetHeight)
21053
+ });
21054
+ }
21055
+ requestAnimationFrame(() => {
21056
+ isResizingRef.current = false;
21057
+ });
21058
+ }
21059
+ document.addEventListener("mousemove", onMouseMove);
21060
+ document.addEventListener("mouseup", onMouseUp);
21061
+ resizeCleanupRef.current = cleanup;
21062
+ },
21063
+ [updateAttributes2, preserveAspectRatio, minWidth]
21064
+ );
20923
21065
  return /* @__PURE__ */ jsxRuntime.jsx(NodeViewWrapper, { "data-drag-handle": true, "data-align": node.attrs["data-align"] ?? void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Popover.Root, { open: isPopoverOpen, onOpenChange: setIsPopoverOpen, children: [
20924
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Popover.Anchor, { children: /* @__PURE__ */ jsxRuntime.jsx(
20925
- "img",
21066
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Popover.Anchor, { children: /* @__PURE__ */ jsxRuntime.jsxs(
21067
+ "div",
20926
21068
  {
20927
- src: node.attrs.src,
20928
- alt: node.attrs.alt ?? "",
20929
- style: { maxWidth: "100%", display: "block" },
20930
- draggable: false,
20931
- onClick: () => setIsPopoverOpen(true)
21069
+ className: "image-wrapper",
21070
+ "data-selected": selected || isPopoverOpen || void 0,
21071
+ children: [
21072
+ /* @__PURE__ */ jsxRuntime.jsx(
21073
+ "img",
21074
+ {
21075
+ ref: imgRef,
21076
+ src: node.attrs.src,
21077
+ alt: node.attrs.alt ?? "",
21078
+ style: {
21079
+ display: "block",
21080
+ maxWidth: "100%",
21081
+ width: currentWidth ? `${currentWidth}px` : void 0,
21082
+ height: currentHeight ? `${currentHeight}px` : "auto"
21083
+ },
21084
+ draggable: false,
21085
+ onClick: () => {
21086
+ if (!isResizingRef.current) setIsPopoverOpen(true);
21087
+ }
21088
+ }
21089
+ ),
21090
+ resizeEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "image-resize-handle", onMouseDown: handleResizeStart })
21091
+ ]
20932
21092
  }
20933
21093
  ) }),
20934
21094
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Popover.Content, { side: "bottom", children: [
@@ -20967,6 +21127,43 @@ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode:
20967
21127
  }
20968
21128
  )
20969
21129
  ] }),
21130
+ resizeEnabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "4px", padding: "8px", paddingBottom: "0" }, children: [
21131
+ /* @__PURE__ */ jsxRuntime.jsx(
21132
+ designSystem.TextInput,
21133
+ {
21134
+ type: "number",
21135
+ placeholder: "W",
21136
+ value: widthInput,
21137
+ onChange: (e) => setWidthInput(e.target.value),
21138
+ onBlur: handleWidthCommit,
21139
+ onKeyDown: handleKeyDown2,
21140
+ "aria-label": formatMessage({ id: "tiptap-editor.image.width", defaultMessage: "Width (px)" }),
21141
+ style: { minWidth: "62px", flexGrow: 1 }
21142
+ }
21143
+ ),
21144
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "1.1rem", color: "#999" }, children: "×" }),
21145
+ /* @__PURE__ */ jsxRuntime.jsx(
21146
+ designSystem.TextInput,
21147
+ {
21148
+ type: "number",
21149
+ placeholder: "H",
21150
+ value: heightInput,
21151
+ onChange: (e) => setHeightInput(e.target.value),
21152
+ onBlur: handleHeightCommit,
21153
+ onKeyDown: handleKeyDown2,
21154
+ "aria-label": formatMessage({ id: "tiptap-editor.image.height", defaultMessage: "Height (px)" }),
21155
+ style: { minWidth: "62px", flexGrow: 1 }
21156
+ }
21157
+ ),
21158
+ (currentWidth || currentHeight) && /* @__PURE__ */ jsxRuntime.jsx(
21159
+ designSystem.IconButton,
21160
+ {
21161
+ onClick: () => updateAttributes2({ width: null, height: null }),
21162
+ label: formatMessage({ id: "tiptap-editor.image.resetSize", defaultMessage: "Reset size" }),
21163
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {})
21164
+ }
21165
+ )
21166
+ ] }),
20970
21167
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", padding: "8px" }, children: [
20971
21168
  /* @__PURE__ */ jsxRuntime.jsx(
20972
21169
  designSystem.TextInput,
@@ -20992,12 +21189,21 @@ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode:
20992
21189
  ] }) });
20993
21190
  }
20994
21191
  function ImageNodeViewReadOnly({ node }) {
21192
+ const rawWidth = node.attrs.width;
21193
+ const rawHeight = node.attrs.height;
21194
+ const width = typeof rawWidth === "number" ? rawWidth : null;
21195
+ const height = typeof rawHeight === "number" ? rawHeight : null;
20995
21196
  return /* @__PURE__ */ jsxRuntime.jsx(NodeViewWrapper, { "data-drag-handle": true, "data-align": node.attrs["data-align"] ?? void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
20996
21197
  "img",
20997
21198
  {
20998
21199
  src: node.attrs.src,
20999
21200
  alt: node.attrs.alt ?? "",
21000
- style: { maxWidth: "100%", display: "block" },
21201
+ style: {
21202
+ maxWidth: width ? void 0 : "100%",
21203
+ display: "block",
21204
+ width: width ? `${width}px` : void 0,
21205
+ height: height ? `${height}px` : void 0
21206
+ },
21001
21207
  draggable: false
21002
21208
  }
21003
21209
  ) });
@@ -29500,7 +29706,8 @@ function buildExtensions(config) {
29500
29706
  extensions.push(index_default.configure({ multicolor: true }));
29501
29707
  }
29502
29708
  if (isFeatureEnabled(config.mediaLibrary)) {
29503
- extensions.push(StrapiImage);
29709
+ const mediaOpts = getFeatureOptions(config.mediaLibrary, {});
29710
+ extensions.push(StrapiImage.configure(mediaOpts ?? {}));
29504
29711
  } else {
29505
29712
  extensions.push(StrapiImage.configure({ enableContentCheck: true }));
29506
29713
  }
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
2
- import React, { useRef, useState, useDebugValue, useEffect, useLayoutEffect, forwardRef, createRef, memo, createElement, createContext, version, useContext, useMemo, Component } from "react";
2
+ import React, { useRef, useState, useDebugValue, useEffect, useLayoutEffect, forwardRef, createRef, memo, createElement, createContext, version, useContext, useMemo, Component, useCallback } from "react";
3
3
  import { useIntl } from "react-intl";
4
4
  import { Field, Box, Status, Typography, Flex, Button, Tooltip, Dialog, TextInput, SingleSelect, SingleSelectOption, Popover, IconButton } from "@strapi/design-system";
5
5
  import ReactDOM, { flushSync } from "react-dom";
6
6
  import styled from "styled-components";
7
7
  import { useField, useFetchClient } from "@strapi/strapi/admin";
8
- import { Quotes, Code as Code$1, NumberList, BulletList as BulletList$1, StrikeThrough, Underline as Underline$1, Italic as Italic$1, Bold as Bold$1, Link as Link$1, Trash, Image as Image$1, GridNine } from "@strapi/icons";
9
- import { g as getMediaLibraryComponent, a as getThemeCache } from "./index-sX8SY6P-.mjs";
8
+ import { Quotes, Code as Code$1, NumberList, BulletList as BulletList$1, StrikeThrough, Underline as Underline$1, Italic as Italic$1, Bold as Bold$1, Link as Link$1, Cross, Trash, Image as Image$1, GridNine } from "@strapi/icons";
9
+ import { g as getMediaLibraryComponent, a as getThemeCache } from "./index-BVPd2eP4.mjs";
10
10
  var shim = { exports: {} };
11
11
  var useSyncExternalStoreShim_production = {};
12
12
  /**
@@ -19903,19 +19903,53 @@ const TiptapInputStyles = styled.div`
19903
19903
  margin: 0.75em 0;
19904
19904
  }
19905
19905
 
19906
- /* Image alignment — margin-based, no float */
19907
- [data-align="center"] img {
19908
- margin-left: auto;
19909
- margin-right: auto;
19906
+ /* Image alignment — text-align on the node wrapper centres the inline-block image container */
19907
+ [data-align="center"] {
19908
+ text-align: center;
19910
19909
  }
19911
19910
 
19912
- [data-align="right"] img {
19913
- margin-left: auto;
19914
- margin-right: 0;
19911
+ [data-align="right"] {
19912
+ text-align: right;
19915
19913
  }
19916
19914
 
19917
19915
  /* data-align="left" and null: natural left flow, no rule needed */
19918
19916
 
19917
+ /* Inline-block wrapper for positioning the resize handle relative to the image */
19918
+ .image-wrapper {
19919
+ position: relative;
19920
+ display: inline-block;
19921
+ max-width: 100%;
19922
+ line-height: 0; /* prevent extra space below img */
19923
+ }
19924
+
19925
+ .image-wrapper img {
19926
+ display: block;
19927
+ max-width: 100%;
19928
+ height: auto;
19929
+ }
19930
+
19931
+ /* Resize handle — bottom-right corner */
19932
+ .image-resize-handle {
19933
+ position: absolute;
19934
+ right: -4px;
19935
+ bottom: -4px;
19936
+ width: 12px;
19937
+ height: 12px;
19938
+ background: #4945ff;
19939
+ border: 2px solid #fff;
19940
+ border-radius: 50%;
19941
+ cursor: nwse-resize;
19942
+ opacity: 0;
19943
+ transition: opacity 0.15s;
19944
+ z-index: 1;
19945
+ }
19946
+
19947
+ .image-wrapper:hover .image-resize-handle,
19948
+ .image-wrapper[data-selected] .image-resize-handle,
19949
+ .ProseMirror-selectednode .image-resize-handle {
19950
+ opacity: 1;
19951
+ }
19952
+
19919
19953
  // Source: https://tiptap.dev/docs/editor/extensions/nodes/table
19920
19954
 
19921
19955
  .ProseMirror {
@@ -20887,19 +20921,43 @@ function TextAlignRight(props) {
20887
20921
  }
20888
20922
  );
20889
20923
  }
20890
- function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode: deleteNode2 }) {
20924
+ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode: deleteNode2, selected, extension }) {
20891
20925
  const { formatMessage } = useIntl();
20892
20926
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
20893
20927
  const [altText, setAltText] = useState(node.attrs.alt ?? "");
20928
+ const imgRef = useRef(null);
20929
+ const isResizingRef = useRef(false);
20930
+ const resizeCleanupRef = useRef(null);
20931
+ const rawResize = extension.options.resize;
20932
+ const resizeEnabled = typeof rawResize === "object" && !!rawResize ? rawResize.enabled : !!rawResize;
20933
+ const resizeOpts = resizeEnabled && typeof rawResize === "object" ? rawResize : void 0;
20934
+ const preserveAspectRatio = resizeOpts?.alwaysPreserveAspectRatio ?? true;
20935
+ const minWidth = resizeOpts?.minWidth ?? 50;
20936
+ const minHeight = resizeOpts?.minHeight ?? 50;
20937
+ const currentWidth = node.attrs.width;
20938
+ const currentHeight = node.attrs.height;
20939
+ const [widthInput, setWidthInput] = useState(currentWidth ? String(currentWidth) : "");
20940
+ const [heightInput, setHeightInput] = useState(currentHeight ? String(currentHeight) : "");
20894
20941
  useEffect(() => {
20895
20942
  setAltText(node.attrs.alt ?? "");
20896
20943
  }, [node.attrs.alt]);
20944
+ useEffect(() => {
20945
+ setWidthInput(node.attrs.width ? String(node.attrs.width) : "");
20946
+ }, [node.attrs.width]);
20947
+ useEffect(() => {
20948
+ setHeightInput(node.attrs.height ? String(node.attrs.height) : "");
20949
+ }, [node.attrs.height]);
20897
20950
  useEffect(() => {
20898
20951
  if (!isPopoverOpen) return;
20899
20952
  const handleScroll = () => setIsPopoverOpen(false);
20900
20953
  document.addEventListener("scroll", handleScroll, true);
20901
20954
  return () => document.removeEventListener("scroll", handleScroll, true);
20902
20955
  }, [isPopoverOpen]);
20956
+ useEffect(() => {
20957
+ return () => {
20958
+ resizeCleanupRef.current?.();
20959
+ };
20960
+ }, []);
20903
20961
  const currentAlign = node.attrs["data-align"];
20904
20962
  function handleAlign(value) {
20905
20963
  const current = node.attrs["data-align"];
@@ -20914,15 +20972,117 @@ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode:
20914
20972
  e.currentTarget.blur();
20915
20973
  }
20916
20974
  }
20975
+ function getAspectRatio() {
20976
+ const img = imgRef.current;
20977
+ if (!img || !img.naturalWidth || !img.naturalHeight) return null;
20978
+ return img.naturalWidth / img.naturalHeight;
20979
+ }
20980
+ function handleWidthCommit() {
20981
+ if (widthInput === "") {
20982
+ updateAttributes2({ width: null, height: null });
20983
+ return;
20984
+ }
20985
+ const num = parseInt(widthInput, 10);
20986
+ if (!isNaN(num) && num >= minWidth) {
20987
+ const ratio = getAspectRatio();
20988
+ if (preserveAspectRatio && ratio) {
20989
+ const newHeight = Math.round(num / ratio);
20990
+ updateAttributes2({ width: num, height: newHeight });
20991
+ } else {
20992
+ updateAttributes2({ width: num });
20993
+ }
20994
+ } else {
20995
+ setWidthInput(node.attrs.width ? String(node.attrs.width) : "");
20996
+ }
20997
+ }
20998
+ function handleHeightCommit() {
20999
+ if (heightInput === "") {
21000
+ updateAttributes2({ height: null, width: null });
21001
+ return;
21002
+ }
21003
+ const num = parseInt(heightInput, 10);
21004
+ if (!isNaN(num) && num >= minHeight) {
21005
+ const ratio = getAspectRatio();
21006
+ if (preserveAspectRatio && ratio) {
21007
+ const newWidth = Math.round(num * ratio);
21008
+ updateAttributes2({ width: newWidth, height: num });
21009
+ } else {
21010
+ updateAttributes2({ height: num });
21011
+ }
21012
+ } else {
21013
+ setHeightInput(node.attrs.height ? String(node.attrs.height) : "");
21014
+ }
21015
+ }
21016
+ const handleResizeStart = useCallback(
21017
+ (e) => {
21018
+ e.preventDefault();
21019
+ e.stopPropagation();
21020
+ isResizingRef.current = true;
21021
+ const startX = e.clientX;
21022
+ const img = imgRef.current;
21023
+ if (!img) return;
21024
+ const startWidth = img.offsetWidth;
21025
+ const ratio = img.naturalWidth && img.naturalHeight ? img.naturalWidth / img.naturalHeight : null;
21026
+ function onMouseMove(moveEvent) {
21027
+ const diff = moveEvent.clientX - startX;
21028
+ const newWidth = Math.max(minWidth, startWidth + diff);
21029
+ if (img) {
21030
+ img.style.width = `${newWidth}px`;
21031
+ if (preserveAspectRatio && ratio) {
21032
+ img.style.height = `${Math.round(newWidth / ratio)}px`;
21033
+ }
21034
+ }
21035
+ }
21036
+ function cleanup() {
21037
+ document.removeEventListener("mousemove", onMouseMove);
21038
+ document.removeEventListener("mouseup", onMouseUp);
21039
+ resizeCleanupRef.current = null;
21040
+ }
21041
+ function onMouseUp() {
21042
+ cleanup();
21043
+ if (img) {
21044
+ updateAttributes2({
21045
+ width: Math.round(img.offsetWidth),
21046
+ height: Math.round(img.offsetHeight)
21047
+ });
21048
+ }
21049
+ requestAnimationFrame(() => {
21050
+ isResizingRef.current = false;
21051
+ });
21052
+ }
21053
+ document.addEventListener("mousemove", onMouseMove);
21054
+ document.addEventListener("mouseup", onMouseUp);
21055
+ resizeCleanupRef.current = cleanup;
21056
+ },
21057
+ [updateAttributes2, preserveAspectRatio, minWidth]
21058
+ );
20917
21059
  return /* @__PURE__ */ jsx(NodeViewWrapper, { "data-drag-handle": true, "data-align": node.attrs["data-align"] ?? void 0, children: /* @__PURE__ */ jsxs(Popover.Root, { open: isPopoverOpen, onOpenChange: setIsPopoverOpen, children: [
20918
- /* @__PURE__ */ jsx(Popover.Anchor, { children: /* @__PURE__ */ jsx(
20919
- "img",
21060
+ /* @__PURE__ */ jsx(Popover.Anchor, { children: /* @__PURE__ */ jsxs(
21061
+ "div",
20920
21062
  {
20921
- src: node.attrs.src,
20922
- alt: node.attrs.alt ?? "",
20923
- style: { maxWidth: "100%", display: "block" },
20924
- draggable: false,
20925
- onClick: () => setIsPopoverOpen(true)
21063
+ className: "image-wrapper",
21064
+ "data-selected": selected || isPopoverOpen || void 0,
21065
+ children: [
21066
+ /* @__PURE__ */ jsx(
21067
+ "img",
21068
+ {
21069
+ ref: imgRef,
21070
+ src: node.attrs.src,
21071
+ alt: node.attrs.alt ?? "",
21072
+ style: {
21073
+ display: "block",
21074
+ maxWidth: "100%",
21075
+ width: currentWidth ? `${currentWidth}px` : void 0,
21076
+ height: currentHeight ? `${currentHeight}px` : "auto"
21077
+ },
21078
+ draggable: false,
21079
+ onClick: () => {
21080
+ if (!isResizingRef.current) setIsPopoverOpen(true);
21081
+ }
21082
+ }
21083
+ ),
21084
+ resizeEnabled && /* @__PURE__ */ jsx("div", { className: "image-resize-handle", onMouseDown: handleResizeStart })
21085
+ ]
20926
21086
  }
20927
21087
  ) }),
20928
21088
  /* @__PURE__ */ jsxs(Popover.Content, { side: "bottom", children: [
@@ -20961,6 +21121,43 @@ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode:
20961
21121
  }
20962
21122
  )
20963
21123
  ] }),
21124
+ resizeEnabled && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "4px", padding: "8px", paddingBottom: "0" }, children: [
21125
+ /* @__PURE__ */ jsx(
21126
+ TextInput,
21127
+ {
21128
+ type: "number",
21129
+ placeholder: "W",
21130
+ value: widthInput,
21131
+ onChange: (e) => setWidthInput(e.target.value),
21132
+ onBlur: handleWidthCommit,
21133
+ onKeyDown: handleKeyDown2,
21134
+ "aria-label": formatMessage({ id: "tiptap-editor.image.width", defaultMessage: "Width (px)" }),
21135
+ style: { minWidth: "62px", flexGrow: 1 }
21136
+ }
21137
+ ),
21138
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "1.1rem", color: "#999" }, children: "×" }),
21139
+ /* @__PURE__ */ jsx(
21140
+ TextInput,
21141
+ {
21142
+ type: "number",
21143
+ placeholder: "H",
21144
+ value: heightInput,
21145
+ onChange: (e) => setHeightInput(e.target.value),
21146
+ onBlur: handleHeightCommit,
21147
+ onKeyDown: handleKeyDown2,
21148
+ "aria-label": formatMessage({ id: "tiptap-editor.image.height", defaultMessage: "Height (px)" }),
21149
+ style: { minWidth: "62px", flexGrow: 1 }
21150
+ }
21151
+ ),
21152
+ (currentWidth || currentHeight) && /* @__PURE__ */ jsx(
21153
+ IconButton,
21154
+ {
21155
+ onClick: () => updateAttributes2({ width: null, height: null }),
21156
+ label: formatMessage({ id: "tiptap-editor.image.resetSize", defaultMessage: "Reset size" }),
21157
+ children: /* @__PURE__ */ jsx(Cross, {})
21158
+ }
21159
+ )
21160
+ ] }),
20964
21161
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", padding: "8px" }, children: [
20965
21162
  /* @__PURE__ */ jsx(
20966
21163
  TextInput,
@@ -20986,12 +21183,21 @@ function ImageNodeView({ node, updateAttributes: updateAttributes2, deleteNode:
20986
21183
  ] }) });
20987
21184
  }
20988
21185
  function ImageNodeViewReadOnly({ node }) {
21186
+ const rawWidth = node.attrs.width;
21187
+ const rawHeight = node.attrs.height;
21188
+ const width = typeof rawWidth === "number" ? rawWidth : null;
21189
+ const height = typeof rawHeight === "number" ? rawHeight : null;
20989
21190
  return /* @__PURE__ */ jsx(NodeViewWrapper, { "data-drag-handle": true, "data-align": node.attrs["data-align"] ?? void 0, children: /* @__PURE__ */ jsx(
20990
21191
  "img",
20991
21192
  {
20992
21193
  src: node.attrs.src,
20993
21194
  alt: node.attrs.alt ?? "",
20994
- style: { maxWidth: "100%", display: "block" },
21195
+ style: {
21196
+ maxWidth: width ? void 0 : "100%",
21197
+ display: "block",
21198
+ width: width ? `${width}px` : void 0,
21199
+ height: height ? `${height}px` : void 0
21200
+ },
20995
21201
  draggable: false
20996
21202
  }
20997
21203
  ) });
@@ -29494,7 +29700,8 @@ function buildExtensions(config) {
29494
29700
  extensions.push(index_default.configure({ multicolor: true }));
29495
29701
  }
29496
29702
  if (isFeatureEnabled(config.mediaLibrary)) {
29497
- extensions.push(StrapiImage);
29703
+ const mediaOpts = getFeatureOptions(config.mediaLibrary, {});
29704
+ extensions.push(StrapiImage.configure(mediaOpts ?? {}));
29498
29705
  } else {
29499
29706
  extensions.push(StrapiImage.configure({ enableContentCheck: true }));
29500
29707
  }
@@ -146,7 +146,7 @@ const richTextField = {
146
146
  },
147
147
  icon: Paragraph,
148
148
  components: {
149
- Input: async () => import("./RichTextInput-B8CLPOyo.mjs").then((m) => ({ default: m.default }))
149
+ Input: async () => import("./RichTextInput-B4D0Djcf.mjs").then((m) => ({ default: m.default }))
150
150
  },
151
151
  options: {
152
152
  advanced: [
@@ -147,7 +147,7 @@ const richTextField = {
147
147
  },
148
148
  icon: icons.Paragraph,
149
149
  components: {
150
- Input: async () => Promise.resolve().then(() => require("./RichTextInput-EtL-yFqV.js")).then((m) => ({ default: m.default }))
150
+ Input: async () => Promise.resolve().then(() => require("./RichTextInput-1uZSm3Nc.js")).then((m) => ({ default: m.default }))
151
151
  },
152
152
  options: {
153
153
  advanced: [
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-yXpX_VsO.js");
2
+ const index = require("../_chunks/index-CTcKIvYv.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-sX8SY6P-.mjs";
1
+ import { i } from "../_chunks/index-BVPd2eP4.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -1,3 +1,3 @@
1
1
  import type { NodeViewProps } from '@tiptap/react';
2
- export declare function ImageNodeView({ node, updateAttributes, deleteNode }: NodeViewProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ImageNodeView({ node, updateAttributes, deleteNode, selected, extension }: NodeViewProps): import("react/jsx-runtime").JSX.Element;
3
3
  export default ImageNodeView;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.2.0",
2
+ "version": "1.2.1",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "plugin",