@pentestpad/tiptap-extension-figure 1.0.0

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.
Files changed (32) hide show
  1. package/README.md +39 -0
  2. package/dist/component/tip-tap-image-resize-with-caption.d.ts +5 -0
  3. package/dist/index.cjs.js +452 -0
  4. package/dist/index.cjs.js.map +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +448 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/utils/add-caption-controls.util.d.ts +1 -0
  9. package/dist/utils/add-image-alignment-controls.util.d.ts +1 -0
  10. package/dist/utils/add-image-resize-controls.util.d.ts +1 -0
  11. package/dist/utils/is-mobile-screen.util.d.ts +1 -0
  12. package/dist/utils/remove-image-controls.util.d.ts +1 -0
  13. package/dist/utils/replace-element.util.d.ts +2 -0
  14. package/package.json +33 -0
  15. package/rollup.config.js +48 -0
  16. package/src/assets/icons/closed-caption-add.svg +1 -0
  17. package/src/assets/icons/delete.svg +1 -0
  18. package/src/assets/icons/format-align-center.svg +1 -0
  19. package/src/assets/icons/format-align-left.svg +1 -0
  20. package/src/assets/icons/format-align-right.svg +1 -0
  21. package/src/assets/icons/svg.d.ts +4 -0
  22. package/src/assets/styles/styles.css +85 -0
  23. package/src/assets/styles/styles.d.ts +4 -0
  24. package/src/component/tip-tap-image-resize-with-caption.ts +300 -0
  25. package/src/index.ts +3 -0
  26. package/src/utils/add-caption-controls.util.ts +51 -0
  27. package/src/utils/add-image-alignment-controls.util.ts +62 -0
  28. package/src/utils/add-image-resize-controls.util.ts +104 -0
  29. package/src/utils/is-mobile-screen.util.ts +1 -0
  30. package/src/utils/remove-image-controls.util.ts +22 -0
  31. package/src/utils/replace-element.util.ts +44 -0
  32. package/tsconfig.json +18 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # tiptap-extension-figure
