@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.
- package/README.md +39 -0
- package/dist/component/tip-tap-image-resize-with-caption.d.ts +5 -0
- package/dist/index.cjs.js +452 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +448 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/add-caption-controls.util.d.ts +1 -0
- package/dist/utils/add-image-alignment-controls.util.d.ts +1 -0
- package/dist/utils/add-image-resize-controls.util.d.ts +1 -0
- package/dist/utils/is-mobile-screen.util.d.ts +1 -0
- package/dist/utils/remove-image-controls.util.d.ts +1 -0
- package/dist/utils/replace-element.util.d.ts +2 -0
- package/package.json +33 -0
- package/rollup.config.js +48 -0
- package/src/assets/icons/closed-caption-add.svg +1 -0
- package/src/assets/icons/delete.svg +1 -0
- package/src/assets/icons/format-align-center.svg +1 -0
- package/src/assets/icons/format-align-left.svg +1 -0
- package/src/assets/icons/format-align-right.svg +1 -0
- package/src/assets/icons/svg.d.ts +4 -0
- package/src/assets/styles/styles.css +85 -0
- package/src/assets/styles/styles.d.ts +4 -0
- package/src/component/tip-tap-image-resize-with-caption.ts +300 -0
- package/src/index.ts +3 -0
- package/src/utils/add-caption-controls.util.ts +51 -0
- package/src/utils/add-image-alignment-controls.util.ts +62 -0
- package/src/utils/add-image-resize-controls.util.ts +104 -0
- package/src/utils/is-mobile-screen.util.ts +1 -0
- package/src/utils/remove-image-controls.util.ts +22 -0
- package/src/utils/replace-element.util.ts +44 -0
- 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]}
|
package/dist/index.d.ts
ADDED