@pentestpad/tiptap-extension-figure 1.0.2 → 1.0.4

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.
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/tiptap-figure.iml" filepath="$PROJECT_DIR$/.idea/tiptap-figure.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/.idea/php.xml ADDED
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="MessDetectorOptionsConfiguration">
4
+ <option name="transferred" value="true" />
5
+ </component>
6
+ <component name="PHPCSFixerOptionsConfiguration">
7
+ <option name="transferred" value="true" />
8
+ </component>
9
+ <component name="PHPCodeSnifferOptionsConfiguration">
10
+ <option name="highlightLevel" value="WARNING" />
11
+ <option name="transferred" value="true" />
12
+ </component>
13
+ <component name="PhpStanOptionsConfiguration">
14
+ <option name="transferred" value="true" />
15
+ </component>
16
+ <component name="PsalmOptionsConfiguration">
17
+ <option name="transferred" value="true" />
18
+ </component>
19
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="inheritedJdk" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # tiptap-extension-figure
1
+ # TipTap Figure Extension
2
+
3
+ <img src="demo.gif">
2
4
 
3
5
  An extension for Tiptap that allows you to add and edit captions for images as well as align and resize them.
4
6
 
package/demo.gif ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pentestpad/tiptap-extension-figure",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "An extension for Tiptap that allows you to add and edit captions for images as well as align and resize them.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.cjs.js",
@@ -18,7 +18,7 @@
18
18
  "@tiptap/core": "^2.11.5",
19
19
  "@tiptap/extension-image": "^2.11.5",
20
20
  "@tiptap/pm": "^2.11.5",
21
- "rollup": "^4.34.7",
21
+ "rollup": "^4.34.8",
22
22
  "rollup-plugin-auto-external": "^2.0.0",
23
23
  "rollup-plugin-postcss": "^4.0.2",
24
24
  "rollup-plugin-sourcemaps": "^0.6.3",
