@seorii/tiptap 0.4.2 → 0.4.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.
|
@@ -29,33 +29,37 @@ function matchItem(item, query, compactQuery) {
|
|
|
29
29
|
return [item.title, item.subtitle ?? '', ...(item.keywords ?? [])].some((value) => matchQuery(value, query, compactQuery));
|
|
30
30
|
}
|
|
31
31
|
function fixRange(editor, rawRange, split = '/') {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
const doc = editor.state.doc;
|
|
33
|
+
const docSize = doc.content.size;
|
|
34
|
+
const range = {
|
|
35
|
+
from: Math.max(0, Math.min(rawRange.from, docSize)),
|
|
36
|
+
to: Math.max(0, Math.min(rawRange.to, docSize))
|
|
37
|
+
};
|
|
38
|
+
if (range.to < range.from)
|
|
39
|
+
range.to = range.from;
|
|
40
|
+
if (doc.textBetween(range.from, Math.min(range.from + 1, docSize)) !== split && range.from > 0) {
|
|
41
|
+
const minFrom = Math.max(0, range.from - 64);
|
|
42
|
+
for (let cursor = range.from; cursor > minFrom; cursor -= 1) {
|
|
43
|
+
const char = doc.textBetween(cursor - 1, cursor);
|
|
44
|
+
if (!char || /\s/.test(char))
|
|
43
45
|
break;
|
|
44
|
-
|
|
46
|
+
if (char !== split)
|
|
47
|
+
continue;
|
|
48
|
+
range.from = cursor - 1;
|
|
49
|
+
break;
|
|
45
50
|
}
|
|
46
|
-
range.from -= 1;
|
|
47
51
|
}
|
|
48
|
-
while (range.to <
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
range.to -= 1;
|
|
52
|
+
while (range.to < docSize) {
|
|
53
|
+
const next = doc.textBetween(range.to, range.to + 1);
|
|
54
|
+
if (!next || /\s/.test(next))
|
|
54
55
|
break;
|
|
55
|
-
|
|
56
|
+
range.to += 1;
|
|
56
57
|
}
|
|
57
58
|
return range;
|
|
58
59
|
}
|
|
60
|
+
function clampDocPos(editor, pos) {
|
|
61
|
+
return Math.max(0, Math.min(pos, editor.state.doc.content.size));
|
|
62
|
+
}
|
|
59
63
|
export function getDetail(editor, range, option) {
|
|
60
64
|
slashState.selection = () => {
|
|
61
65
|
editor.chain().focus().deleteRange(fixRange(editor, range)).run();
|
|
@@ -147,7 +151,18 @@ export const suggest = {
|
|
|
147
151
|
subtitle: i18n('imageInfo'),
|
|
148
152
|
keywords: createKeywords(['image', 'imageInfo']),
|
|
149
153
|
command: ({ editor, range }) => {
|
|
150
|
-
|
|
154
|
+
const fixedRange = fixRange(editor, range);
|
|
155
|
+
editor.chain().focus().deleteRange(fixedRange).run();
|
|
156
|
+
let insertAt = clampDocPos(editor, fixedRange.from);
|
|
157
|
+
let insertParagraph = true;
|
|
158
|
+
const { selection } = editor.state;
|
|
159
|
+
if (selection.empty &&
|
|
160
|
+
selection.$from.depth > 0 &&
|
|
161
|
+
selection.$from.parent.isTextblock &&
|
|
162
|
+
selection.$from.parent.content.size === 0) {
|
|
163
|
+
insertAt = clampDocPos(editor, selection.$from.before(selection.$from.depth));
|
|
164
|
+
insertParagraph = false;
|
|
165
|
+
}
|
|
151
166
|
const input = document.createElement('input');
|
|
152
167
|
input.type = 'file';
|
|
153
168
|
input.accept = 'image/*';
|
|
@@ -159,10 +174,13 @@ export const suggest = {
|
|
|
159
174
|
return;
|
|
160
175
|
const skeleton = insertUploadSkeleton(editor, {
|
|
161
176
|
kind: 'image',
|
|
162
|
-
height: 220
|
|
177
|
+
height: 220,
|
|
178
|
+
at: insertAt,
|
|
179
|
+
insertParagraph
|
|
163
180
|
});
|
|
164
181
|
try {
|
|
165
|
-
const
|
|
182
|
+
const imageUploader = window.__image_uploader;
|
|
183
|
+
const upload = imageUploader ?? fallbackUpload;
|
|
166
184
|
const src = await upload(file);
|
|
167
185
|
if (skeleton) {
|
|
168
186
|
skeleton.replaceWith({
|
|
@@ -171,9 +189,16 @@ export const suggest = {
|
|
|
171
189
|
});
|
|
172
190
|
}
|
|
173
191
|
else {
|
|
174
|
-
editor
|
|
192
|
+
editor
|
|
193
|
+
.chain()
|
|
194
|
+
.insertContentAt(clampDocPos(editor, insertAt), insertParagraph
|
|
195
|
+
? [{ type: 'image', attrs: { src } }, { type: 'paragraph' }]
|
|
196
|
+
: { type: 'image', attrs: { src } })
|
|
197
|
+
.run();
|
|
198
|
+
}
|
|
199
|
+
if (imageUploader) {
|
|
200
|
+
releaseObjectUrlOnImageSettled(editor.view, src);
|
|
175
201
|
}
|
|
176
|
-
releaseObjectUrlOnImageSettled(editor.view, src);
|
|
177
202
|
}
|
|
178
203
|
catch {
|
|
179
204
|
skeleton?.remove();
|
|
@@ -67,7 +67,8 @@ export const dropImagePlugin = () => {
|
|
|
67
67
|
props: {
|
|
68
68
|
handleDOMEvents: {
|
|
69
69
|
paste(view, event) {
|
|
70
|
-
const
|
|
70
|
+
const imageUploader = window.__image_uploader;
|
|
71
|
+
const upload = imageUploader || fallbackUpload;
|
|
71
72
|
const items = Array.from(event.clipboardData?.items || []);
|
|
72
73
|
const { schema } = view.state;
|
|
73
74
|
items.forEach((item) => {
|
|
@@ -95,7 +96,9 @@ export const dropImagePlugin = () => {
|
|
|
95
96
|
const transaction = view.state.tr.replaceSelectionWith(node);
|
|
96
97
|
view.dispatch(transaction);
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
+
if (imageUploader) {
|
|
100
|
+
releaseObjectUrlOnImageSettled(view, src);
|
|
101
|
+
}
|
|
99
102
|
})
|
|
100
103
|
.catch(() => {
|
|
101
104
|
skeleton?.remove();
|
|
@@ -119,7 +122,8 @@ export const dropImagePlugin = () => {
|
|
|
119
122
|
return false;
|
|
120
123
|
},
|
|
121
124
|
drop: (view, event) => {
|
|
122
|
-
const
|
|
125
|
+
const imageUploader = window.__image_uploader;
|
|
126
|
+
const upload = imageUploader || fallbackUpload;
|
|
123
127
|
const hasFiles = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length;
|
|
124
128
|
if (!hasFiles) {
|
|
125
129
|
return false;
|
|
@@ -160,7 +164,9 @@ export const dropImagePlugin = () => {
|
|
|
160
164
|
const transaction = view.state.tr.insert(coordinates.pos, node);
|
|
161
165
|
view.dispatch(transaction);
|
|
162
166
|
}
|
|
163
|
-
|
|
167
|
+
if (imageUploader) {
|
|
168
|
+
releaseObjectUrlOnImageSettled(view, src);
|
|
169
|
+
}
|
|
164
170
|
}
|
|
165
171
|
catch {
|
|
166
172
|
skeleton?.remove();
|
|
@@ -28,6 +28,17 @@ const aspectRatioOptions = [
|
|
|
28
28
|
{ label: '4:3', value: '4:3' },
|
|
29
29
|
{ label: '9:16', value: '9:16' }
|
|
30
30
|
];
|
|
31
|
+
const horizontalAlignValues = ['left', 'center', 'right'];
|
|
32
|
+
const horizontalAlignOptions = [
|
|
33
|
+
{ label: 'Auto', value: null },
|
|
34
|
+
{ label: 'Left', value: 'left' },
|
|
35
|
+
{ label: 'Center', value: 'center' },
|
|
36
|
+
{ label: 'Right', value: 'right' }
|
|
37
|
+
];
|
|
38
|
+
const widthPresetOptions = [
|
|
39
|
+
{ label: '100%', value: '100%' },
|
|
40
|
+
{ label: '50%', value: '50%' }
|
|
41
|
+
];
|
|
31
42
|
function isResizableType(value) {
|
|
32
43
|
return typeSet.has(value);
|
|
33
44
|
}
|
|
@@ -109,6 +120,28 @@ function sameAspectRatio(left, right) {
|
|
|
109
120
|
return false;
|
|
110
121
|
return Math.abs(leftRatio - rightRatio) <= 0.001;
|
|
111
122
|
}
|
|
123
|
+
function normalizeHorizontalAlignAttr(value) {
|
|
124
|
+
if (typeof value !== 'string')
|
|
125
|
+
return null;
|
|
126
|
+
const normalized = value.trim().toLowerCase();
|
|
127
|
+
if (!normalized)
|
|
128
|
+
return null;
|
|
129
|
+
return horizontalAlignValues.includes(normalized)
|
|
130
|
+
? normalized
|
|
131
|
+
: null;
|
|
132
|
+
}
|
|
133
|
+
function normalizeWidthPreset(value) {
|
|
134
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
135
|
+
return null;
|
|
136
|
+
const normalized = String(value).trim();
|
|
137
|
+
if (!normalized)
|
|
138
|
+
return null;
|
|
139
|
+
if (normalized === '100' || normalized === '100%')
|
|
140
|
+
return '100%';
|
|
141
|
+
if (normalized === '50' || normalized === '50%')
|
|
142
|
+
return '50%';
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
112
145
|
function normalizeStringAttr(value) {
|
|
113
146
|
if (typeof value !== 'string')
|
|
114
147
|
return null;
|
|
@@ -242,46 +275,228 @@ function buildResizeAttrs(kind, node, height, imageRatio, aspectRatio = normaliz
|
|
|
242
275
|
// Keep image responsive by storing width only; fixed height causes ratio distortion on narrow layouts.
|
|
243
276
|
return { ...attrs, width: roundedWidth, height: null };
|
|
244
277
|
}
|
|
245
|
-
if (kind === 'iframe'
|
|
246
|
-
|
|
278
|
+
if (kind === 'iframe') {
|
|
279
|
+
if (aspectRatio) {
|
|
280
|
+
// A fixed height prevents CSS aspect-ratio from taking effect.
|
|
281
|
+
return { ...attrs, width: attrs.width || '100%', height: null, aspectRatio };
|
|
282
|
+
}
|
|
283
|
+
return { ...attrs, width: attrs.width || '100%', height: roundedHeight, aspectRatio: null };
|
|
284
|
+
}
|
|
285
|
+
if (kind === 'embed') {
|
|
286
|
+
if (aspectRatio) {
|
|
287
|
+
// Keep PDF/embed responsive with CSS aspect-ratio when a preset is selected.
|
|
288
|
+
return { ...attrs, width: attrs.width || '100%', height: null, aspectRatio };
|
|
289
|
+
}
|
|
290
|
+
return { ...attrs, width: attrs.width || '100%', height: roundedHeight, aspectRatio: null };
|
|
247
291
|
}
|
|
248
|
-
return { ...attrs, height: roundedHeight, aspectRatio };
|
|
292
|
+
return { ...attrs, height: roundedHeight, aspectRatio: null };
|
|
293
|
+
}
|
|
294
|
+
function canUseAspectRatioPreset(kind) {
|
|
295
|
+
return kind === 'iframe' || kind === 'embed';
|
|
296
|
+
}
|
|
297
|
+
function canUseLayoutOptions(kind) {
|
|
298
|
+
return kind === 'image' || canUseAspectRatioPreset(kind);
|
|
299
|
+
}
|
|
300
|
+
function createToolbarGroupIcon(group) {
|
|
301
|
+
const icon = document.createElement('span');
|
|
302
|
+
icon.className = 'tiptap-media-toolbar-group-icon';
|
|
303
|
+
icon.dataset.group = group;
|
|
304
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
305
|
+
return icon;
|
|
249
306
|
}
|
|
250
307
|
function createResizeHandleDecoration(nodePos, widgetPos, resizeMeta, node) {
|
|
308
|
+
const widthKey = normalizeWidthAttr(node.attrs.width) || 'auto';
|
|
309
|
+
const aspectRatioKey = normalizeAspectRatioAttr(node.attrs.aspectRatio) || 'auto';
|
|
310
|
+
const horizontalAlignKey = normalizeHorizontalAlignAttr(node.attrs.horizontalAlign) || 'auto';
|
|
251
311
|
return Decoration.widget(widgetPos, () => {
|
|
252
312
|
const anchor = document.createElement('div');
|
|
253
313
|
anchor.className = 'tiptap-media-resize-anchor';
|
|
314
|
+
const isImageAnchor = resizeMeta.kind === 'image';
|
|
315
|
+
let controlsContainer = anchor;
|
|
316
|
+
if (isImageAnchor) {
|
|
317
|
+
anchor.classList.add('is-image-anchor');
|
|
318
|
+
const controls = document.createElement('div');
|
|
319
|
+
controls.className = 'tiptap-media-resize-controls';
|
|
320
|
+
controls.style.left = '0px';
|
|
321
|
+
controls.style.width = '100%';
|
|
322
|
+
anchor.append(controls);
|
|
323
|
+
controlsContainer = controls;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const normalizedWidth = normalizeWidthAttr(node.attrs.width);
|
|
327
|
+
if (normalizedWidth) {
|
|
328
|
+
if (normalizedWidth.endsWith('%')) {
|
|
329
|
+
anchor.style.width = normalizedWidth;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
const numericWidth = parseNumericSize(normalizedWidth);
|
|
333
|
+
if (numericWidth !== null) {
|
|
334
|
+
anchor.style.width = `${Math.round(numericWidth)}px`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
anchor.style.maxWidth = '100%';
|
|
338
|
+
}
|
|
339
|
+
const horizontalAlign = normalizeHorizontalAlignAttr(node.attrs.horizontalAlign);
|
|
340
|
+
if (horizontalAlign === 'center') {
|
|
341
|
+
anchor.style.marginLeft = 'auto';
|
|
342
|
+
anchor.style.marginRight = 'auto';
|
|
343
|
+
}
|
|
344
|
+
else if (horizontalAlign === 'right') {
|
|
345
|
+
anchor.style.marginLeft = 'auto';
|
|
346
|
+
anchor.style.marginRight = '0';
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
anchor.style.marginLeft = '0';
|
|
350
|
+
anchor.style.marginRight = '0';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (isImageAnchor) {
|
|
354
|
+
requestAnimationFrame(() => {
|
|
355
|
+
if (!anchor.isConnected)
|
|
356
|
+
return;
|
|
357
|
+
let candidate = anchor.previousElementSibling;
|
|
358
|
+
let imageElement = null;
|
|
359
|
+
while (candidate && !imageElement) {
|
|
360
|
+
if (candidate instanceof HTMLImageElement) {
|
|
361
|
+
imageElement = candidate;
|
|
362
|
+
}
|
|
363
|
+
else if (candidate instanceof HTMLElement) {
|
|
364
|
+
imageElement = candidate.querySelector('img');
|
|
365
|
+
}
|
|
366
|
+
candidate = candidate.previousElementSibling;
|
|
367
|
+
}
|
|
368
|
+
if (!imageElement && anchor.parentElement instanceof HTMLElement) {
|
|
369
|
+
imageElement =
|
|
370
|
+
anchor.parentElement.querySelector('figure.ProseMirror-selectednode img, img.ProseMirror-selectednode') || null;
|
|
371
|
+
}
|
|
372
|
+
if (!(imageElement instanceof HTMLElement))
|
|
373
|
+
return;
|
|
374
|
+
if (!(controlsContainer instanceof HTMLElement))
|
|
375
|
+
return;
|
|
376
|
+
const rect = imageElement.getBoundingClientRect();
|
|
377
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
378
|
+
return;
|
|
379
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
380
|
+
const leftOffset = rect.left - anchorRect.left;
|
|
381
|
+
const maxLeft = Math.max(0, anchorRect.width - rect.width);
|
|
382
|
+
const clampedLeft = clamp(leftOffset, 0, maxLeft);
|
|
383
|
+
const topOffset = rect.bottom - anchorRect.top + 6;
|
|
384
|
+
controlsContainer.style.top = `${Math.round(topOffset)}px`;
|
|
385
|
+
controlsContainer.style.left = `${Math.round(clampedLeft)}px`;
|
|
386
|
+
controlsContainer.style.width = `${Math.round(rect.width)}px`;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
254
389
|
const button = document.createElement('button');
|
|
255
390
|
button.type = 'button';
|
|
256
391
|
button.className = 'tiptap-media-resize-handle';
|
|
257
392
|
button.dataset.resizePos = String(nodePos);
|
|
258
393
|
button.dataset.resizeKind = resizeMeta.kind;
|
|
259
|
-
button.setAttribute('aria-label',
|
|
260
|
-
|
|
261
|
-
|
|
394
|
+
button.setAttribute('aria-label', canUseLayoutOptions(resizeMeta.kind)
|
|
395
|
+
? 'Resize media height (click for layout options)'
|
|
396
|
+
: 'Resize media height');
|
|
397
|
+
controlsContainer.append(button);
|
|
398
|
+
if (canUseLayoutOptions(resizeMeta.kind)) {
|
|
262
399
|
const selectedAspectRatio = normalizeAspectRatioAttr(node.attrs.aspectRatio);
|
|
400
|
+
const selectedHorizontalAlign = normalizeHorizontalAlignAttr(node.attrs.horizontalAlign);
|
|
401
|
+
const selectedWidthPreset = normalizeWidthPreset(node.attrs.width);
|
|
402
|
+
const supportsAspectRatio = canUseAspectRatioPreset(resizeMeta.kind);
|
|
403
|
+
const supportsBottomWidthPreset = resizeMeta.kind === 'image';
|
|
404
|
+
if (supportsAspectRatio) {
|
|
405
|
+
const widthHandle = document.createElement('button');
|
|
406
|
+
widthHandle.type = 'button';
|
|
407
|
+
widthHandle.className = 'tiptap-media-width-resize-handle';
|
|
408
|
+
widthHandle.dataset.resizePos = String(nodePos);
|
|
409
|
+
widthHandle.dataset.resizeKind = resizeMeta.kind;
|
|
410
|
+
widthHandle.setAttribute('aria-label', 'Resize media width (click for width presets)');
|
|
411
|
+
controlsContainer.append(widthHandle);
|
|
412
|
+
}
|
|
263
413
|
const toolbar = document.createElement('div');
|
|
264
414
|
toolbar.className = 'tiptap-media-aspect-ratio-toolbar';
|
|
265
415
|
toolbar.setAttribute('role', 'toolbar');
|
|
266
|
-
toolbar.setAttribute('aria-label', '
|
|
416
|
+
toolbar.setAttribute('aria-label', 'Media resize options');
|
|
267
417
|
toolbar.dataset.resizePos = String(nodePos);
|
|
268
|
-
|
|
418
|
+
let hasToolbarItems = false;
|
|
419
|
+
if (supportsAspectRatio) {
|
|
420
|
+
toolbar.append(createToolbarGroupIcon('aspect'));
|
|
421
|
+
for (const option of aspectRatioOptions) {
|
|
422
|
+
const optionButton = document.createElement('button');
|
|
423
|
+
optionButton.type = 'button';
|
|
424
|
+
optionButton.className = 'tiptap-media-aspect-ratio-option';
|
|
425
|
+
optionButton.dataset.resizePos = String(nodePos);
|
|
426
|
+
optionButton.dataset.aspectRatio = option.value ?? 'auto';
|
|
427
|
+
optionButton.textContent = option.label;
|
|
428
|
+
const isActive = option.value
|
|
429
|
+
? sameAspectRatio(option.value, selectedAspectRatio)
|
|
430
|
+
: !selectedAspectRatio;
|
|
431
|
+
optionButton.setAttribute('aria-pressed', isActive ? 'true' : 'false');
|
|
432
|
+
toolbar.append(optionButton);
|
|
433
|
+
}
|
|
434
|
+
hasToolbarItems = true;
|
|
435
|
+
}
|
|
436
|
+
if (hasToolbarItems) {
|
|
437
|
+
const separator = document.createElement('span');
|
|
438
|
+
separator.className = 'tiptap-media-toolbar-separator';
|
|
439
|
+
separator.setAttribute('aria-hidden', 'true');
|
|
440
|
+
toolbar.append(separator);
|
|
441
|
+
}
|
|
442
|
+
toolbar.append(createToolbarGroupIcon('align'));
|
|
443
|
+
for (const option of horizontalAlignOptions) {
|
|
269
444
|
const optionButton = document.createElement('button');
|
|
270
445
|
optionButton.type = 'button';
|
|
271
|
-
optionButton.className = 'tiptap-media-
|
|
446
|
+
optionButton.className = 'tiptap-media-horizontal-align-option';
|
|
272
447
|
optionButton.dataset.resizePos = String(nodePos);
|
|
273
|
-
optionButton.dataset.
|
|
448
|
+
optionButton.dataset.horizontalAlign = option.value ?? 'auto';
|
|
274
449
|
optionButton.textContent = option.label;
|
|
275
450
|
const isActive = option.value
|
|
276
|
-
?
|
|
277
|
-
: !
|
|
451
|
+
? option.value === selectedHorizontalAlign
|
|
452
|
+
: !selectedHorizontalAlign;
|
|
278
453
|
optionButton.setAttribute('aria-pressed', isActive ? 'true' : 'false');
|
|
279
454
|
toolbar.append(optionButton);
|
|
280
455
|
}
|
|
281
|
-
|
|
456
|
+
hasToolbarItems = true;
|
|
457
|
+
if (supportsBottomWidthPreset) {
|
|
458
|
+
const separator = document.createElement('span');
|
|
459
|
+
separator.className = 'tiptap-media-toolbar-separator';
|
|
460
|
+
separator.setAttribute('aria-hidden', 'true');
|
|
461
|
+
toolbar.append(separator);
|
|
462
|
+
toolbar.append(createToolbarGroupIcon('width'));
|
|
463
|
+
for (const option of widthPresetOptions) {
|
|
464
|
+
const optionButton = document.createElement('button');
|
|
465
|
+
optionButton.type = 'button';
|
|
466
|
+
optionButton.className = 'tiptap-media-width-option';
|
|
467
|
+
optionButton.dataset.resizePos = String(nodePos);
|
|
468
|
+
optionButton.dataset.widthPreset = option.value;
|
|
469
|
+
optionButton.textContent = option.label;
|
|
470
|
+
optionButton.setAttribute('aria-pressed', selectedWidthPreset === option.value ? 'true' : 'false');
|
|
471
|
+
toolbar.append(optionButton);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
controlsContainer.append(toolbar);
|
|
475
|
+
if (supportsAspectRatio) {
|
|
476
|
+
const widthToolbar = document.createElement('div');
|
|
477
|
+
widthToolbar.className = 'tiptap-media-width-toolbar';
|
|
478
|
+
widthToolbar.setAttribute('role', 'toolbar');
|
|
479
|
+
widthToolbar.setAttribute('aria-label', 'Media width presets');
|
|
480
|
+
widthToolbar.dataset.resizePos = String(nodePos);
|
|
481
|
+
widthToolbar.append(createToolbarGroupIcon('width'));
|
|
482
|
+
for (const option of widthPresetOptions) {
|
|
483
|
+
const optionButton = document.createElement('button');
|
|
484
|
+
optionButton.type = 'button';
|
|
485
|
+
optionButton.className = 'tiptap-media-width-option';
|
|
486
|
+
optionButton.dataset.resizePos = String(nodePos);
|
|
487
|
+
optionButton.dataset.widthPreset = option.value;
|
|
488
|
+
optionButton.textContent = option.label;
|
|
489
|
+
optionButton.setAttribute('aria-pressed', selectedWidthPreset === option.value ? 'true' : 'false');
|
|
490
|
+
widthToolbar.append(optionButton);
|
|
491
|
+
}
|
|
492
|
+
controlsContainer.append(widthToolbar);
|
|
493
|
+
}
|
|
282
494
|
}
|
|
283
495
|
return anchor;
|
|
284
|
-
}, {
|
|
496
|
+
}, {
|
|
497
|
+
side: 1,
|
|
498
|
+
key: `media-resize-${nodePos}-${resizeMeta.typeName}-${resizeMeta.kind}-${widthKey}-${aspectRatioKey}-${horizontalAlignKey}`
|
|
499
|
+
});
|
|
285
500
|
}
|
|
286
501
|
function tryCreateNodeSelection(doc, pos) {
|
|
287
502
|
if (pos < 0 || pos > doc.content.size)
|
|
@@ -342,8 +557,17 @@ export default Extension.create({
|
|
|
342
557
|
},
|
|
343
558
|
height: {
|
|
344
559
|
default: '600',
|
|
345
|
-
parseHTML: (element) =>
|
|
560
|
+
parseHTML: (element) => {
|
|
561
|
+
const aspectRatio = normalizeAspectRatioAttr(element.getAttribute('data-resize-aspect-ratio'));
|
|
562
|
+
if (aspectRatio)
|
|
563
|
+
return null;
|
|
564
|
+
return (normalizeNumericAttr(element.getAttribute('height') || element.style.height) ||
|
|
565
|
+
'600');
|
|
566
|
+
},
|
|
346
567
|
renderHTML: (attributes) => {
|
|
568
|
+
const aspectRatio = normalizeAspectRatioAttr(attributes.aspectRatio);
|
|
569
|
+
if (aspectRatio)
|
|
570
|
+
return {};
|
|
347
571
|
const height = normalizeNumericAttr(attributes.height) || '600';
|
|
348
572
|
return { height };
|
|
349
573
|
}
|
|
@@ -390,6 +614,14 @@ export default Extension.create({
|
|
|
390
614
|
const aspectRatio = normalizeAspectRatioAttr(attributes.aspectRatio);
|
|
391
615
|
return aspectRatio ? { 'data-resize-aspect-ratio': aspectRatio } : {};
|
|
392
616
|
}
|
|
617
|
+
},
|
|
618
|
+
horizontalAlign: {
|
|
619
|
+
default: null,
|
|
620
|
+
parseHTML: (element) => normalizeHorizontalAlignAttr(element.getAttribute('data-resize-horizontal-align')),
|
|
621
|
+
renderHTML: (attributes) => {
|
|
622
|
+
const horizontalAlign = normalizeHorizontalAlignAttr(attributes.horizontalAlign);
|
|
623
|
+
return horizontalAlign ? { 'data-resize-horizontal-align': horizontalAlign } : {};
|
|
624
|
+
}
|
|
393
625
|
}
|
|
394
626
|
}
|
|
395
627
|
}
|
|
@@ -399,11 +631,11 @@ export default Extension.create({
|
|
|
399
631
|
let removeDragListeners = null;
|
|
400
632
|
const closeOpenToolbars = (view, except = null) => {
|
|
401
633
|
view.dom
|
|
402
|
-
.querySelectorAll('.tiptap-media-resize-anchor.is-toolbar-open')
|
|
634
|
+
.querySelectorAll('.tiptap-media-resize-anchor.is-toolbar-open, .tiptap-media-resize-anchor.is-width-toolbar-open')
|
|
403
635
|
.forEach((anchor) => {
|
|
404
636
|
if (except && anchor === except)
|
|
405
637
|
return;
|
|
406
|
-
anchor.classList.remove('is-toolbar-open');
|
|
638
|
+
anchor.classList.remove('is-toolbar-open', 'is-width-toolbar-open');
|
|
407
639
|
});
|
|
408
640
|
};
|
|
409
641
|
return [
|
|
@@ -467,6 +699,60 @@ export default Extension.create({
|
|
|
467
699
|
return false;
|
|
468
700
|
if (!(event.target instanceof HTMLElement))
|
|
469
701
|
return false;
|
|
702
|
+
const widthOption = event.target.closest('.tiptap-media-width-option');
|
|
703
|
+
if (widthOption) {
|
|
704
|
+
event.preventDefault();
|
|
705
|
+
event.stopPropagation();
|
|
706
|
+
const pos = Number.parseInt(widthOption.dataset.resizePos || '', 10);
|
|
707
|
+
if (!Number.isFinite(pos))
|
|
708
|
+
return true;
|
|
709
|
+
const node = view.state.doc.nodeAt(pos);
|
|
710
|
+
if (!node)
|
|
711
|
+
return true;
|
|
712
|
+
const resizeMeta = resolveResizeMeta(node);
|
|
713
|
+
if (!resizeMeta || !canUseLayoutOptions(resizeMeta.kind))
|
|
714
|
+
return true;
|
|
715
|
+
const widthPreset = normalizeWidthPreset(widthOption.dataset.widthPreset);
|
|
716
|
+
if (!widthPreset)
|
|
717
|
+
return true;
|
|
718
|
+
if (node.attrs.width !== widthPreset) {
|
|
719
|
+
view.dispatch(view.state.tr.setNodeMarkup(pos, node.type, {
|
|
720
|
+
...node.attrs,
|
|
721
|
+
width: widthPreset
|
|
722
|
+
}));
|
|
723
|
+
}
|
|
724
|
+
closeOpenToolbars(view);
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
const horizontalAlignOption = event.target.closest('.tiptap-media-horizontal-align-option');
|
|
728
|
+
if (horizontalAlignOption) {
|
|
729
|
+
event.preventDefault();
|
|
730
|
+
event.stopPropagation();
|
|
731
|
+
const pos = Number.parseInt(horizontalAlignOption.dataset.resizePos || '', 10);
|
|
732
|
+
if (!Number.isFinite(pos))
|
|
733
|
+
return true;
|
|
734
|
+
const node = view.state.doc.nodeAt(pos);
|
|
735
|
+
if (!node)
|
|
736
|
+
return true;
|
|
737
|
+
const resizeMeta = resolveResizeMeta(node);
|
|
738
|
+
if (!resizeMeta || !canUseLayoutOptions(resizeMeta.kind))
|
|
739
|
+
return true;
|
|
740
|
+
const selectedHorizontalAlign = horizontalAlignOption.dataset.horizontalAlign || 'auto';
|
|
741
|
+
const normalizedHorizontalAlign = selectedHorizontalAlign === 'auto'
|
|
742
|
+
? null
|
|
743
|
+
: normalizeHorizontalAlignAttr(selectedHorizontalAlign);
|
|
744
|
+
if (selectedHorizontalAlign !== 'auto' && !normalizedHorizontalAlign)
|
|
745
|
+
return true;
|
|
746
|
+
const currentHorizontalAlign = normalizeHorizontalAlignAttr(node.attrs.horizontalAlign);
|
|
747
|
+
if (normalizedHorizontalAlign !== currentHorizontalAlign) {
|
|
748
|
+
view.dispatch(view.state.tr.setNodeMarkup(pos, node.type, {
|
|
749
|
+
...node.attrs,
|
|
750
|
+
horizontalAlign: normalizedHorizontalAlign
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
closeOpenToolbars(view);
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
470
756
|
const ratioOption = event.target.closest('.tiptap-media-aspect-ratio-option');
|
|
471
757
|
if (ratioOption) {
|
|
472
758
|
event.preventDefault();
|
|
@@ -478,7 +764,7 @@ export default Extension.create({
|
|
|
478
764
|
if (!node)
|
|
479
765
|
return true;
|
|
480
766
|
const resizeMeta = resolveResizeMeta(node);
|
|
481
|
-
if (!resizeMeta || resizeMeta.kind
|
|
767
|
+
if (!resizeMeta || !canUseAspectRatioPreset(resizeMeta.kind))
|
|
482
768
|
return true;
|
|
483
769
|
const target = resolveTargetElement(view, pos, resizeMeta, node);
|
|
484
770
|
if (!target)
|
|
@@ -506,6 +792,151 @@ export default Extension.create({
|
|
|
506
792
|
closeOpenToolbars(view);
|
|
507
793
|
return true;
|
|
508
794
|
}
|
|
795
|
+
const widthHandle = event.target.closest('.tiptap-media-width-resize-handle');
|
|
796
|
+
if (widthHandle) {
|
|
797
|
+
const pos = Number.parseInt(widthHandle.dataset.resizePos || '', 10);
|
|
798
|
+
if (!Number.isFinite(pos))
|
|
799
|
+
return false;
|
|
800
|
+
const node = view.state.doc.nodeAt(pos);
|
|
801
|
+
if (!node)
|
|
802
|
+
return false;
|
|
803
|
+
const resizeMeta = resolveResizeMeta(node);
|
|
804
|
+
if (!resizeMeta || !canUseAspectRatioPreset(resizeMeta.kind))
|
|
805
|
+
return false;
|
|
806
|
+
const resizeKind = widthHandle.dataset.resizeKind;
|
|
807
|
+
if (resizeKind && resizeMeta.kind !== resizeKind)
|
|
808
|
+
return false;
|
|
809
|
+
const target = resolveTargetElement(view, pos, resizeMeta, node);
|
|
810
|
+
if (!target)
|
|
811
|
+
return false;
|
|
812
|
+
event.preventDefault();
|
|
813
|
+
event.stopPropagation();
|
|
814
|
+
const anchor = widthHandle.closest('.tiptap-media-resize-anchor');
|
|
815
|
+
const startX = event.clientX;
|
|
816
|
+
const startY = event.clientY;
|
|
817
|
+
const startHeight = resolveStartHeight(resizeMeta.kind, node, target);
|
|
818
|
+
const targetParent = target.parentElement;
|
|
819
|
+
const shouldShowProxy = resizeMeta.kind !== 'image' && Boolean(targetParent);
|
|
820
|
+
const currentHorizontalAlign = normalizeHorizontalAlignAttr(node.attrs.horizontalAlign);
|
|
821
|
+
const startWidth = Math.max(1, resolveElementWidth(node, target) ||
|
|
822
|
+
targetParent?.getBoundingClientRect().width ||
|
|
823
|
+
0);
|
|
824
|
+
let frame = 0;
|
|
825
|
+
let pendingWidth = startWidth;
|
|
826
|
+
let resizeProxy = null;
|
|
827
|
+
let restoreTarget = null;
|
|
828
|
+
let isDragging = false;
|
|
829
|
+
let appliedDragCursor = false;
|
|
830
|
+
const previousCursor = document.body.style.cursor;
|
|
831
|
+
const previousSelect = document.body.style.userSelect;
|
|
832
|
+
const dispatchWidth = (width) => {
|
|
833
|
+
const current = view.state.doc.nodeAt(pos);
|
|
834
|
+
if (!current || current.type.name !== resizeMeta.typeName)
|
|
835
|
+
return;
|
|
836
|
+
const currentMeta = resolveResizeMeta(current);
|
|
837
|
+
if (!currentMeta || !canUseAspectRatioPreset(currentMeta.kind))
|
|
838
|
+
return;
|
|
839
|
+
const containerWidth = target.parentElement?.getBoundingClientRect().width || 0;
|
|
840
|
+
const nextWidth = containerWidth > 0
|
|
841
|
+
? `${Math.round(clamp((width / containerWidth) * 100, 10, 100))}%`
|
|
842
|
+
: String(Math.max(1, Math.round(width)));
|
|
843
|
+
if (nextWidth === current.attrs.width)
|
|
844
|
+
return;
|
|
845
|
+
view.dispatch(view.state.tr.setNodeMarkup(pos, current.type, {
|
|
846
|
+
...current.attrs,
|
|
847
|
+
width: nextWidth
|
|
848
|
+
}));
|
|
849
|
+
};
|
|
850
|
+
const beginDrag = () => {
|
|
851
|
+
if (isDragging)
|
|
852
|
+
return;
|
|
853
|
+
isDragging = true;
|
|
854
|
+
closeOpenToolbars(view);
|
|
855
|
+
if (shouldShowProxy && targetParent) {
|
|
856
|
+
const targetElement = target;
|
|
857
|
+
const originalDisplay = targetElement.style.display;
|
|
858
|
+
resizeProxy = document.createElement('div');
|
|
859
|
+
resizeProxy.className = 'tiptap-media-resize-proxy';
|
|
860
|
+
resizeProxy.style.height = `${Math.round(startHeight)}px`;
|
|
861
|
+
resizeProxy.style.width = `${Math.round(startWidth)}px`;
|
|
862
|
+
if (currentHorizontalAlign === 'center') {
|
|
863
|
+
resizeProxy.style.marginLeft = 'auto';
|
|
864
|
+
resizeProxy.style.marginRight = 'auto';
|
|
865
|
+
}
|
|
866
|
+
else if (currentHorizontalAlign === 'right') {
|
|
867
|
+
resizeProxy.style.marginLeft = 'auto';
|
|
868
|
+
resizeProxy.style.marginRight = '0';
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
resizeProxy.style.marginLeft = '0';
|
|
872
|
+
resizeProxy.style.marginRight = '0';
|
|
873
|
+
}
|
|
874
|
+
targetParent.insertBefore(resizeProxy, targetElement);
|
|
875
|
+
targetElement.style.display = 'none';
|
|
876
|
+
restoreTarget = () => {
|
|
877
|
+
targetElement.style.display = originalDisplay;
|
|
878
|
+
resizeProxy?.remove();
|
|
879
|
+
resizeProxy = null;
|
|
880
|
+
restoreTarget = null;
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
document.body.style.cursor = 'ew-resize';
|
|
884
|
+
document.body.style.userSelect = 'none';
|
|
885
|
+
appliedDragCursor = true;
|
|
886
|
+
};
|
|
887
|
+
const onMove = (moveEvent) => {
|
|
888
|
+
const deltaX = moveEvent.clientX - startX;
|
|
889
|
+
const deltaY = moveEvent.clientY - startY;
|
|
890
|
+
if (!isDragging && Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4)
|
|
891
|
+
return;
|
|
892
|
+
if (!isDragging)
|
|
893
|
+
beginDrag();
|
|
894
|
+
const containerWidth = target.parentElement?.getBoundingClientRect().width || 0;
|
|
895
|
+
const minWidth = containerWidth > 0 ? Math.max(120, containerWidth * 0.2) : 120;
|
|
896
|
+
const maxWidth = containerWidth > 0 ? containerWidth : maxHeight;
|
|
897
|
+
const nextWidth = clamp(startWidth + deltaX, minWidth, maxWidth);
|
|
898
|
+
pendingWidth = nextWidth;
|
|
899
|
+
if (resizeProxy)
|
|
900
|
+
resizeProxy.style.width = `${Math.round(nextWidth)}px`;
|
|
901
|
+
if (shouldShowProxy)
|
|
902
|
+
return;
|
|
903
|
+
if (frame)
|
|
904
|
+
cancelAnimationFrame(frame);
|
|
905
|
+
frame = requestAnimationFrame(() => dispatchWidth(nextWidth));
|
|
906
|
+
};
|
|
907
|
+
const cleanup = () => {
|
|
908
|
+
if (frame)
|
|
909
|
+
cancelAnimationFrame(frame);
|
|
910
|
+
window.removeEventListener('mousemove', onMove);
|
|
911
|
+
window.removeEventListener('mouseup', onUp);
|
|
912
|
+
if (appliedDragCursor) {
|
|
913
|
+
document.body.style.cursor = previousCursor;
|
|
914
|
+
document.body.style.userSelect = previousSelect;
|
|
915
|
+
}
|
|
916
|
+
restoreTarget?.();
|
|
917
|
+
removeDragListeners = null;
|
|
918
|
+
};
|
|
919
|
+
const onUp = () => {
|
|
920
|
+
if (!isDragging) {
|
|
921
|
+
const shouldOpen = Boolean(anchor) &&
|
|
922
|
+
!(anchor?.classList.contains('is-width-toolbar-open') ?? false);
|
|
923
|
+
closeOpenToolbars(view, shouldOpen && anchor ? anchor : null);
|
|
924
|
+
if (shouldOpen)
|
|
925
|
+
anchor?.classList.remove('is-toolbar-open');
|
|
926
|
+
anchor?.classList.toggle('is-width-toolbar-open', shouldOpen);
|
|
927
|
+
cleanup();
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (shouldShowProxy)
|
|
931
|
+
dispatchWidth(pendingWidth);
|
|
932
|
+
cleanup();
|
|
933
|
+
};
|
|
934
|
+
removeDragListeners?.();
|
|
935
|
+
window.addEventListener('mousemove', onMove);
|
|
936
|
+
window.addEventListener('mouseup', onUp);
|
|
937
|
+
removeDragListeners = cleanup;
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
509
940
|
const handle = event.target.closest('.tiptap-media-resize-handle');
|
|
510
941
|
if (!handle) {
|
|
511
942
|
closeOpenToolbars(view);
|
|
@@ -533,7 +964,9 @@ export default Extension.create({
|
|
|
533
964
|
const startY = event.clientY;
|
|
534
965
|
const startHeight = resolveStartHeight(resizeMeta.kind, node, target);
|
|
535
966
|
const imageRatio = resizeMeta.kind === 'image' ? resolveImageRatio(node, target) : 1;
|
|
536
|
-
const shouldShowProxy =
|
|
967
|
+
const shouldShowProxy = true;
|
|
968
|
+
const currentHorizontalAlign = normalizeHorizontalAlignAttr(node.attrs.horizontalAlign);
|
|
969
|
+
const startWidth = resolveElementWidth(node, target);
|
|
537
970
|
let resizeProxy = null;
|
|
538
971
|
let restoreTarget = null;
|
|
539
972
|
let frame = 0;
|
|
@@ -573,6 +1006,21 @@ export default Extension.create({
|
|
|
573
1006
|
resizeProxy = document.createElement('div');
|
|
574
1007
|
resizeProxy.className = 'tiptap-media-resize-proxy';
|
|
575
1008
|
resizeProxy.style.height = `${Math.round(startHeight)}px`;
|
|
1009
|
+
if (startWidth > 0) {
|
|
1010
|
+
resizeProxy.style.width = `${Math.round(startWidth)}px`;
|
|
1011
|
+
}
|
|
1012
|
+
if (currentHorizontalAlign === 'center') {
|
|
1013
|
+
resizeProxy.style.marginLeft = 'auto';
|
|
1014
|
+
resizeProxy.style.marginRight = 'auto';
|
|
1015
|
+
}
|
|
1016
|
+
else if (currentHorizontalAlign === 'right') {
|
|
1017
|
+
resizeProxy.style.marginLeft = 'auto';
|
|
1018
|
+
resizeProxy.style.marginRight = '0';
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
resizeProxy.style.marginLeft = '0';
|
|
1022
|
+
resizeProxy.style.marginRight = '0';
|
|
1023
|
+
}
|
|
576
1024
|
target.parentElement.insertBefore(resizeProxy, targetElement);
|
|
577
1025
|
targetElement.style.display = 'none';
|
|
578
1026
|
restoreTarget = () => {
|
|
@@ -619,6 +1067,8 @@ export default Extension.create({
|
|
|
619
1067
|
if (!isDragging) {
|
|
620
1068
|
const shouldOpen = Boolean(anchor) && !(anchor?.classList.contains('is-toolbar-open') ?? false);
|
|
621
1069
|
closeOpenToolbars(view, shouldOpen && anchor ? anchor : null);
|
|
1070
|
+
if (shouldOpen)
|
|
1071
|
+
anchor?.classList.remove('is-width-toolbar-open');
|
|
622
1072
|
anchor?.classList.toggle('is-toolbar-open', shouldOpen);
|
|
623
1073
|
cleanup();
|
|
624
1074
|
return;
|
|
@@ -12,11 +12,9 @@
|
|
|
12
12
|
import { quartOut } from 'svelte/easing';
|
|
13
13
|
import defaultI18n, { I18N_CONTEXT, type I18nTranslate } from '../i18n';
|
|
14
14
|
|
|
15
|
-
const editor = getContext<{ v: any }>('editor');
|
|
16
15
|
const i18nFromContext = getContext<I18nTranslate | undefined>(I18N_CONTEXT);
|
|
17
16
|
const i18n: I18nTranslate = (...args) =>
|
|
18
17
|
i18nFromContext ? i18nFromContext(...args) : defaultI18n(...args);
|
|
19
|
-
const tiptap = $derived(editor.v);
|
|
20
18
|
|
|
21
19
|
let height = $state(0);
|
|
22
20
|
let input = $state(''),
|
|
@@ -32,7 +30,6 @@
|
|
|
32
30
|
if (!editor || !range) return;
|
|
33
31
|
|
|
34
32
|
item.command({ editor, range });
|
|
35
|
-
setTimeout(() => tiptap?.commands?.focus?.());
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
function runDetailCommand() {
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
type I18nTranslate
|
|
19
19
|
} from '../i18n';
|
|
20
20
|
import type { UploadFn } from '../plugin/image/dragdrop';
|
|
21
|
-
import { fallbackUpload } from '../plugin/image/dragdrop';
|
|
22
21
|
import MediaResize, { type ResizeOptions } from '../plugin/resize';
|
|
23
22
|
import { Render } from 'nunui';
|
|
24
23
|
|
|
@@ -51,7 +50,7 @@
|
|
|
51
50
|
ref = $bindable(null),
|
|
52
51
|
options = {},
|
|
53
52
|
loaded = $bindable(false),
|
|
54
|
-
imageUpload
|
|
53
|
+
imageUpload,
|
|
55
54
|
style = '',
|
|
56
55
|
blocks = [],
|
|
57
56
|
placeholder,
|
|
@@ -82,6 +81,7 @@
|
|
|
82
81
|
'data-resize-min-height',
|
|
83
82
|
'data-resize-max-height',
|
|
84
83
|
'data-resize-aspect-ratio',
|
|
84
|
+
'data-resize-horizontal-align',
|
|
85
85
|
'data-bubble-menu',
|
|
86
86
|
'data-hide-bubble-menu'
|
|
87
87
|
];
|
|
@@ -341,6 +341,10 @@
|
|
|
341
341
|
outline: 3px solid var(--primary);
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
.editable :global(.ProseMirror) {
|
|
345
|
+
position: relative;
|
|
346
|
+
}
|
|
347
|
+
|
|
344
348
|
.editable :global(.tiptap-media-resize-anchor) {
|
|
345
349
|
width: 100%;
|
|
346
350
|
display: flex;
|
|
@@ -353,6 +357,27 @@
|
|
|
353
357
|
overflow: visible;
|
|
354
358
|
}
|
|
355
359
|
|
|
360
|
+
.editable :global(.tiptap-media-resize-anchor.is-image-anchor) {
|
|
361
|
+
position: relative;
|
|
362
|
+
display: block;
|
|
363
|
+
width: 100%;
|
|
364
|
+
height: 0;
|
|
365
|
+
margin: 0;
|
|
366
|
+
z-index: 4;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.editable :global(.tiptap-media-resize-anchor.is-image-anchor .tiptap-media-resize-controls) {
|
|
370
|
+
position: absolute;
|
|
371
|
+
top: 0;
|
|
372
|
+
left: 0;
|
|
373
|
+
display: flex;
|
|
374
|
+
flex-direction: column;
|
|
375
|
+
align-items: center;
|
|
376
|
+
line-height: 0;
|
|
377
|
+
pointer-events: none;
|
|
378
|
+
overflow: visible;
|
|
379
|
+
}
|
|
380
|
+
|
|
356
381
|
.editable :global(.tiptap-media-resize-handle) {
|
|
357
382
|
appearance: none;
|
|
358
383
|
-webkit-appearance: none;
|
|
@@ -383,6 +408,39 @@
|
|
|
383
408
|
transform: translateY(1px);
|
|
384
409
|
}
|
|
385
410
|
|
|
411
|
+
.editable :global(.tiptap-media-width-resize-handle) {
|
|
412
|
+
appearance: none;
|
|
413
|
+
-webkit-appearance: none;
|
|
414
|
+
position: absolute;
|
|
415
|
+
top: 0;
|
|
416
|
+
right: 0;
|
|
417
|
+
width: 12px;
|
|
418
|
+
height: 12px;
|
|
419
|
+
margin: 0;
|
|
420
|
+
padding: 0;
|
|
421
|
+
border: 1px solid var(--primary-light3, rgba(120, 120, 120, 0.45));
|
|
422
|
+
border-radius: 999px;
|
|
423
|
+
background: var(--primary-light6, rgba(120, 120, 120, 0.2));
|
|
424
|
+
cursor: ew-resize;
|
|
425
|
+
pointer-events: auto;
|
|
426
|
+
transform: translate(40%, -40%);
|
|
427
|
+
transition:
|
|
428
|
+
background-color 0.15s ease,
|
|
429
|
+
border-color 0.15s ease,
|
|
430
|
+
transform 0.15s ease;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.editable :global(.tiptap-media-width-resize-handle:hover),
|
|
434
|
+
.editable :global(.tiptap-media-width-resize-handle:focus-visible) {
|
|
435
|
+
background: var(--primary-light4, rgba(120, 120, 120, 0.35));
|
|
436
|
+
border-color: var(--primary-light2, rgba(100, 100, 100, 0.55));
|
|
437
|
+
outline: none;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.editable :global(.tiptap-media-width-resize-handle:active) {
|
|
441
|
+
transform: translate(40%, -40%) scale(0.95);
|
|
442
|
+
}
|
|
443
|
+
|
|
386
444
|
.editable :global(.tiptap-media-aspect-ratio-toolbar) {
|
|
387
445
|
display: none;
|
|
388
446
|
align-items: center;
|
|
@@ -402,12 +460,81 @@
|
|
|
402
460
|
white-space: nowrap;
|
|
403
461
|
}
|
|
404
462
|
|
|
463
|
+
.editable :global(.tiptap-media-width-toolbar) {
|
|
464
|
+
display: none;
|
|
465
|
+
align-items: center;
|
|
466
|
+
gap: 4px;
|
|
467
|
+
padding: 4px;
|
|
468
|
+
border: 1px solid var(--primary-light3, rgba(120, 120, 120, 0.4));
|
|
469
|
+
border-radius: 999px;
|
|
470
|
+
background: var(--surface, #fff);
|
|
471
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
|
472
|
+
pointer-events: auto;
|
|
473
|
+
line-height: 1;
|
|
474
|
+
position: absolute;
|
|
475
|
+
top: calc(100% + 6px);
|
|
476
|
+
right: 0;
|
|
477
|
+
z-index: 4;
|
|
478
|
+
white-space: nowrap;
|
|
479
|
+
}
|
|
480
|
+
|
|
405
481
|
.editable
|
|
406
482
|
:global(.tiptap-media-resize-anchor.is-toolbar-open .tiptap-media-aspect-ratio-toolbar) {
|
|
407
483
|
display: flex;
|
|
408
484
|
}
|
|
409
485
|
|
|
410
|
-
.editable :global(.tiptap-media-
|
|
486
|
+
.editable :global(.tiptap-media-resize-anchor.is-width-toolbar-open .tiptap-media-width-toolbar) {
|
|
487
|
+
display: flex;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.editable :global(.tiptap-media-toolbar-separator) {
|
|
491
|
+
width: 1px;
|
|
492
|
+
height: 16px;
|
|
493
|
+
background: var(--primary-light2, rgba(120, 120, 120, 0.35));
|
|
494
|
+
opacity: 0.9;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.editable :global(.tiptap-media-toolbar-group-icon) {
|
|
498
|
+
width: 14px;
|
|
499
|
+
height: 14px;
|
|
500
|
+
border: 1px solid var(--primary-light3, rgba(120, 120, 120, 0.45));
|
|
501
|
+
border-radius: 4px;
|
|
502
|
+
color: var(--on-surface, #333);
|
|
503
|
+
opacity: 0.75;
|
|
504
|
+
flex: 0 0 auto;
|
|
505
|
+
pointer-events: none;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.editable :global(.tiptap-media-toolbar-group-icon[data-group='aspect']) {
|
|
509
|
+
background: linear-gradient(
|
|
510
|
+
135deg,
|
|
511
|
+
transparent 42%,
|
|
512
|
+
currentColor 43%,
|
|
513
|
+
currentColor 57%,
|
|
514
|
+
transparent 58%
|
|
515
|
+
)
|
|
516
|
+
center / 100% 100% no-repeat;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.editable :global(.tiptap-media-toolbar-group-icon[data-group='align']) {
|
|
520
|
+
background:
|
|
521
|
+
linear-gradient(currentColor, currentColor) left 2px top 3px / 8px 1.5px no-repeat,
|
|
522
|
+
linear-gradient(currentColor, currentColor) center top 6px / 10px 1.5px no-repeat,
|
|
523
|
+
linear-gradient(currentColor, currentColor) right 2px top 9px / 8px 1.5px no-repeat;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.editable :global(.tiptap-media-toolbar-group-icon[data-group='width']) {
|
|
527
|
+
background:
|
|
528
|
+
linear-gradient(currentColor, currentColor) center / 8px 1.5px no-repeat,
|
|
529
|
+
linear-gradient(45deg, transparent 38%, currentColor 39%, currentColor 61%, transparent 62%)
|
|
530
|
+
left 2px center / 4px 4px no-repeat,
|
|
531
|
+
linear-gradient(-45deg, transparent 38%, currentColor 39%, currentColor 61%, transparent 62%)
|
|
532
|
+
right 2px center / 4px 4px no-repeat;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.editable :global(.tiptap-media-aspect-ratio-option),
|
|
536
|
+
.editable :global(.tiptap-media-horizontal-align-option),
|
|
537
|
+
.editable :global(.tiptap-media-width-option) {
|
|
411
538
|
appearance: none;
|
|
412
539
|
-webkit-appearance: none;
|
|
413
540
|
margin: 0;
|
|
@@ -423,12 +550,18 @@
|
|
|
423
550
|
}
|
|
424
551
|
|
|
425
552
|
.editable :global(.tiptap-media-aspect-ratio-option:hover),
|
|
426
|
-
.editable :global(.tiptap-media-aspect-ratio-option:focus-visible)
|
|
553
|
+
.editable :global(.tiptap-media-aspect-ratio-option:focus-visible),
|
|
554
|
+
.editable :global(.tiptap-media-horizontal-align-option:hover),
|
|
555
|
+
.editable :global(.tiptap-media-horizontal-align-option:focus-visible),
|
|
556
|
+
.editable :global(.tiptap-media-width-option:hover),
|
|
557
|
+
.editable :global(.tiptap-media-width-option:focus-visible) {
|
|
427
558
|
background: var(--primary-light1, rgba(120, 120, 120, 0.14));
|
|
428
559
|
outline: none;
|
|
429
560
|
}
|
|
430
561
|
|
|
431
|
-
.editable :global(.tiptap-media-aspect-ratio-option[aria-pressed='true'])
|
|
562
|
+
.editable :global(.tiptap-media-aspect-ratio-option[aria-pressed='true']),
|
|
563
|
+
.editable :global(.tiptap-media-horizontal-align-option[aria-pressed='true']),
|
|
564
|
+
.editable :global(.tiptap-media-width-option[aria-pressed='true']) {
|
|
432
565
|
background: var(--primary-light4, rgba(120, 120, 120, 0.3));
|
|
433
566
|
color: var(--on-primary, #000);
|
|
434
567
|
}
|
|
@@ -469,6 +602,10 @@
|
|
|
469
602
|
position: relative;
|
|
470
603
|
}
|
|
471
604
|
|
|
605
|
+
& :global(figure[data-bubble-menu='false']) {
|
|
606
|
+
margin: 0;
|
|
607
|
+
}
|
|
608
|
+
|
|
472
609
|
& :global(code.inline) {
|
|
473
610
|
background: var(--primary-light1);
|
|
474
611
|
padding: 2px 4px;
|
|
@@ -535,8 +672,38 @@
|
|
|
535
672
|
& :global(iframe),
|
|
536
673
|
& :global(embed) {
|
|
537
674
|
display: block;
|
|
538
|
-
width: 100%;
|
|
539
675
|
max-width: 100%;
|
|
676
|
+
margin-left: var(--tiptap-media-horizontal-margin-left, 0);
|
|
677
|
+
margin-right: var(--tiptap-media-horizontal-margin-right, 0);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
& :global(img[data-resize-horizontal-align]) {
|
|
681
|
+
display: block;
|
|
682
|
+
margin-left: var(--tiptap-media-horizontal-margin-left, 0);
|
|
683
|
+
margin-right: var(--tiptap-media-horizontal-margin-right, 0);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
& :global(img[width='100%']) {
|
|
687
|
+
width: 100%;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
& :global(img[width='50%']) {
|
|
691
|
+
width: 50%;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
& :global([data-resize-horizontal-align='left']) {
|
|
695
|
+
--tiptap-media-horizontal-margin-left: 0;
|
|
696
|
+
--tiptap-media-horizontal-margin-right: auto;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
& :global([data-resize-horizontal-align='center']) {
|
|
700
|
+
--tiptap-media-horizontal-margin-left: auto;
|
|
701
|
+
--tiptap-media-horizontal-margin-right: auto;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
& :global([data-resize-horizontal-align='right']) {
|
|
705
|
+
--tiptap-media-horizontal-margin-left: auto;
|
|
706
|
+
--tiptap-media-horizontal-margin-right: 0;
|
|
540
707
|
}
|
|
541
708
|
|
|
542
709
|
& :global([data-resize-aspect-ratio='16:9']) {
|