2
+
3
+ An extension for Tiptap that allows you to add and edit captions for images as well as align and resize them.
4
+
5
+ [Tiptap](https://tiptap.dev/) is a suite of open source content editing and real-time collaboration tools for developers building apps like Notion or Google Docs.
6
+
7
+ ## Installation
8
+
9
+ You can install it using npm:
10
+
11
+ ```bash
12
+ npm install @pentestpad/tiptap-extension-figure
13
+ ```
14
+
15
+ Keep in mind that this package requires Tiptap 2.x.
16
+
17
+ ## Usage
18
+
19
+ ```javascript
20
+ import StarterKit from "@tiptap/starter-kit";
21
+ import ImageResizeWithCaption from "@pentestpad/tiptap-extension-figure";
22
+ import { EditorContent, useEditor } from "@tiptap/react";
23
+
24
+ const editor = useEditor({
25
+ extensions: [StarterKit, ImageResizeWithCaption],
26
+ content: `
27
+ <h3>Image with caption</h3>
28
+ <figure>
29
+ <img src="https://placehold.co/800x400/orange/white" alt="Random photo of something" title="Who's dat?">
30
+ <figcaption>
31
+ <p>Very <strong>bold</strong> of <i>you</i>!</p>
32
+ </figcaption>
33
+ </figure>
34
+
35
+ <h3>Image without caption</h3>
36
+ <img src="https://placehold.co/800x400/green/white">
37
+ `,
38
+ });
39
+ ```
@@ -0,0 +1,5 @@
1
+ import { ImageOptions } from "@tiptap/extension-image";
2
+ export declare const inputRegex: RegExp;
3
+ declare const TiptapImageFigureExtension: import("@tiptap/core").Node<ImageOptions, any>;
4
+ export { TiptapImageFigureExtension };
5
+ export default TiptapImageFigureExtension;
@@ -0,0 +1,452 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var core = require('@tiptap/core');
6
+ var ImageExtension = require('@tiptap/extension-image');
7
+
8
+ const isMobileScreen = () => document.documentElement.clientWidth < 768;
9
+
10
+ const removeImageControlsAndResetStyles = (clickedElement, wrapperElement, styles) => {
11
+ const containerContainsClickedElement = wrapperElement.contains(clickedElement);
12
+ if (containerContainsClickedElement) {
13
+ return;
14
+ }
15
+ // Remove all custom UI elements and styling
16
+ wrapperElement.classList.remove(styles["active"]);
17
+ const children = Array.from(wrapperElement.children);
18
+ children.forEach((child) => {
19
+ if (child.tagName !== "IMG" && child.tagName !== "FIGCAPTION") {
20
+ wrapperElement.removeChild(child);
21
+ }
22
+ });
23
+ };
24
+
25
+ var leftIcon = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M144-144v-72h672v72H144Zm0-150v-72h480v72H144Zm0-150v-72h672v72H144Zm0-150v-72h480v72H144Zm0-150v-72h672v72H144Z%22%2F%3E%3C%2Fsvg%3E";
26
+
27
+ var centerIcon = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M144-144v-72h672v72H144Zm144-150v-72h384v72H288ZM144-444v-72h672v72H144Zm144-150v-72h384v72H288ZM144-744v-72h672v72H144Z%22%2F%3E%3C%2Fsvg%3E";
28
+
29
+ var rightIcon = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M144-744v-72h672v72H144Zm192%20150v-72h480v72H336ZM144-444v-72h672v72H144Zm192%20150v-72h480v72H336ZM144-144v-72h672v72H144Z%22%2F%3E%3C%2Fsvg%3E";
30
+
31
+ const imageAlignmentControls = [
32
+ {
33
+ type: "left",
34
+ icon: leftIcon,
35
+ styleToApply: "margin: 0 auto 0 0;",
36
+ },
37
+ {
38
+ type: "center",
39
+ icon: centerIcon,
40
+ styleToApply: "margin: 0 auto;",
41
+ },
42
+ {
43
+ type: "right",
44
+ icon: rightIcon,
45
+ styleToApply: "margin: 0 0 0 auto;",
46
+ },
47
+ ];
48
+ const addImageAlignmentControls = (wrapperElement, imageElement, styles, onAlign) => {
49
+ const imageAlignmentContainer = document.createElement("div");
50
+ imageAlignmentContainer.setAttribute("contenteditable", "false");
51
+ imageAlignmentContainer.setAttribute("class", styles["image-alignment-container"]);
52
+ imageAlignmentControls.forEach((imageControl) => {
53
+ const imageAlignmentControl = document.createElement("img");
54
+ imageAlignmentControl.src = imageControl.icon;
55
+ imageAlignmentControl.setAttribute("class", styles["image-alignment-control"]);
56
+ imageAlignmentControl.addEventListener("click", (event) => {
57
+ event.stopPropagation();
58
+ imageElement.style.cssText = `${imageElement.style.cssText} ${imageControl.styleToApply}`;
59
+ onAlign();
60
+ });
61
+ imageAlignmentContainer.appendChild(imageAlignmentControl);
62
+ });
63
+ wrapperElement.appendChild(imageAlignmentContainer);
64
+ };
65
+
66
+ const addImageResizeControls = (wrapperElement, imageElement, isResizing, startX, startWidth, styles, onResize) => {
67
+ const isMobile = isMobileScreen();
68
+ const dotPosition = isMobile ? "-8px" : "-4px";
69
+ const dotSize = isMobile ? 16 : 9;
70
+ const dotsPosition = [
71
+ `top: ${dotPosition}; left: ${dotPosition}; cursor: nwse-resize;`,
72
+ `top: ${dotPosition}; right: ${dotPosition}; cursor: nesw-resize;`,
73
+ `bottom: ${dotPosition}; left: ${dotPosition}; cursor: nesw-resize;`,
74
+ `bottom: ${dotPosition}; right: ${dotPosition}; cursor: nwse-resize;`,
75
+ ];
76
+ Array.from({ length: 4 }, (_, index) => {
77
+ const dotElement = document.createElement("div");
78
+ dotElement.setAttribute("class", styles["dot-element"]);
79
+ dotElement.setAttribute("style", `width: ${dotSize}px; height: ${dotSize}px; ${dotsPosition[index]}`);
80
+ dotElement.addEventListener("mousedown", (e) => {
81
+ e.preventDefault();
82
+ isResizing = true;
83
+ startX = e.clientX;
84
+ startWidth = wrapperElement.offsetWidth;
85
+ const onMouseMove = (event) => {
86
+ if (!isResizing) {
87
+ return;
88
+ }
89
+ const deltaX = index % 2 === 0 ? -(event.clientX - startX) : event.clientX - startX;
90
+ const newWidth = startWidth + deltaX;
91
+ wrapperElement.style.width = newWidth + "px";
92
+ imageElement.style.width = newWidth + "px";
93
+ };
94
+ const onMouseUp = () => {
95
+ if (isResizing) {
96
+ isResizing = false;
97
+ }
98
+ onResize();
99
+ document.removeEventListener("mousemove", onMouseMove);
100
+ document.removeEventListener("mouseup", onMouseUp);
101
+ };
102
+ document.addEventListener("mousemove", onMouseMove);
103
+ document.addEventListener("mouseup", onMouseUp);
104
+ });
105
+ dotElement.addEventListener("touchstart", (e) => {
106
+ e.cancelable && e.preventDefault();
107
+ isResizing = true;
108
+ startX = e.touches[0].clientX;
109
+ startWidth = wrapperElement.offsetWidth;
110
+ const onTouchMove = (e) => {
111
+ if (!isResizing)
112
+ return;
113
+ const deltaX = index % 2 === 0
114
+ ? -(e.touches[0].clientX - startX)
115
+ : e.touches[0].clientX - startX;
116
+ const newWidth = startWidth + deltaX;
117
+ wrapperElement.style.width = newWidth + "px";
118
+ imageElement.style.width = newWidth + "px";
119
+ };
120
+ const onTouchEnd = () => {
121
+ if (isResizing) {
122
+ isResizing = false;
123
+ }
124
+ onResize();
125
+ document.removeEventListener("touchmove", onTouchMove);
126
+ document.removeEventListener("touchend", onTouchEnd);
127
+ };
128
+ document.addEventListener("touchmove", onTouchMove);
129
+ document.addEventListener("touchend", onTouchEnd);
130
+ }, { passive: false });
131
+ wrapperElement.appendChild(dotElement);
132
+ });
133
+ };
134
+
135
+ var closedCaptionAddIcon = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M480-480Zm120%20288H216q-29.7%200-50.85-21.16Q144-234.32%20144-264.04v-432.24Q144-726%20165.15-747T216-768h528q29.7%200%2050.85%2021.15Q816-725.7%20816-696v288h-72v-288H216v432h384v72Zm144%2072v-72h-72v-72h72v-72h72v72h72v72h-72v72h-72ZM293.29-368h111.86Q421-368%20432-378.78q11-10.78%2011-26.72V-443h-56.14v19H312v-112h75v19h56v-37.89q0-16.11-10.64-26.61Q421.73-592%20406-592H293.01q-16.01%200-26.51%2010.71-10.5%2010.7-10.5%2026.52v148.95Q256-390%20266.72-379t26.57%2011Zm261.22%200h112.55q15.94%200%2026.44-10.78Q704-389.56%20704-405.5V-443h-56.14v19H573v-112h75v19h56v-37.89q0-16.11-10.72-26.61T666.71-592H554.85Q539-592%20528-581.29q-11%2010.7-11%2026.52v148.95Q517-390%20527.79-379q10.78%2011%2026.72%2011Z%22%2F%3E%3C%2Fsvg%3E";
136
+
137
+ var deleteIcon = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M312-144q-29.7%200-50.85-21.15Q240-186.3%20240-216v-480h-48v-72h192v-48h192v48h192v72h-48v479.57Q720-186%20698.85-165T648-144H312Zm336-552H312v480h336v-480ZM384-288h72v-336h-72v336Zm120%200h72v-336h-72v336ZM312-696v480-480Z%22%2F%3E%3C%2Fsvg%3E";
138
+
139
+ const addCaptionControls = (wrapperElement, styles, onCaptionRemove, onCaptionAdd) => {
140
+ const captionControlsContainer = document.createElement("div");
141
+ captionControlsContainer.setAttribute("contenteditable", "false");
142
+ captionControlsContainer.setAttribute("class", styles["caption-controls-element"]);
143
+ // If wrapper element is a figure and the button doesn't already exist, add a button to remove caption
144
+ // Also, wrapper elements needs to become a div
145
+ if (wrapperElement.tagName === "FIGURE" &&
146
+ !wrapperElement.querySelector(styles["remove-caption-button"])) {
147
+ const removeCaptionButton = document.createElement("img");
148
+ removeCaptionButton.src = deleteIcon;
149
+ removeCaptionButton.setAttribute("class", styles["remove-caption-button"]);
150
+ removeCaptionButton.addEventListener("click", (event) => {
151
+ event.stopPropagation();
152
+ onCaptionRemove();
153
+ });
154
+ captionControlsContainer.appendChild(removeCaptionButton);
155
+ wrapperElement.appendChild(captionControlsContainer);
156
+ return;
157
+ }
158
+ // If wrapper element is a div and the button doesn't already exist, add a button to add caption
159
+ if (wrapperElement.tagName === "DIV" &&
160
+ !wrapperElement.querySelector(styles["add-caption-button"])) {
161
+ const addCaptionButton = document.createElement("img");
162
+ addCaptionButton.src = closedCaptionAddIcon;
163
+ addCaptionButton.setAttribute("class", styles["add-caption-button"]);
164
+ addCaptionButton.addEventListener("click", (event) => {
165
+ event.stopPropagation();
166
+ onCaptionAdd();
167
+ });
168
+ captionControlsContainer.appendChild(addCaptionButton);
169
+ wrapperElement.appendChild(captionControlsContainer);
170
+ }
171
+ };
172
+
173
+ const changeFigureToImage = (wrapperElement) => {
174
+ const imageWrapperElement = document.createElement("div");
175
+ const oldAttributes = wrapperElement.attributes;
176
+ const newAttributes = imageWrapperElement.attributes;
177
+ // Copy attributes
178
+ for (let i = 0, len = oldAttributes.length; i < len; i++) {
179
+ newAttributes.setNamedItem(oldAttributes.item(i).cloneNode());
180
+ }
181
+ // Find the image within the old wrapper and set it as the only child of the new wrapper
182
+ const imageElement = wrapperElement.querySelector("img");
183
+ if (imageElement) {
184
+ imageWrapperElement.appendChild(imageElement);
185
+ }
186
+ // Replace wrapperElement with imageWrapperElement
187
+ wrapperElement.replaceWith(imageWrapperElement);
188
+ };
189
+ const changeImageToFigure = (wrapperElement, captionElement) => {
190
+ const figureWrapperElement = document.createElement("figure");
191
+ const oldAttributes = wrapperElement.attributes;
192
+ const newAttributes = figureWrapperElement.attributes;
193
+ // Copy attributes
194
+ for (let i = 0, len = oldAttributes.length; i < len; i++) {
195
+ newAttributes.setNamedItem(oldAttributes.item(i).cloneNode());
196
+ }
197
+ // Find the image within the old wrapper and set it as the only child of the new wrapper
198
+ const imageElement = wrapperElement.querySelector("img");
199
+ if (imageElement) {
200
+ figureWrapperElement.appendChild(imageElement);
201
+ }
202
+ captionElement.innerHTML = "Caption";
203
+ figureWrapperElement.appendChild(captionElement);
204
+ // Replace wrapperElement with figureWrapperElement
205
+ wrapperElement.replaceWith(figureWrapperElement);
206
+ };
207
+
208
+ function styleInject(css, ref) {
209
+ if ( ref === void 0 ) ref = {};
210
+ var insertAt = ref.insertAt;
211
+
212
+ if (typeof document === 'undefined') { return; }
213
+
214
+ var head = document.head || document.getElementsByTagName('head')[0];
215
+ var style = document.createElement('style');
216
+ style.type = 'text/css';
217
+
218
+ if (insertAt === 'top') {
219
+ if (head.firstChild) {
220
+ head.insertBefore(style, head.firstChild);
221
+ } else {
222
+ head.appendChild(style);
223
+ }
224
+ } else {
225
+ head.appendChild(style);
226
+ }
227
+
228
+ if (style.styleSheet) {
229
+ style.styleSheet.cssText = css;
230
+ } else {
231
+ style.appendChild(document.createTextNode(css));
232
+ }
233
+ }
234
+
235
+ var css_248z = ".styles_wrapper-element__SoyDK {\n display: flex;\n flex-direction: column;\n position: relative;\n cursor: pointer;\n width: fit-content;\n}\n.styles_wrapper-element__SoyDK.styles_active__kXAaT {\n border: 2px dashed #6c6c6c;\n}\n\n.styles_figure-element__wBqOu {\n}\n\n.styles_caption-element__-9Bt- {\n text-align: center;\n margin-top: 8px;\n min-height: 1em;\n margin: 0.5rem 2rem;\n padding: 0.5rem 0;\n}\n.styles_caption-element__-9Bt-:hover {\n border-radius: 4px;\n border: 2px dashed #6c6c6c;\n}\n\n.styles_caption-controls-element__Pwjxq {\n position: absolute;\n bottom: 7.5%;\n left: 50%;\n width: 40px;\n height: 35px;\n z-index: 999;\n background-color: rgba(255, 255, 255, 0.7);\n border-radius: 4px;\n border: 2px solid #6c6c6c;\n cursor: pointer;\n transform: translate(-50%, -50%);\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.styles_remove-caption-button__OzMEn,\n.styles_add-caption-button__2rKuu {\n cursor: pointer;\n font-size: 20px;\n}\n\n.styles_remove-caption-button__OzMEn:hover,\n.styles_add-caption-button__2rKuu:hover {\n opacity: 0.5;\n}\n\n.styles_dot-element__TQRBe {\n position: absolute;\n border: 1.5px solid #6c6c6c;\n border-radius: 50%;\n}\n\n.styles_image-alignment-container__5byQ2 {\n position: absolute;\n top: 0%;\n left: 50%;\n width: 100px;\n height: 25px;\n z-index: 999;\n background-color: rgba(255, 255, 255, 0.7);\n border-radius: 4px;\n border: 2px solid #6c6c6c;\n cursor: pointer;\n transform: translate(-50%, -50%);\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0 10px;\n}\n\n.styles_image-alignment-control__r3rTj {\n cursor: pointer;\n font-size: 20px;\n}\n.styles_image-alignment-control__r3rTj:hover {\n opacity: 0.5;\n}\n";
236
+ var styles = {"wrapper-element":"styles_wrapper-element__SoyDK","active":"styles_active__kXAaT","caption-element":"styles_caption-element__-9Bt-","caption-controls-element":"styles_caption-controls-element__Pwjxq","remove-caption-button":"styles_remove-caption-button__OzMEn","add-caption-button":"styles_add-caption-button__2rKuu","dot-element":"styles_dot-element__TQRBe","image-alignment-container":"styles_image-alignment-container__5byQ2","image-alignment-control":"styles_image-alignment-control__r3rTj"};
237
+ styleInject(css_248z);
238
+
239
+ const inputRegex = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
240
+ const TiptapImageFigureExtension = ImageExtension.extend({
241
+ addOptions() {
242
+ return {
243
+ ...this.parent?.(),
244
+ inline: false,
245
+ allowBase64: false,
246
+ HTMLAttributes: {},
247
+ };
248
+ },
249
+ group: "block",
250
+ draggable: true,
251
+ isolating: true,
252
+ content: "inline*",
253
+ addStorage() {
254
+ return {
255
+ elementsVisible: false,
256
+ currentActiveWrapper: null,
257
+ };
258
+ },
259
+ addAttributes() {
260
+ return {
261
+ ...this.parent?.(),
262
+ src: {
263
+ default: null,
264
+ parseHTML: (element) => {
265
+ if (element.tagName === "FIGURE") {
266
+ const img = element.querySelector("img");
267
+ return img?.getAttribute("src");
268
+ }
269
+ return element.getAttribute("src");
270
+ },
271
+ },
272
+ alt: {
273
+ default: null,
274
+ parseHTML: (element) => {
275
+ const img = element.querySelector("img");
276
+ return img?.getAttribute("alt");
277
+ },
278
+ },
279
+ title: {
280
+ default: null,
281
+ parseHTML: (element) => {
282
+ const img = element.querySelector("img");
283
+ return img?.getAttribute("title");
284
+ },
285
+ },
286
+ style: {
287
+ // This style is applied to the wrapper element
288
+ default: "width: 100%; height: auto; cursor: pointer;",
289
+ parseHTML: (element) => {
290
+ const width = element.getAttribute("width");
291
+ // const img = element.querySelector("img");
292
+ // const width = img?.getAttribute("width");
293
+ return width
294
+ ? `width: ${width}px; height: auto; cursor: pointer;`
295
+ : `${element.style.cssText}`;
296
+ },
297
+ },
298
+ };
299
+ },
300
+ parseHTML() {
301
+ return [
302
+ {
303
+ tag: "figure",
304
+ contentElement: "figcaption",
305
+ },
306
+ {
307
+ tag: this.options.allowBase64
308
+ ? "img[src]"
309
+ : 'img[src]:not([src^="data:"])',
310
+ },
311
+ ];
312
+ },
313
+ renderHTML({ HTMLAttributes, node }) {
314
+ const hasCaption = node.content.size > 0;
315
+ if (hasCaption) {
316
+ return [
317
+ "figure",
318
+ this.options.HTMLAttributes,
319
+ [
320
+ "img",
321
+ core.mergeAttributes(HTMLAttributes, {
322
+ draggable: false,
323
+ contenteditable: false,
324
+ }),
325
+ ],
326
+ ["figcaption", 0],
327
+ ];
328
+ }
329
+ return ["img", core.mergeAttributes(HTMLAttributes)];
330
+ },
331
+ addNodeView() {
332
+ return ({ node, editor, getPos }) => {
333
+ const dispatchNodeView = () => {
334
+ if (typeof getPos === "function") {
335
+ const newAttrs = {
336
+ ...node.attrs,
337
+ style: imageElement.style.cssText,
338
+ };
339
+ editor.view.dispatch(editor.view.state.tr.setNodeMarkup(getPos(), null, newAttrs));
340
+ this.storage.elementsVisible = false;
341
+ }
342
+ };
343
+ const { options: { editable }, } = editor;
344
+ const { style } = node.attrs;
345
+ // Create wrapper based on content
346
+ const wrapperElement = document.createElement(node.content.size > 0 ? "figure" : "div");
347
+ wrapperElement.setAttribute("class", styles["wrapper-element"]);
348
+ wrapperElement.setAttribute("style", style);
349
+ const imageElement = document.createElement("img");
350
+ wrapperElement.appendChild(imageElement);
351
+ const captionElement = document.createElement("figcaption");
352
+ // Set up image attributes
353
+ Object.entries(node.attrs).forEach(([key, value]) => {
354
+ if (value === undefined || value === null) {
355
+ return;
356
+ }
357
+ imageElement.setAttribute(key, value);
358
+ });
359
+ // Add caption if needed
360
+ if (node.content.size > 0) {
361
+ captionElement.setAttribute("class", styles["caption-element"]);
362
+ captionElement.setAttribute("contenteditable", "true");
363
+ wrapperElement.appendChild(captionElement);
364
+ }
365
+ if (!editable)
366
+ return { dom: wrapperElement, contentDOM: captionElement };
367
+ // Initialize control variables
368
+ let isResizing = false;
369
+ let startX = 0;
370
+ let startWidth = 0;
371
+ // Handle click on container
372
+ wrapperElement.addEventListener("click", (event) => {
373
+ event.stopPropagation();
374
+ event.preventDefault();
375
+ // If controls are already visible, check if another image or figure is being clicked on
376
+ if (this.storage.elementsVisible) {
377
+ const clickedElement = event.target;
378
+ const currentActiveWrapper = this.storage.currentActiveWrapper;
379
+ // Check if the clicked element is a child of the current active wrapper
380
+ // If it isn't, we must remove the controls from the previous wrapper and continue
381
+ // If it is, we do nothing
382
+ if (currentActiveWrapper &&
383
+ currentActiveWrapper.contains(clickedElement)) {
384
+ return;
385
+ }
386
+ if (currentActiveWrapper) {
387
+ removeImageControlsAndResetStyles(clickedElement, currentActiveWrapper, styles);
388
+ }
389
+ }
390
+ this.storage.currentActiveWrapper = wrapperElement;
391
+ const isMobile = isMobileScreen();
392
+ if (isMobile) {
393
+ const focusedElement = document.querySelector(".ProseMirror-focused");
394
+ focusedElement?.blur();
395
+ }
396
+ // Remove existing controls first
397
+ removeImageControlsAndResetStyles(event.target, wrapperElement, styles);
398
+ // Show new controls
399
+ wrapperElement.classList.toggle(styles["active"]);
400
+ addImageAlignmentControls(wrapperElement, imageElement, styles, () => {
401
+ dispatchNodeView();
402
+ editor.commands.focus();
403
+ });
404
+ addImageResizeControls(wrapperElement, imageElement, isResizing, startX, startWidth, styles, () => {
405
+ dispatchNodeView();
406
+ editor.commands.focus();
407
+ });
408
+ addCaptionControls(wrapperElement, styles, () => {
409
+ // On caption remove
410
+ changeFigureToImage(wrapperElement);
411
+ this.storage.elementsVisible = false;
412
+ }, () => {
413
+ // On caption add
414
+ changeImageToFigure(wrapperElement, captionElement);
415
+ this.storage.elementsVisible = false;
416
+ });
417
+ this.storage.elementsVisible = true;
418
+ });
419
+ // Handle clicks outside
420
+ document.addEventListener("click", (event) => {
421
+ if (!wrapperElement.contains(event.target)) {
422
+ removeImageControlsAndResetStyles(event.target, wrapperElement, styles);
423
+ this.storage.elementsVisible = false;
424
+ this.storage.currentActiveWrapper = null;
425
+ }
426
+ });
427
+ return {
428
+ dom: wrapperElement,
429
+ contentDOM: node.content.size > 0 ? captionElement : undefined,
430
+ ignoreMutation: (mutation) => {
431
+ // We must ignore mutations that happened outside the captionElement
432
+ return !captionElement.contains(mutation.target);
433
+ },
434
+ };
435
+ };
436
+ },
437
+ addInputRules() {
438
+ return [
439
+ core.nodeInputRule({
440
+ find: inputRegex,
441
+ type: this.type,
442
+ getAttributes: (match) => {
443
+ const [, alt, src, title] = match;
444
+ return { src, alt, title };
445
+ },
446
+ }),
447
+ ];
448
+ },
449
+ });
450
+
451
+ exports.default = TiptapImageFigureExtension;
452
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/assets/icons/format-align-left.svg","../src/assets/icons/format-align-center.svg","../src/assets/icons/format-align-right.svg","../src/assets/icons/closed-caption-add.svg","../src/assets/icons/delete.svg","../node_modules/style-inject/dist/style-inject.es.js"],"sourcesContent":["export default \"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M144-144v-72h672v72H144Zm0-150v-72h480v72H144Zm0-150v-72h672v72H144Zm0-150v-72h480v72H144Zm0-150v-72h672v72H144Z%22%2F%3E%3C%2Fsvg%3E\"","export default \"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M144-144v-72h672v72H144Zm144-150v-72h384v72H288ZM144-444v-72h672v72H144Zm144-150v-72h384v72H288ZM144-744v-72h672v72H144Z%22%2F%3E%3C%2Fsvg%3E\"","export default \"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M144-744v-72h672v72H144Zm192%20150v-72h480v72H336ZM144-444v-72h672v72H144Zm192%20150v-72h480v72H336ZM144-144v-72h672v72H144Z%22%2F%3E%3C%2Fsvg%3E\"","export default \"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M480-480Zm120%20288H216q-29.7%200-50.85-21.16Q144-234.32%20144-264.04v-432.24Q144-726%20165.15-747T216-768h528q29.7%200%2050.85%2021.15Q816-725.7%20816-696v288h-72v-288H216v432h384v72Zm144%2072v-72h-72v-72h72v-72h72v72h72v72h-72v72h-72ZM293.29-368h111.86Q421-368%20432-378.78q11-10.78%2011-26.72V-443h-56.14v19H312v-112h75v19h56v-37.89q0-16.11-10.64-26.61Q421.73-592%20406-592H293.01q-16.01%200-26.51%2010.71-10.5%2010.7-10.5%2026.52v148.95Q256-390%20266.72-379t26.57%2011Zm261.22%200h112.55q15.94%200%2026.44-10.78Q704-389.56%20704-405.5V-443h-56.14v19H573v-112h75v19h56v-37.89q0-16.11-10.72-26.61T666.71-592H554.85Q539-592%20528-581.29q-11%2010.7-11%2026.52v148.95Q517-390%20527.79-379q10.78%2011%2026.72%2011Z%22%2F%3E%3C%2Fsvg%3E\"","export default \"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%2220px%22%20viewBox%3D%220%20-960%20960%20960%22%20width%3D%2220px%22%20fill%3D%22%23000000%22%3E%3Cpath%20d%3D%22M312-144q-29.7%200-50.85-21.15Q240-186.3%20240-216v-480h-48v-72h192v-48h192v48h192v72h-48v479.57Q720-186%20698.85-165T648-144H312Zm336-552H312v480h336v-480ZM384-288h72v-336h-72v336Zm120%200h72v-336h-72v336ZM312-696v480-480Z%22%2F%3E%3C%2Fsvg%3E\"","function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,eAAe;;ACAf,iBAAe;;ACAf,gBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACAf,2BAAe;;ACAf,iBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACAf,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;AAC/B,EAAE,KAAK,GAAG,KAAK,MAAM,GAAG,GAAG,GAAG,EAAE;AAChC,EAAE,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ;;AAE7B,EAAE,IAAY,OAAO,QAAQ,KAAK,WAAW,EAAE,EAAE,OAAO;;AAExD,EAAE,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACtE,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;AAC7C,EAAE,KAAK,CAAC,IAAI,GAAG,UAAU;;AAEzB,EAAE,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC1B,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;AACzB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC;AAC/C,KAAK,MAAM;AACX,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AAC7B;AACA,GAAG,MAAM;AACT,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AAC3B;;AAEA,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE;AACxB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG;AAClC,GAAG,MAAM;AACT,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;AACnD;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[5]}
@@ -0,0 +1,2 @@
1
+ import TiptapImageResizeWithCaption from "./component/tip-tap-image-resize-with-caption";
2
+ export default TiptapImageResizeWithCaption;