@@ -99,16 +99,16 @@ const TiptapImageFigureExtension = ImageExtension.extend<ImageOptions>({
99
99
  const hasCaption = node.content.size > 0;
100
100
 
101
101
  if (hasCaption) {
102
+ // We should ignore src, alt and title from html attributes if the wrapper element is a figure
103
+ const { src, alt, title, ...figureHtmlAttributes } = HTMLAttributes;
104
+
102
105
  return [
103
106
  "figure",
104
- this.options.HTMLAttributes,
105
- [
106
- "img",
107
- mergeAttributes(HTMLAttributes, {
108
- draggable: false,
109
- contenteditable: false,
110
- }),
111
- ],
107
+ mergeAttributes(figureHtmlAttributes, {
108
+ draggable: false,
109
+ contenteditable: false,
110
+ }),
111
+ ["img", mergeAttributes(HTMLAttributes)],
112
112
  ["figcaption", 0],
113
113
  ];
114
114
  }
@@ -175,6 +175,8 @@ const TiptapImageFigureExtension = ImageExtension.extend<ImageOptions>({
175
175
  event.stopPropagation();
176
176
  event.preventDefault();
177
177
 
178
+ editor.commands.setNodeSelection(getPos());
179
+
178
180
  // If controls are already visible, check if another image or figure is being clicked on
179
181
  if (this.storage.elementsVisible) {
180
182
  const clickedElement = event.target as HTMLElement;
@@ -1,5 +0,0 @@
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;
package/dist/index.cjs.js DELETED
@@ -1,467 +0,0 @@
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
- var state = require('@tiptap/pm/state');
8
-
9
- const isMobileScreen = () => document.documentElement.clientWidth < 768;
10
-
11
- const removeImageControlsAndResetStyles = (clickedElement, wrapperElement, styles) => {
12
- const containerContainsClickedElement = wrapperElement.contains(clickedElement);
13
- if (containerContainsClickedElement) {
14
- return;
15
- }
16
- // Remove all custom UI elements and styling
17
- wrapperElement.classList.remove(styles["active"]);
18
- const children = Array.from(wrapperElement.children);
19
- children.forEach((child) => {
20
- if (child.tagName !== "IMG" && child.tagName !== "FIGCAPTION") {
21
- wrapperElement.removeChild(child);
22
- }
23
- });
24
- };
25
-
26
- 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";
27
-
28
- 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";
29
-
30
- 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";
31
-
32
- const imageAlignmentControls = [
33
- {
34
- type: "left",
35
- icon: leftIcon,
36
- styleToApply: "margin: 0 auto 0 0;",
37
- },
38
- {
39
- type: "center",
40
- icon: centerIcon,
41
- styleToApply: "margin: 0 auto;",
42
- },
43
- {
44
- type: "right",
45
- icon: rightIcon,
46
- styleToApply: "margin: 0 0 0 auto;",
47
- },
48
- ];
49
- const addImageAlignmentControls = (wrapperElement, imageElement, styles, onAlign) => {
50
- const imageAlignmentContainer = document.createElement("div");
51
- imageAlignmentContainer.setAttribute("contenteditable", "false");
52
- imageAlignmentContainer.setAttribute("class", styles["image-alignment-container"]);
53
- imageAlignmentControls.forEach((imageControl) => {
54
- const imageAlignmentControl = document.createElement("img");
55
- imageAlignmentControl.src = imageControl.icon;
56
- imageAlignmentControl.setAttribute("class", styles["image-alignment-control"]);
57
- imageAlignmentControl.addEventListener("click", (event) => {
58
- event.stopPropagation();
59
- imageElement.style.cssText = `${imageElement.style.cssText} ${imageControl.styleToApply}`;
60
- onAlign();
61
- });
62
- imageAlignmentContainer.appendChild(imageAlignmentControl);
63
- });
64
- wrapperElement.appendChild(imageAlignmentContainer);
65
- };
66
-
67
- const addImageResizeControls = (wrapperElement, imageElement, isResizing, startX, startWidth, styles, onResize) => {
68
- const isMobile = isMobileScreen();
69
- const dotPosition = isMobile ? "-8px" : "-4px";
70
- const dotSize = isMobile ? 16 : 9;
71
- const dotsPosition = [
72
- `top: ${dotPosition}; left: ${dotPosition}; cursor: nwse-resize;`,
73
- `top: ${dotPosition}; right: ${dotPosition}; cursor: nesw-resize;`,
74
- `bottom: ${dotPosition}; left: ${dotPosition}; cursor: nesw-resize;`,
75
- `bottom: ${dotPosition}; right: ${dotPosition}; cursor: nwse-resize;`,
76
- ];
77
- Array.from({ length: 4 }, (_, index) => {
78
- const dotElement = document.createElement("div");
79
- dotElement.setAttribute("class", styles["dot-element"]);
80
- dotElement.setAttribute("style", `width: ${dotSize}px; height: ${dotSize}px; ${dotsPosition[index]}`);
81
- dotElement.addEventListener("mousedown", (e) => {
82
- e.preventDefault();
83
- isResizing = true;
84
- startX = e.clientX;
85
- startWidth = wrapperElement.offsetWidth;
86
- const onMouseMove = (event) => {
87
- if (!isResizing) {
88
- return;
89
- }
90
- const deltaX = index % 2 === 0 ? -(event.clientX - startX) : event.clientX - startX;
91
- const newWidth = startWidth + deltaX;
92
- wrapperElement.style.width = newWidth + "px";
93
- imageElement.style.width = newWidth + "px";
94
- };
95
- const onMouseUp = () => {
96
- if (isResizing) {
97
- isResizing = false;
98
- }
99
- onResize();
100
- document.removeEventListener("mousemove", onMouseMove);
101
- document.removeEventListener("mouseup", onMouseUp);
102
- };
103
- document.addEventListener("mousemove", onMouseMove);
104
- document.addEventListener("mouseup", onMouseUp);
105
- });
106
- dotElement.addEventListener("touchstart", (e) => {
107
- e.cancelable && e.preventDefault();
108
- isResizing = true;
109
- startX = e.touches[0].clientX;
110
- startWidth = wrapperElement.offsetWidth;
111
- const onTouchMove = (e) => {
112
- if (!isResizing)
113
- return;
114
- const deltaX = index % 2 === 0
115
- ? -(e.touches[0].clientX - startX)
116
- : e.touches[0].clientX - startX;
117
- const newWidth = startWidth + deltaX;
118
- wrapperElement.style.width = newWidth + "px";
119
- imageElement.style.width = newWidth + "px";
120
- };
121
- const onTouchEnd = () => {
122
- if (isResizing) {
123
- isResizing = false;
124
- }
125
- onResize();
126
- document.removeEventListener("touchmove", onTouchMove);
127
- document.removeEventListener("touchend", onTouchEnd);
128
- };
129
- document.addEventListener("touchmove", onTouchMove);
130
- document.addEventListener("touchend", onTouchEnd);
131
- }, { passive: false });
132
- wrapperElement.appendChild(dotElement);
133
- });
134
- };
135
-
136
- 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";
137
-
138
- 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";
139
-
140
- const addCaptionControls = (wrapperElement, styles, onCaptionRemove, onCaptionAdd) => {
141
- const captionControlsContainer = document.createElement("div");
142
- captionControlsContainer.setAttribute("contenteditable", "false");
143
- captionControlsContainer.setAttribute("class", styles["caption-controls-element"]);
144
- // If wrapper element is a figure and the button doesn't already exist, add a button to remove caption
145
- // Also, wrapper elements needs to become a div
146
- if (wrapperElement.tagName === "FIGURE" &&
147
- !wrapperElement.querySelector(styles["remove-caption-button"])) {
148
- const removeCaptionButton = document.createElement("img");
149
- removeCaptionButton.src = deleteIcon;
150
- removeCaptionButton.setAttribute("class", styles["remove-caption-button"]);
151
- removeCaptionButton.addEventListener("click", (event) => {
152
- event.stopPropagation();
153
- onCaptionRemove();
154
- });
155
- captionControlsContainer.appendChild(removeCaptionButton);
156
- wrapperElement.appendChild(captionControlsContainer);
157
- return;
158
- }
159
- // If wrapper element is a div and the button doesn't already exist, add a button to add caption
160
- if (wrapperElement.tagName === "DIV" &&
161
- !wrapperElement.querySelector(styles["add-caption-button"])) {
162
- const addCaptionButton = document.createElement("img");
163
- addCaptionButton.src = closedCaptionAddIcon;
164
- addCaptionButton.setAttribute("class", styles["add-caption-button"]);
165
- addCaptionButton.addEventListener("click", (event) => {
166
- event.stopPropagation();
167
- onCaptionAdd();
168
- });
169
- captionControlsContainer.appendChild(addCaptionButton);
170
- wrapperElement.appendChild(captionControlsContainer);
171
- }
172
- };
173
-
174
- const changeFigureToImage = (editor, wrapperElement) => {
175
- const { state: state$1, view } = editor;
176
- const { tr } = state$1;
177
- const imageWrapperElement = document.createElement("div");
178
- const oldAttributes = wrapperElement.attributes;
179
- const newAttributes = imageWrapperElement.attributes;
180
- const imageWrapperPosition = view.posAtDOM(wrapperElement, 0);
181
- // Copy attributes
182
- for (let i = 0, len = oldAttributes.length; i < len; i++) {
183
- newAttributes.setNamedItem(oldAttributes.item(i).cloneNode());
184
- }
185
- // Find the image within the old wrapper and set it as the only child of the new wrapper
186
- const imageElement = wrapperElement.querySelector("img");
187
- if (imageElement) {
188
- imageWrapperElement.appendChild(imageElement);
189
- }
190
- // Replace wrapperElement with imageWrapperElement
191
- wrapperElement.replaceWith(imageWrapperElement);
192
- // Focus on the newly replaced wrapper element
193
- const newSelection = state.TextSelection.create(tr.doc, imageWrapperPosition);
194
- const transaction = tr.setSelection(newSelection);
195
- view.dispatch(transaction);
196
- };
197
- const changeImageToFigure = (editor, wrapperElement, captionElement) => {
198
- const { state: state$1, view } = editor;
199
- const { tr } = state$1;
200
- const figureWrapperElement = document.createElement("figure");
201
- const oldAttributes = wrapperElement.attributes;
202
- const newAttributes = figureWrapperElement.attributes;
203
- const figureWrapperPosition = view.posAtDOM(wrapperElement, 0);
204
- // Copy attributes
205
- for (let i = 0, len = oldAttributes.length; i < len; i++) {
206
- newAttributes.setNamedItem(oldAttributes.item(i).cloneNode());
207
- }
208
- // Find the image within the old wrapper and set it as the only child of the new wrapper
209
- const imageElement = wrapperElement.querySelector("img");
210
- if (imageElement) {
211
- figureWrapperElement.appendChild(imageElement);
212
- }
213
- captionElement.innerHTML = "Caption";
214
- figureWrapperElement.appendChild(captionElement);
215
- // Replace wrapperElement with figureWrapperElement
216
- wrapperElement.replaceWith(figureWrapperElement);
217
- // Focus on the newly replaced wrapper element
218
- const newSelection = state.TextSelection.create(tr.doc, figureWrapperPosition);
219
- const transaction = tr.setSelection(newSelection);
220
- view.dispatch(transaction);
221
- };
222
-
223
- function styleInject(css, ref) {
224
- if ( ref === void 0 ) ref = {};
225
- var insertAt = ref.insertAt;
226
-
227
- if (typeof document === 'undefined') { return; }
228
-
229
- var head = document.head || document.getElementsByTagName('head')[0];
230
- var style = document.createElement('style');
231
- style.type = 'text/css';
232
-
233
- if (insertAt === 'top') {
234
- if (head.firstChild) {
235
- head.insertBefore(style, head.firstChild);
236
- } else {
237
- head.appendChild(style);
238
- }
239
- } else {
240
- head.appendChild(style);
241
- }
242
-
243
- if (style.styleSheet) {
244
- style.styleSheet.cssText = css;
245
- } else {
246
- style.appendChild(document.createTextNode(css));
247
- }
248
- }
249
-
250
- 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 margin-inline: 0;\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 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 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";
251
- 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"};
252
- styleInject(css_248z);
253
-
254
- const inputRegex = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
255
- const TiptapImageFigureExtension = ImageExtension.extend({
256
- addOptions() {
257
- return {
258
- ...this.parent?.(),
259
- inline: false,
260
- allowBase64: false,
261
- HTMLAttributes: {},
262
- };
263
- },
264
- group: "block",
265
- draggable: true,
266
- isolating: true,
267
- content: "inline*",
268
- addStorage() {
269
- return {
270
- elementsVisible: false,
271
- currentActiveWrapper: null,
272
- };
273
- },
274
- addAttributes() {
275
- return {
276
- ...this.parent?.(),
277
- src: {
278
- default: null,
279
- parseHTML: (element) => {
280
- if (element.tagName === "FIGURE") {
281
- const img = element.querySelector("img");
282
- return img?.getAttribute("src");
283
- }
284
- return element.getAttribute("src");
285
- },
286
- },
287
- alt: {
288
- default: null,
289
- parseHTML: (element) => {
290
- const img = element.querySelector("img");
291
- return img?.getAttribute("alt");
292
- },
293
- },
294
- title: {
295
- default: null,
296
- parseHTML: (element) => {
297
- const img = element.querySelector("img");
298
- return img?.getAttribute("title");
299
- },
300
- },
301
- style: {
302
- // This style is applied to the wrapper element
303
- default: "width: 100%; height: auto; cursor: pointer;",
304
- parseHTML: (element) => {
305
- const width = element.getAttribute("width");
306
- // const img = element.querySelector("img");
307
- // const width = img?.getAttribute("width");
308
- return width
309
- ? `width: ${width}px; height: auto; cursor: pointer;`
310
- : `${element.style.cssText}`;
311
- },
312
- },
313
- };
314
- },
315
- parseHTML() {
316
- return [
317
- {
318
- tag: "figure",
319
- contentElement: "figcaption",
320
- },
321
- {
322
- tag: this.options.allowBase64
323
- ? "img[src]"
324
- : 'img[src]:not([src^="data:"])',
325
- },
326
- ];
327
- },
328
- renderHTML({ HTMLAttributes, node }) {
329
- const hasCaption = node.content.size > 0;
330
- if (hasCaption) {
331
- return [
332
- "figure",
333
- this.options.HTMLAttributes,
334
- [
335
- "img",
336
- core.mergeAttributes(HTMLAttributes, {
337
- draggable: false,
338
- contenteditable: false,
339
- }),
340
- ],
341
- ["figcaption", 0],
342
- ];
343
- }
344
- return ["img", core.mergeAttributes(HTMLAttributes)];
345
- },
346
- addNodeView() {
347
- return ({ node, editor, getPos }) => {
348
- const dispatchNodeView = () => {
349
- if (typeof getPos === "function") {
350
- const newAttrs = {
351
- ...node.attrs,
352
- style: imageElement.style.cssText,
353
- };
354
- editor.view.dispatch(editor.view.state.tr.setNodeMarkup(getPos(), null, newAttrs));
355
- this.storage.elementsVisible = false;
356
- }
357
- };
358
- const { options: { editable }, } = editor;
359
- const { style } = node.attrs;
360
- // Create wrapper based on content
361
- const wrapperElement = document.createElement(node.content.size > 0 ? "figure" : "div");
362
- wrapperElement.setAttribute("class", styles["wrapper-element"]);
363
- wrapperElement.setAttribute("style", style);
364
- const imageElement = document.createElement("img");
365
- wrapperElement.appendChild(imageElement);
366
- const captionElement = document.createElement("figcaption");
367
- // Set up image attributes
368
- Object.entries(node.attrs).forEach(([key, value]) => {
369
- if (value === undefined || value === null) {
370
- return;
371
- }
372
- imageElement.setAttribute(key, value);
373
- });
374
- // Add caption if needed
375
- if (node.content.size > 0) {
376
- captionElement.setAttribute("class", styles["caption-element"]);
377
- captionElement.setAttribute("contenteditable", "true");
378
- wrapperElement.appendChild(captionElement);
379
- }
380
- if (!editable)
381
- return { dom: wrapperElement, contentDOM: captionElement };
382
- // Initialize control variables
383
- let isResizing = false;
384
- let startX = 0;
385
- let startWidth = 0;
386
- // Handle click on container
387
- wrapperElement.addEventListener("click", (event) => {
388
- event.stopPropagation();
389
- event.preventDefault();
390
- // If controls are already visible, check if another image or figure is being clicked on
391
- if (this.storage.elementsVisible) {
392
- const clickedElement = event.target;
393
- const currentActiveWrapper = this.storage.currentActiveWrapper;
394
- // Check if the clicked element is a child of the current active wrapper
395
- // If it isn't, we must remove the controls from the previous wrapper and continue
396
- // If it is, we do nothing
397
- if (currentActiveWrapper &&
398
- currentActiveWrapper.contains(clickedElement)) {
399
- return;
400
- }
401
- if (currentActiveWrapper) {
402
- removeImageControlsAndResetStyles(clickedElement, currentActiveWrapper, styles);
403
- }
404
- }
405
- this.storage.currentActiveWrapper = wrapperElement;
406
- const isMobile = isMobileScreen();
407
- if (isMobile) {
408
- const focusedElement = document.querySelector(".ProseMirror-focused");
409
- focusedElement?.blur();
410
- }
411
- // Remove existing controls first
412
- removeImageControlsAndResetStyles(event.target, wrapperElement, styles);
413
- // Show new controls
414
- wrapperElement.classList.toggle(styles["active"]);
415
- addImageAlignmentControls(wrapperElement, imageElement, styles, () => {
416
- dispatchNodeView();
417
- editor.commands.focus();
418
- });
419
- addImageResizeControls(wrapperElement, imageElement, isResizing, startX, startWidth, styles, () => {
420
- dispatchNodeView();
421
- editor.commands.focus();
422
- });
423
- addCaptionControls(wrapperElement, styles, () => {
424
- // On caption remove
425
- changeFigureToImage(this.editor, wrapperElement);
426
- this.storage.elementsVisible = false;
427
- }, () => {
428
- // On caption add
429
- changeImageToFigure(this.editor, wrapperElement, captionElement);
430
- this.storage.elementsVisible = false;
431
- });
432
- this.storage.elementsVisible = true;
433
- });
434
- // Handle clicks outside
435
- document.addEventListener("click", (event) => {
436
- if (!wrapperElement.contains(event.target)) {
437
- removeImageControlsAndResetStyles(event.target, wrapperElement, styles);
438
- this.storage.elementsVisible = false;
439
- this.storage.currentActiveWrapper = null;
440
- }
441
- });
442
- return {
443
- dom: wrapperElement,
444
- contentDOM: node.content.size > 0 ? captionElement : undefined,
445
- ignoreMutation: (mutation) => {
446
- // We must ignore mutations that happened outside the captionElement
447
- return !captionElement.contains(mutation.target);
448
- },
449
- };
450
- };
451
- },
452
- addInputRules() {
453
- return [
454
- core.nodeInputRule({
455
- find: inputRegex,
456
- type: this.type,
457
- getAttributes: (match) => {
458
- const [, alt, src, title] = match;
459
- return { src, alt, title };
460
- },
461
- }),
462
- ];
463
- },
464
- });
465
-
466
- exports.default = TiptapImageFigureExtension;
467
- //# sourceMappingURL=index.cjs.js.map
@@ -1 +0,0 @@
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]}
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import TiptapImageResizeWithCaption from "./component/tip-tap-image-resize-with-caption";
2
- export default TiptapImageResizeWithCaption;
package/dist/index.js DELETED
@@ -1,463 +0,0 @@
1
- import { nodeInputRule, mergeAttributes } from '@tiptap/core';
2
- import ImageExtension from '@tiptap/extension-image';
3
- import { TextSelection } from '@tiptap/pm/state';
4
-
5
- const isMobileScreen = () => document.documentElement.clientWidth < 768;
6
-
7
- const removeImageControlsAndResetStyles = (clickedElement, wrapperElement, styles) => {
8
- const containerContainsClickedElement = wrapperElement.contains(clickedElement);
9
- if (containerContainsClickedElement) {
10
- return;
11
- }
12
- // Remove all custom UI elements and styling
13
- wrapperElement.classList.remove(styles["active"]);
14
- const children = Array.from(wrapperElement.children);
15
- children.forEach((child) => {
16
- if (child.tagName !== "IMG" && child.tagName !== "FIGCAPTION") {
17
- wrapperElement.removeChild(child);
18
- }
19
- });
20
- };
21
-
22
- 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";
23
-
24
- 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";
25
-
26
- 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";
27
-
28
- const imageAlignmentControls = [
29
- {
30
- type: "left",
31
- icon: leftIcon,
32
- styleToApply: "margin: 0 auto 0 0;",
33
- },
34
- {
35
- type: "center",
36
- icon: centerIcon,
37
- styleToApply: "margin: 0 auto;",
38
- },
39
- {
40
- type: "right",
41
- icon: rightIcon,
42
- styleToApply: "margin: 0 0 0 auto;",
43
- },
44
- ];
45
- const addImageAlignmentControls = (wrapperElement, imageElement, styles, onAlign) => {
46
- const imageAlignmentContainer = document.createElement("div");
47
- imageAlignmentContainer.setAttribute("contenteditable", "false");
48
- imageAlignmentContainer.setAttribute("class", styles["image-alignment-container"]);
49
- imageAlignmentControls.forEach((imageControl) => {
50
- const imageAlignmentControl = document.createElement("img");
51
- imageAlignmentControl.src = imageControl.icon;
52
- imageAlignmentControl.setAttribute("class", styles["image-alignment-control"]);
53
- imageAlignmentControl.addEventListener("click", (event) => {
54
- event.stopPropagation();
55
- imageElement.style.cssText = `${imageElement.style.cssText} ${imageControl.styleToApply}`;
56
- onAlign();
57
- });
58
- imageAlignmentContainer.appendChild(imageAlignmentControl);
59
- });
60
- wrapperElement.appendChild(imageAlignmentContainer);
61
- };
62
-
63
- const addImageResizeControls = (wrapperElement, imageElement, isResizing, startX, startWidth, styles, onResize) => {
64
- const isMobile = isMobileScreen();
65
- const dotPosition = isMobile ? "-8px" : "-4px";
66
- const dotSize = isMobile ? 16 : 9;
67
- const dotsPosition = [
68
- `top: ${dotPosition}; left: ${dotPosition}; cursor: nwse-resize;`,
69
- `top: ${dotPosition}; right: ${dotPosition}; cursor: nesw-resize;`,
70
- `bottom: ${dotPosition}; left: ${dotPosition}; cursor: nesw-resize;`,
71
- `bottom: ${dotPosition}; right: ${dotPosition}; cursor: nwse-resize;`,
72
- ];
73
- Array.from({ length: 4 }, (_, index) => {
74
- const dotElement = document.createElement("div");
75
- dotElement.setAttribute("class", styles["dot-element"]);
76
- dotElement.setAttribute("style", `width: ${dotSize}px; height: ${dotSize}px; ${dotsPosition[index]}`);
77
- dotElement.addEventListener("mousedown", (e) => {
78
- e.preventDefault();
79
- isResizing = true;
80
- startX = e.clientX;
81
- startWidth = wrapperElement.offsetWidth;
82
- const onMouseMove = (event) => {
83
- if (!isResizing) {
84
- return;
85
- }
86
- const deltaX = index % 2 === 0 ? -(event.clientX - startX) : event.clientX - startX;
87
- const newWidth = startWidth + deltaX;
88
- wrapperElement.style.width = newWidth + "px";
89
- imageElement.style.width = newWidth + "px";
90
- };
91
- const onMouseUp = () => {
92
- if (isResizing) {
93
- isResizing = false;
94
- }
95
- onResize();
96
- document.removeEventListener("mousemove", onMouseMove);
97
- document.removeEventListener("mouseup", onMouseUp);
98
- };
99
- document.addEventListener("mousemove", onMouseMove);
100
- document.addEventListener("mouseup", onMouseUp);
101
- });
102
- dotElement.addEventListener("touchstart", (e) => {
103
- e.cancelable && e.preventDefault();
104
- isResizing = true;
105
- startX = e.touches[0].clientX;
106
- startWidth = wrapperElement.offsetWidth;
107
- const onTouchMove = (e) => {
108
- if (!isResizing)
109
- return;
110
- const deltaX = index % 2 === 0
111
- ? -(e.touches[0].clientX - startX)
112
- : e.touches[0].clientX - startX;
113
- const newWidth = startWidth + deltaX;
114
- wrapperElement.style.width = newWidth + "px";
115
- imageElement.style.width = newWidth + "px";
116
- };
117
- const onTouchEnd = () => {
118
- if (isResizing) {
119
- isResizing = false;
120
- }
121
- onResize();
122
- document.removeEventListener("touchmove", onTouchMove);
123
- document.removeEventListener("touchend", onTouchEnd);
124
- };
125
- document.addEventListener("touchmove", onTouchMove);
126
- document.addEventListener("touchend", onTouchEnd);
127
- }, { passive: false });
128
- wrapperElement.appendChild(dotElement);
129
- });
130
- };
131
-
132
- 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";
133
-
134
- 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";
135
-
136
- const addCaptionControls = (wrapperElement, styles, onCaptionRemove, onCaptionAdd) => {
137
- const captionControlsContainer = document.createElement("div");
138
- captionControlsContainer.setAttribute("contenteditable", "false");
139
- captionControlsContainer.setAttribute("class", styles["caption-controls-element"]);
140
- // If wrapper element is a figure and the button doesn't already exist, add a button to remove caption
141
- // Also, wrapper elements needs to become a div
142
- if (wrapperElement.tagName === "FIGURE" &&
143
- !wrapperElement.querySelector(styles["remove-caption-button"])) {
144
- const removeCaptionButton = document.createElement("img");
145
- removeCaptionButton.src = deleteIcon;
146
- removeCaptionButton.setAttribute("class", styles["remove-caption-button"]);
147
- removeCaptionButton.addEventListener("click", (event) => {
148
- event.stopPropagation();
149
- onCaptionRemove();
150
- });
151
- captionControlsContainer.appendChild(removeCaptionButton);
152
- wrapperElement.appendChild(captionControlsContainer);
153
- return;
154
- }
155
- // If wrapper element is a div and the button doesn't already exist, add a button to add caption
156
- if (wrapperElement.tagName === "DIV" &&
157
- !wrapperElement.querySelector(styles["add-caption-button"])) {
158
- const addCaptionButton = document.createElement("img");
159
- addCaptionButton.src = closedCaptionAddIcon;
160
- addCaptionButton.setAttribute("class", styles["add-caption-button"]);
161
- addCaptionButton.addEventListener("click", (event) => {
162
- event.stopPropagation();
163
- onCaptionAdd();
164
- });
165
- captionControlsContainer.appendChild(addCaptionButton);
166
- wrapperElement.appendChild(captionControlsContainer);
167
- }
168
- };
169
-
170
- const changeFigureToImage = (editor, wrapperElement) => {
171
- const { state, view } = editor;
172
- const { tr } = state;
173
- const imageWrapperElement = document.createElement("div");
174
- const oldAttributes = wrapperElement.attributes;
175
- const newAttributes = imageWrapperElement.attributes;
176
- const imageWrapperPosition = view.posAtDOM(wrapperElement, 0);
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
- // Focus on the newly replaced wrapper element
189
- const newSelection = TextSelection.create(tr.doc, imageWrapperPosition);
190
- const transaction = tr.setSelection(newSelection);
191
- view.dispatch(transaction);
192
- };
193
- const changeImageToFigure = (editor, wrapperElement, captionElement) => {
194
- const { state, view } = editor;
195
- const { tr } = state;
196
- const figureWrapperElement = document.createElement("figure");
197
- const oldAttributes = wrapperElement.attributes;
198
- const newAttributes = figureWrapperElement.attributes;
199
- const figureWrapperPosition = view.posAtDOM(wrapperElement, 0);
200
- // Copy attributes
201
- for (let i = 0, len = oldAttributes.length; i < len; i++) {
202
- newAttributes.setNamedItem(oldAttributes.item(i).cloneNode());
203
- }
204
- // Find the image within the old wrapper and set it as the only child of the new wrapper
205
- const imageElement = wrapperElement.querySelector("img");
206
- if (imageElement) {
207
- figureWrapperElement.appendChild(imageElement);
208
- }
209
- captionElement.innerHTML = "Caption";
210
- figureWrapperElement.appendChild(captionElement);
211
- // Replace wrapperElement with figureWrapperElement
212
- wrapperElement.replaceWith(figureWrapperElement);
213
- // Focus on the newly replaced wrapper element
214
- const newSelection = TextSelection.create(tr.doc, figureWrapperPosition);
215
- const transaction = tr.setSelection(newSelection);
216
- view.dispatch(transaction);
217
- };
218
-
219
- function styleInject(css, ref) {
220
- if ( ref === void 0 ) ref = {};
221
- var insertAt = ref.insertAt;
222
-
223
- if (typeof document === 'undefined') { return; }
224
-
225
- var head = document.head || document.getElementsByTagName('head')[0];
226
- var style = document.createElement('style');
227
- style.type = 'text/css';
228
-
229
- if (insertAt === 'top') {
230
- if (head.firstChild) {
231
- head.insertBefore(style, head.firstChild);
232
- } else {
233
- head.appendChild(style);
234
- }
235
- } else {
236
- head.appendChild(style);
237
- }
238
-
239
- if (style.styleSheet) {
240
- style.styleSheet.cssText = css;
241
- } else {
242
- style.appendChild(document.createTextNode(css));
243
- }
244
- }
245
-
246
- 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 margin-inline: 0;\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 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 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";
247
- 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"};
248
- styleInject(css_248z);
249
-
250
- const inputRegex = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
251
- const TiptapImageFigureExtension = ImageExtension.extend({
252
- addOptions() {
253
- return {
254
- ...this.parent?.(),
255
- inline: false,
256
- allowBase64: false,
257
- HTMLAttributes: {},
258
- };
259
- },
260
- group: "block",
261
- draggable: true,
262
- isolating: true,
263
- content: "inline*",
264
- addStorage() {
265
- return {
266
- elementsVisible: false,
267
- currentActiveWrapper: null,
268
- };
269
- },
270
- addAttributes() {
271
- return {
272
- ...this.parent?.(),
273
- src: {
274
- default: null,
275
- parseHTML: (element) => {
276
- if (element.tagName === "FIGURE") {
277
- const img = element.querySelector("img");
278
- return img?.getAttribute("src");
279
- }
280
- return element.getAttribute("src");
281
- },
282
- },
283
- alt: {
284
- default: null,
285
- parseHTML: (element) => {
286
- const img = element.querySelector("img");
287
- return img?.getAttribute("alt");
288
- },
289
- },
290
- title: {
291
- default: null,
292
- parseHTML: (element) => {
293
- const img = element.querySelector("img");
294
- return img?.getAttribute("title");
295
- },
296
- },
297
- style: {
298
- // This style is applied to the wrapper element
299
- default: "width: 100%; height: auto; cursor: pointer;",
300
- parseHTML: (element) => {
301
- const width = element.getAttribute("width");
302
- // const img = element.querySelector("img");
303
- // const width = img?.getAttribute("width");
304
- return width
305
- ? `width: ${width}px; height: auto; cursor: pointer;`
306
- : `${element.style.cssText}`;
307
- },
308
- },
309
- };
310
- },
311
- parseHTML() {
312
- return [
313
- {
314
- tag: "figure",
315
- contentElement: "figcaption",
316
- },
317
- {
318
- tag: this.options.allowBase64
319
- ? "img[src]"
320
- : 'img[src]:not([src^="data:"])',
321
- },
322
- ];
323
- },
324
- renderHTML({ HTMLAttributes, node }) {
325
- const hasCaption = node.content.size > 0;
326
- if (hasCaption) {
327
- return [
328
- "figure",
329
- this.options.HTMLAttributes,
330
- [
331
- "img",
332
- mergeAttributes(HTMLAttributes, {
333
- draggable: false,
334
- contenteditable: false,
335
- }),
336
- ],
337
- ["figcaption", 0],
338
- ];
339
- }
340
- return ["img", mergeAttributes(HTMLAttributes)];
341
- },
342
- addNodeView() {
343
- return ({ node, editor, getPos }) => {
344
- const dispatchNodeView = () => {
345
- if (typeof getPos === "function") {
346
- const newAttrs = {
347
- ...node.attrs,
348
- style: imageElement.style.cssText,
349
- };
350
- editor.view.dispatch(editor.view.state.tr.setNodeMarkup(getPos(), null, newAttrs));
351
- this.storage.elementsVisible = false;
352
- }
353
- };
354
- const { options: { editable }, } = editor;
355
- const { style } = node.attrs;
356
- // Create wrapper based on content
357
- const wrapperElement = document.createElement(node.content.size > 0 ? "figure" : "div");
358
- wrapperElement.setAttribute("class", styles["wrapper-element"]);
359
- wrapperElement.setAttribute("style", style);
360
- const imageElement = document.createElement("img");
361
- wrapperElement.appendChild(imageElement);
362
- const captionElement = document.createElement("figcaption");
363
- // Set up image attributes
364
- Object.entries(node.attrs).forEach(([key, value]) => {
365
- if (value === undefined || value === null) {
366
- return;
367
- }
368
- imageElement.setAttribute(key, value);
369
- });
370
- // Add caption if needed
371
- if (node.content.size > 0) {
372
- captionElement.setAttribute("class", styles["caption-element"]);
373
- captionElement.setAttribute("contenteditable", "true");
374
- wrapperElement.appendChild(captionElement);
375
- }
376
- if (!editable)
377
- return { dom: wrapperElement, contentDOM: captionElement };
378
- // Initialize control variables
379
- let isResizing = false;
380
- let startX = 0;
381
- let startWidth = 0;
382
- // Handle click on container
383
- wrapperElement.addEventListener("click", (event) => {
384
- event.stopPropagation();
385
- event.preventDefault();
386
- // If controls are already visible, check if another image or figure is being clicked on
387
- if (this.storage.elementsVisible) {
388
- const clickedElement = event.target;
389
- const currentActiveWrapper = this.storage.currentActiveWrapper;
390
- // Check if the clicked element is a child of the current active wrapper
391
- // If it isn't, we must remove the controls from the previous wrapper and continue
392
- // If it is, we do nothing
393
- if (currentActiveWrapper &&
394
- currentActiveWrapper.contains(clickedElement)) {
395
- return;
396
- }
397
- if (currentActiveWrapper) {
398
- removeImageControlsAndResetStyles(clickedElement, currentActiveWrapper, styles);
399
- }
400
- }
401
- this.storage.currentActiveWrapper = wrapperElement;
402
- const isMobile = isMobileScreen();
403
- if (isMobile) {
404
- const focusedElement = document.querySelector(".ProseMirror-focused");
405
- focusedElement?.blur();
406
- }
407
- // Remove existing controls first
408
- removeImageControlsAndResetStyles(event.target, wrapperElement, styles);
409
- // Show new controls
410
- wrapperElement.classList.toggle(styles["active"]);
411
- addImageAlignmentControls(wrapperElement, imageElement, styles, () => {
412
- dispatchNodeView();
413
- editor.commands.focus();
414
- });
415
- addImageResizeControls(wrapperElement, imageElement, isResizing, startX, startWidth, styles, () => {
416
- dispatchNodeView();
417
- editor.commands.focus();
418
- });
419
- addCaptionControls(wrapperElement, styles, () => {
420
- // On caption remove
421
- changeFigureToImage(this.editor, wrapperElement);
422
- this.storage.elementsVisible = false;
423
- }, () => {
424
- // On caption add
425
- changeImageToFigure(this.editor, wrapperElement, captionElement);
426
- this.storage.elementsVisible = false;
427
- });
428
- this.storage.elementsVisible = true;
429
- });
430
- // Handle clicks outside
431
- document.addEventListener("click", (event) => {
432
- if (!wrapperElement.contains(event.target)) {
433
- removeImageControlsAndResetStyles(event.target, wrapperElement, styles);
434
- this.storage.elementsVisible = false;
435
- this.storage.currentActiveWrapper = null;
436
- }
437
- });
438
- return {
439
- dom: wrapperElement,
440
- contentDOM: node.content.size > 0 ? captionElement : undefined,
441
- ignoreMutation: (mutation) => {
442
- // We must ignore mutations that happened outside the captionElement
443
- return !captionElement.contains(mutation.target);
444
- },
445
- };
446
- };
447
- },
448
- addInputRules() {
449
- return [
450
- nodeInputRule({
451
- find: inputRegex,
452
- type: this.type,
453
- getAttributes: (match) => {
454
- const [, alt, src, title] = match;
455
- return { src, alt, title };
456
- },
457
- }),
458
- ];
459
- },
460
- });
461
-
462
- export { TiptapImageFigureExtension as default };
463
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.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]}
@@ -1 +0,0 @@
1
- export declare const addCaptionControls: (wrapperElement: HTMLElement, styles: Record<string, string>, onCaptionRemove: () => void, onCaptionAdd: () => void) => void;
@@ -1 +0,0 @@
1
- export declare const addImageAlignmentControls: (wrapperElement: HTMLElement, imageElement: HTMLImageElement, styles: Record<string, string>, onAlign: () => void) => void;
@@ -1 +0,0 @@
1
- export declare const addImageResizeControls: (wrapperElement: HTMLElement, imageElement: HTMLImageElement, isResizing: boolean, startX: number, startWidth: number, styles: Record<string, string>, onResize: () => void) => void;
@@ -1 +0,0 @@
1
- export declare const isMobileScreen: () => boolean;
@@ -1 +0,0 @@
1
- export declare const removeImageControlsAndResetStyles: (clickedElement: HTMLElement, wrapperElement: HTMLElement, styles: Record<string, string>) => void;
@@ -1,3 +0,0 @@
1
- import { Editor } from "@tiptap/core";
2
- export declare const changeFigureToImage: (editor: Editor, wrapperElement: HTMLElement) => void;
3
- export declare const changeImageToFigure: (editor: Editor, wrapperElement: HTMLElement, captionElement: HTMLElement) => void;