UNPKG

@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
"use strict"; 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 });