tiptap-extension-izzy-resizable-image 0.1.8 → 0.1.10
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 +6 -0
- package/npm/index.js +89 -10
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Resizable inline image node for TipTap v2 with eight resize handles, optional al
|
|
|
4
4
|
|
|
5
5
|
Demo: https://startinn.github.io/TipTap-Extension-Izzy-Resizable-Image/
|
|
6
6
|
|
|
7
|
+
Download: https://www.npmjs.com/package/tiptap-extension-izzy-resizable-image
|
|
8
|
+
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
11
|
- Inline resizable image with 8 handles (nw, n, ne, e, se, s, sw, w)
|
|
@@ -91,6 +93,8 @@ const editor = new Editor({
|
|
|
91
93
|
// size50: true,
|
|
92
94
|
// size100: true,
|
|
93
95
|
},
|
|
96
|
+
fitToContainerSelector: '.tiptap',
|
|
97
|
+
autoFitWhenOversized: true,
|
|
94
98
|
};
|
|
95
99
|
},
|
|
96
100
|
}),
|
|
@@ -108,6 +112,8 @@ editor.commands.insertResizableImage({ src: 'https://picsum.photos/300/200', wid
|
|
|
108
112
|
- `alignMenuPosition: 'above' | 'below'` — where the menu appears relative to the image.
|
|
109
113
|
- `alignMenuIcons: { left: string, center: string, right: string, clear: string, preview: string }` — HTML strings for menu buttons.
|
|
110
114
|
- `alignMenuButtonsHide: Record<string, boolean>` — hide specific buttons when `true` (supports `left`, `center`, `right`, `clear`, `preview`, `size50`, `size100`).
|
|
115
|
+
- `fitToContainerSelector: string` — CSS selector used as width reference for the initial image fit (default: `.tiptap`).
|
|
116
|
+
- `autoFitWhenOversized: boolean` — when `true`, oversized images inserted without explicit width are automatically set to `width: 100%` and `height: auto` (default: `true`).
|
|
111
117
|
|
|
112
118
|
Reference:
|
|
113
119
|
- Defined in `resizable-image.js:387-394`
|
package/npm/index.js
CHANGED
|
@@ -9,13 +9,45 @@ function createHandle(direction) {
|
|
|
9
9
|
return handle
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function applyDimensionStyle(el, property, value) {
|
|
13
|
+
if (value == null || value === '') {
|
|
14
|
+
el.style.removeProperty(property)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof value === 'number') {
|
|
19
|
+
el.style[property] = value + 'px'
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const raw = String(value).trim()
|
|
24
|
+
if (!raw) {
|
|
25
|
+
el.style.removeProperty(property)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (/^\d+(\.\d+)?$/.test(raw)) {
|
|
30
|
+
el.style[property] = raw + 'px'
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
el.style[property] = raw
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toFiniteNumber(value) {
|
|
38
|
+
const n = Number(value)
|
|
39
|
+
return Number.isFinite(n) ? n : null
|
|
40
|
+
}
|
|
41
|
+
|
|
12
42
|
class ResizableImageView {
|
|
13
43
|
constructor(node, view, getPos, options) {
|
|
14
44
|
this.node = node
|
|
15
45
|
this.view = view
|
|
16
46
|
this.getPos = getPos
|
|
17
47
|
this.options = options || {}
|
|
18
|
-
|
|
48
|
+
const initialWidth = toFiniteNumber(node.attrs.width)
|
|
49
|
+
const initialHeight = toFiniteNumber(node.attrs.height)
|
|
50
|
+
this.aspect = (initialWidth && initialHeight) ? initialWidth / initialHeight : null
|
|
19
51
|
|
|
20
52
|
this.dom = document.createElement('span')
|
|
21
53
|
this.dom.className = 'tiptap-izzy-resizable-image'
|
|
@@ -43,16 +75,17 @@ class ResizableImageView {
|
|
|
43
75
|
this.img.style.pointerEvents = 'none'
|
|
44
76
|
if (node.attrs.id) this.img.id = node.attrs.id
|
|
45
77
|
if (node.attrs.class) this.img.className = node.attrs.class
|
|
46
|
-
|
|
78
|
+
applyDimensionStyle(this.img, 'width', node.attrs.width)
|
|
47
79
|
if (node.attrs.height) {
|
|
48
|
-
this.img
|
|
80
|
+
applyDimensionStyle(this.img, 'height', node.attrs.height)
|
|
49
81
|
} else if (this.options && this.options.height) {
|
|
50
|
-
this.img
|
|
82
|
+
applyDimensionStyle(this.img, 'height', this.options.height)
|
|
51
83
|
}
|
|
52
84
|
this.img.addEventListener('load', () => {
|
|
53
85
|
if (!this.aspect && this.img.naturalWidth && this.img.naturalHeight) {
|
|
54
86
|
this.aspect = this.img.naturalWidth / this.img.naturalHeight
|
|
55
87
|
}
|
|
88
|
+
this.applyInitialFitIfNeeded()
|
|
56
89
|
})
|
|
57
90
|
this.inner.appendChild(this.img)
|
|
58
91
|
|
|
@@ -160,6 +193,50 @@ class ResizableImageView {
|
|
|
160
193
|
|
|
161
194
|
}
|
|
162
195
|
|
|
196
|
+
getEditorContentWidth() {
|
|
197
|
+
const selector = this.options?.fitToContainerSelector
|
|
198
|
+
const root = this.view?.dom || this.dom?.parentElement
|
|
199
|
+
if (!root) return null
|
|
200
|
+
|
|
201
|
+
if (selector) {
|
|
202
|
+
const bySelector = root.closest?.(selector) || root.querySelector?.(selector)
|
|
203
|
+
if (bySelector) {
|
|
204
|
+
const w = Math.round(bySelector.getBoundingClientRect().width)
|
|
205
|
+
if (w > 0) return w
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const tiptap = this.dom?.closest?.('.tiptap') || root.closest?.('.tiptap') || root.querySelector?.('.tiptap')
|
|
210
|
+
if (tiptap) {
|
|
211
|
+
const w = Math.round(tiptap.getBoundingClientRect().width)
|
|
212
|
+
if (w > 0) return w
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const rootWidth = Math.round(root.getBoundingClientRect().width)
|
|
216
|
+
return rootWidth > 0 ? rootWidth : null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
applyInitialFitIfNeeded() {
|
|
220
|
+
if (this.options?.autoFitWhenOversized === false) return
|
|
221
|
+
if (this.node?.attrs?.width != null) return
|
|
222
|
+
|
|
223
|
+
const containerWidth = this.getEditorContentWidth()
|
|
224
|
+
if (!containerWidth || !this.img?.naturalWidth) return
|
|
225
|
+
|
|
226
|
+
if (this.img.naturalWidth <= containerWidth) return
|
|
227
|
+
|
|
228
|
+
applyDimensionStyle(this.img, 'width', '100%')
|
|
229
|
+
applyDimensionStyle(this.img, 'height', 'auto')
|
|
230
|
+
|
|
231
|
+
const pos = this.getPos()
|
|
232
|
+
const tr = this.view.state.tr.setNodeMarkup(pos, null, {
|
|
233
|
+
...this.node.attrs,
|
|
234
|
+
width: '100%',
|
|
235
|
+
height: 'auto',
|
|
236
|
+
})
|
|
237
|
+
this.view.dispatch(tr)
|
|
238
|
+
}
|
|
239
|
+
|
|
163
240
|
updateMenuPosition(pos) {
|
|
164
241
|
this.menu.classList.remove('menu-above', 'menu-below')
|
|
165
242
|
if (pos === 'above') {
|
|
@@ -270,10 +347,8 @@ class ResizableImageView {
|
|
|
270
347
|
this.node = node
|
|
271
348
|
if (node.attrs.src !== this.img.src) this.img.src = node.attrs.src
|
|
272
349
|
this.img.alt = node.attrs.alt || ''
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (node.attrs.height) this.img.style.height = node.attrs.height + 'px'
|
|
276
|
-
else this.img.style.removeProperty('height')
|
|
350
|
+
applyDimensionStyle(this.img, 'width', node.attrs.width)
|
|
351
|
+
applyDimensionStyle(this.img, 'height', node.attrs.height)
|
|
277
352
|
this.applyAlignment(node.attrs.align)
|
|
278
353
|
this.updateMenuPosition(node.attrs.alignMenuPosition != null ? node.attrs.alignMenuPosition : (this.options && this.options.alignMenuPosition != null ? this.options.alignMenuPosition : 'below'))
|
|
279
354
|
this.btnLeft.innerHTML = (node.attrs.iconLeft != null ? node.attrs.iconLeft : (this.options && this.options.alignMenuIcons ? this.options.alignMenuIcons.left : '⟸'))
|
|
@@ -281,7 +356,9 @@ class ResizableImageView {
|
|
|
281
356
|
this.btnRight.innerHTML = (node.attrs.iconRight != null ? node.attrs.iconRight : (this.options && this.options.alignMenuIcons ? this.options.alignMenuIcons.right : '⟹'))
|
|
282
357
|
this.btnClear.innerHTML = (node.attrs.iconClear != null ? node.attrs.iconClear : (this.options && this.options.alignMenuIcons ? this.options.alignMenuIcons.clear : 'x'))
|
|
283
358
|
if (this.btnView) this.btnView.innerHTML = (node.attrs.iconView != null ? node.attrs.iconView : (this.options && this.options.alignMenuIcons ? this.options.alignMenuIcons.preview : '🔍'))
|
|
284
|
-
|
|
359
|
+
const widthNum = toFiniteNumber(node.attrs.width)
|
|
360
|
+
const heightNum = toFiniteNumber(node.attrs.height)
|
|
361
|
+
this.aspect = (widthNum && heightNum) ? widthNum / heightNum : this.aspect
|
|
285
362
|
return true
|
|
286
363
|
}
|
|
287
364
|
|
|
@@ -357,7 +434,7 @@ export const TiptapIzzyExtensionResizableImage = Node.create({
|
|
|
357
434
|
height: { default: null },
|
|
358
435
|
id: { default: null },
|
|
359
436
|
class: { default: null },
|
|
360
|
-
align: { default:
|
|
437
|
+
align: { default: null },
|
|
361
438
|
showAlignMenu: { default: null },
|
|
362
439
|
alignMenuPosition: { default: null },
|
|
363
440
|
iconLeft: { default: null },
|
|
@@ -387,6 +464,8 @@ export const TiptapIzzyExtensionResizableImage = Node.create({
|
|
|
387
464
|
alignMenuPosition: 'below',
|
|
388
465
|
alignMenuIcons: { left: '⟸', center: '⇔', right: '⟹', clear: 'x', preview: '🔍' },
|
|
389
466
|
alignMenuButtonsHide: {},
|
|
467
|
+
fitToContainerSelector: '.tiptap',
|
|
468
|
+
autoFitWhenOversized: true,
|
|
390
469
|
}
|
|
391
470
|
},
|
|
392
471
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiptap-extension-izzy-resizable-image",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Resizable image NodeView for TipTap v2 with alignment menu and defaults",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./npm/index.js"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://startinn.github.io/TipTap-Extension-Izzy-Resizable-Image/",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/startinn/TipTap-Extension-Izzy-Resizable-Image/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"registry": "https://registry.npmjs.org/"
|
|
17
|
+
},
|
|
12
18
|
"files": [
|
|
13
19
|
"npm/index.js",
|
|
14
20
|
"README.md"
|