@harshtalks/image-tiptap
Version:
A Tiptap extension for React.js to add image resize and alignment options to the image extension.
287 lines (277 loc) • 8.64 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
ImageAligner: () => image_aligner_default,
ImageExtension: () => image_extension_default
});
module.exports = __toCommonJS(src_exports);
// src/components/image-aligner.tsx
var import_react2 = require("@tiptap/react");
var React2 = __toESM(require("react"), 1);
// src/constants.ts
var DATA_ALIGNMENT_KEY = "data-alignment";
var IMAGE_NODE = "image";
var PROSE_ACTIVE_NODE = ".ProseMirror-selectednode";
// src/components/image-aligner.tsx
var import_react_slot = require("@radix-ui/react-slot");
// src/components/image-resize.tsx
var import_react = require("@tiptap/react");
var import_react_moveable = __toESM(require("react-moveable"), 1);
var React = __toESM(require("react"), 1);
var ImageResizer = ({
editor,
onResize,
onResizeEnd,
onScale,
...moveableProps
}) => {
if (!editor) {
return null;
}
if (!editor.isActive(IMAGE_NODE)) {
return null;
}
const updateImageSize = () => {
const imageNodeInfo = document.querySelector(PROSE_ACTIVE_NODE);
if (imageNodeInfo) {
const selection = editor.state.selection;
const setImage = editor.commands.setImage;
const width = Number(imageNodeInfo.style.width.replace("px", ""));
const height = Number(imageNodeInfo.style.height.replace("px", ""));
setImage({
src: imageNodeInfo.src,
[DATA_ALIGNMENT_KEY]: imageNodeInfo.getAttribute(
DATA_ALIGNMENT_KEY
),
...imageNodeInfo.alt && { alt: imageNodeInfo.alt },
...imageNodeInfo.title && { title: imageNodeInfo.title },
width,
height
});
editor.commands.setNodeSelection(selection.from);
}
};
return /* @__PURE__ */ React.createElement(
import_react_moveable.default,
{
target: document.querySelector(PROSE_ACTIVE_NODE),
container: null,
origin: false,
edge: false,
throttleDrag: 0,
keepRatio: true,
resizable: true,
throttleResize: 0,
onResize: (e) => {
const {
target,
width,
height,
// dist,
delta
} = e;
if (delta[0]) target.style.width = `${width}px`;
if (delta[1]) target.style.height = `${height}px`;
onResize?.(e);
},
onResizeEnd: (e) => {
updateImageSize();
onResizeEnd?.(e);
},
scalable: true,
throttleScale: 0,
renderDirections: ["w", "e", "s", "n"],
onScale: (e) => {
const {
target,
// scale,
// dist,
// delta,
transform
} = e;
target.style.transform = transform;
onScale?.(e);
},
...moveableProps
}
);
};
var image_resize_default = ImageResizer;
// src/components/image-aligner.tsx
var alignerContext = React2.createContext(null);
var Root = ({
children,
editor: propEditor,
...resizerProps
}) => {
const { editor: editorContext } = (0, import_react2.useCurrentEditor)() || null;
const editor = propEditor || editorContext;
return /* @__PURE__ */ React2.createElement(alignerContext.Provider, { value: editor }, children, /* @__PURE__ */ React2.createElement(image_resize_default, { editor, ...resizerProps }));
};
var AlignMenu = ({
shouldShow,
children,
...props
}) => {
const editor = React2.useContext(alignerContext);
if (!editor) {
return null;
}
return /* @__PURE__ */ React2.createElement(
import_react2.BubbleMenu,
{
editor,
...props,
shouldShow: (args) => {
const { editor: editor2 } = args;
return editor2.isActive("image") && (shouldShow?.(args) || true);
}
},
children
);
};
var Items = React2.forwardRef(
(props, ref) => {
return /* @__PURE__ */ React2.createElement("div", { ref, ...props });
}
);
Items.displayName = "ImageAligner.Items";
var Item = React2.forwardRef(
({ onClick, alignment, asChild, ...props }, ref) => {
const editor = React2.useContext(alignerContext);
const Component = asChild ? import_react_slot.Slot : "button";
if (!editor) {
return null;
}
const getImageInfo = () => {
if (editor.isActive(IMAGE_NODE)) {
const imageNodeInfo = document.querySelector(PROSE_ACTIVE_NODE);
return imageNodeInfo;
}
return null;
};
const isItemActive = () => {
const imageNodeInfo = getImageInfo();
if (imageNodeInfo) {
const imageAlignment = imageNodeInfo.getAttribute(DATA_ALIGNMENT_KEY);
if (imageAlignment === alignment) {
return true;
} else {
return false;
}
}
return false;
};
const updateImageAlignment = () => {
const imageNodeInfo = getImageInfo();
if (imageNodeInfo) {
const selection = editor.state.selection;
const setImage = editor.commands.setImage;
setImage({
src: imageNodeInfo.src,
...imageNodeInfo.alt && { alt: imageNodeInfo.alt },
...imageNodeInfo.title && { title: imageNodeInfo.title },
...imageNodeInfo.width && { width: imageNodeInfo.width },
...imageNodeInfo.height && { height: imageNodeInfo.height },
[DATA_ALIGNMENT_KEY]: alignment
});
editor.commands.setNodeSelection(selection.from);
}
};
return /* @__PURE__ */ React2.createElement(
Component,
{
ref,
...props,
onClick: (e) => {
updateImageAlignment();
if (onClick) {
onClick(e);
}
},
"data-active": isItemActive() ? "true" : null,
"data-alignment": alignment
}
);
}
);
Item.displayName = "ImageAligner.Item";
var ImageAligner = {
Root,
AlignMenu,
Items,
Item
};
var image_aligner_default = ImageAligner;
// src/extensions/image-extension.ts
var import_extension_image = __toESM(require("@tiptap/extension-image"), 1);
// src/utils.ts
var alignmentVariants = {
center: `margin: 0 auto;`,
left: `margin-right: auto; margin-top: 0;`,
right: `margin-left: auto; margin-top: 0;`
};
// src/extensions/image-extension.ts
var ImageExtension = import_extension_image.default.extend({
// Adding name to the extension
name: IMAGE_NODE,
// we will extend the base image extension to include few custom attirbutes
// this custom attributes will appear in the DOM for the given image.
addAttributes() {
return {
...this.parent?.(),
// Adding custom attributes
// Custom Attribute for height
height: {
default: null
},
// Custom Attribute for width
width: {
default: null
},
// this will be used to determine current alignment of the image
[DATA_ALIGNMENT_KEY]: {
default: "center",
// use current alignment to set the style attribute
renderHTML: (attributes) => ({
[DATA_ALIGNMENT_KEY]: attributes[DATA_ALIGNMENT_KEY],
style: alignmentVariants[attributes[DATA_ALIGNMENT_KEY]]
})
}
};
}
});
var image_extension_default = ImageExtension;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ImageAligner,
ImageExtension
});