Image.jsx 24.1 KB
Newer Older
yakoff94's avatar
yakoff94 committed
1
import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
2
import React, { useEffect, useRef, useState, Fragment } from "react";
yakoff94's avatar
yakoff94 committed
3
import TipTapImage from "@tiptap/extension-image";
Яков's avatar
update    
Яков committed
4
import { Button, Modal, Input } from 'antd';
Яков's avatar
update    
Яков committed
5
import {FontSizeOutlined} from "@ant-design/icons";
Яков's avatar
update    
Яков committed
6
7
const { TextArea } = Input;

yakoff94's avatar
yakoff94 committed
8
9
10

const MIN_WIDTH = 60;
const BORDER_COLOR = '#0096fd';
Яков's avatar
Яков committed
11
const ALIGN_OPTIONS = ['left', 'center', 'right', 'text'];
yakoff94's avatar
yakoff94 committed
12

Яков's avatar
fix    
Яков committed
13
const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, selected }) => {
yakoff94's avatar
yakoff94 committed
14
    const imgRef = useRef(null);
15
16
17
    const wrapperRef = useRef(null);
    const [showAlignMenu, setShowAlignMenu] = useState(false);
    const isInitialized = useRef(false);
Яков's avatar
update    
Яков committed
18
    const [isResizing, setIsResizing] = useState(false);
Яков's avatar
update    
Яков committed
19
20
21
    const [altModalVisible, setAltModalVisible] = useState(false);
    const [tempAlt, setTempAlt] = useState(node.attrs.alt || '');

Яков's avatar
update    
Яков committed
22

Яков's avatar
fix    
Яков committed
23
24
25
26
    // Добавляем прозрачный нулевой пробел после изображения
    useEffect(() => {
        if (!editor || !getPos) return;

Яков's avatar
update    
Яков committed
27
28
29
30
31
32
33
34
35
        let pos;
        try {
            pos = getPos();
            if (typeof pos !== 'number') return;
        } catch (e) {
            console.warn('getPos() failed:', e);
            return;
        }

Яков's avatar
fix    
Яков committed
36
37
38
        const doc = editor.state.doc;

        if (doc.nodeSize > pos && doc.nodeAt(pos)?.textContent !== '\u200B') {
Яков's avatar
update    
Яков committed
39
            editor.commands.insertContentAt(pos + 1, {
Яков's avatar
fix    
Яков committed
40
                type: 'text',
Яков's avatar
update    
Яков committed
41
                text: '\u200B'
Яков's avatar
fix    
Яков committed
42
43
44
45
            });
        }
    }, [editor, getPos]);

Яков's avatar
update    
Яков committed
46

Яков's avatar
fix    
Яков committed
47
48
    // Получаем текущую ширину редактора и доступное пространство
    const getEditorDimensions = () => {
Яков's avatar
Яков committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
        const editorContent = editor?.options?.element?.closest('.atma-editor-content');
        if (!editorContent) return { width: Infinity, availableSpace: Infinity };

        const fullEditorWidth = editorContent.clientWidth;
        const editorStyles = window.getComputedStyle(editorContent);
        const paddingLeft = parseFloat(editorStyles.paddingLeft) || 0;
        const paddingRight = parseFloat(editorStyles.paddingRight) || 0;
        const availableEditorWidth = fullEditorWidth - paddingLeft - paddingRight;

        let container;

        // при center — всегда редактор
        if (node.attrs.align === 'center') {
            container = editorContent;
        } else {
            // при других выравниваниях — ближайший блок
            container = imgRef.current?.closest('li, blockquote, td, p, div') || editorContent;
Яков's avatar
fix    
Яков committed
66
67
        }

Яков's avatar
Яков committed
68
69
70
71
72
73
74
75
76
        const containerStyles = window.getComputedStyle(container);
        const containerPaddingLeft = parseFloat(containerStyles.paddingLeft) || 0;
        const containerPaddingRight = parseFloat(containerStyles.paddingRight) || 0;
        const containerWidth = container.clientWidth - containerPaddingLeft - containerPaddingRight;

        return {
            width: containerWidth,            // текущая ширина контейнера
            availableSpace: availableEditorWidth // фиксированная доступная ширина
        };
Яков's avatar
fix    
Яков committed
77
78
    };

Яков's avatar
Яков committed
79

Яков's avatar
fix    
Яков committed
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
    // Безопасное обновление атрибутов с учетом выравнивания и границ
    const safeUpdateAttributes = (newAttrs) => {
        const { width: editorWidth, availableSpace } = getEditorDimensions();
        let { width, height, align } = { ...node.attrs, ...newAttrs };
        const newAlign = newAttrs.align || align;

        // При изменении выравнивания проверяем доступное пространство
        if (newAlign && newAlign !== align) {
            const maxWidth = availableSpace;
            if (width > maxWidth) {
                const ratio = maxWidth / width;
                width = maxWidth;
                height = Math.round(height * ratio);
            }
        } else {
            // Для обычного обновления размеров
            const maxWidth = availableSpace;
            if (width > maxWidth) {
                const ratio = maxWidth / width;
                width = maxWidth;
                height = Math.round(height * ratio);
            }
        }

        // Проверяем минимальный размер
        if (width < MIN_WIDTH) {
            const ratio = MIN_WIDTH / width;
            width = MIN_WIDTH;
            height = Math.round(height * ratio);
        }

        updateAttributes({ width, height, ...newAttrs });
Яков's avatar
update    
Яков committed
112
    };
113

Яков's avatar
fix    
Яков committed
114
    // Инициализация изображения
115
    useEffect(() => {
Яков's avatar
fix    
Яков committed
116
        if (!node.attrs['data-node-id']) {
Яков's avatar
fix    
Яков committed
117
            safeUpdateAttributes({
Яков's avatar
fix    
Яков committed
118
119
120
                'data-node-id': `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
            });
        }
Яков's avatar
fix    
Яков committed
121
    }, [node.attrs['data-node-id']]);
Яков's avatar
fix    
Яков committed
122

Яков's avatar
fix    
Яков committed
123
    // Обработка кликов вне изображения
Яков's avatar
fix    
Яков committed
124
125
126
    useEffect(() => {
        const handleClickOutside = (event) => {
            if (wrapperRef.current && !wrapperRef.current.contains(event.target) && selected) {
Яков's avatar
update    
Яков committed
127
128
129
130
131
132
133
134
135
                try {
                    const pos = getPos?.()
                    if (typeof pos === 'number') {
                        editor.commands.setNodeSelection(pos)
                    }
                } catch (e) {
                    console.warn('getPos() failed:', e)
                }
                // editor.commands.setNodeSelection(getPos());
Яков's avatar
fix    
Яков committed
136
137
            }
        };
Яков's avatar
fix    
Яков committed
138
139
140
        document.addEventListener('mousedown', handleClickOutside);
        return () => document.removeEventListener('mousedown', handleClickOutside);
    }, [selected, editor, getPos]);
yakoff94's avatar
yakoff94 committed
141

Яков's avatar
fix    
Яков committed
142
    // Загрузка и инициализация изображения
yakoff94's avatar
yakoff94 committed
143
    useEffect(() => {
Яков's avatar
fix    
Яков committed
144
145
146
147
        if (!imgRef.current || isInitialized.current) return;

        const initImageSize = () => {
            try {
Яков's avatar
fix    
Яков committed
148
149
150
151
152
                // Если размеры уже заданы в атрибутах - используем их сразу
                if (node.attrs.width && node.attrs.height) {
                    isInitialized.current = true;
                    return;
                }
Яков's avatar
fix    
Яков committed
153

Яков's avatar
fix    
Яков committed
154
                const { width: editorWidth } = getEditorDimensions();
Яков's avatar
fix    
Яков committed
155
156
                const naturalWidth = imgRef.current.naturalWidth;
                const naturalHeight = imgRef.current.naturalHeight;
Яков's avatar
update    
Яков committed
157

Яков's avatar
fix    
Яков committed
158
159
                if (naturalWidth <= 0 || naturalHeight <= 0) {
                    console.warn('Image has invalid natural dimensions, retrying...');
Яков's avatar
fix    
Яков committed
160
                    setTimeout(initImageSize, 100);
Яков's avatar
fix    
Яков committed
161
162
163
164
165
166
167
168
169
170
171
172
                    return;
                }

                let initialWidth = naturalWidth;
                let initialHeight = naturalHeight;

                if (initialWidth > editorWidth) {
                    const ratio = editorWidth / initialWidth;
                    initialWidth = editorWidth;
                    initialHeight = Math.round(initialHeight * ratio);
                }

Яков's avatar
fix    
Яков committed
173
                safeUpdateAttributes({
Яков's avatar
fix    
Яков committed
174
175
176
                    width: initialWidth,
                    height: initialHeight,
                    'data-node-id': node.attrs['data-node-id'] || `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
Яков's avatar
fix    
Яков committed
177
178
                });
                isInitialized.current = true;
Яков's avatar
fix    
Яков committed
179
180
181
182
183
            } catch (error) {
                console.warn('Error initializing image size:', error);
            }
        };

Яков's avatar
fix    
Яков committed
184
        const handleLoad = () => {
Яков's avatar
fix    
Яков committed
185
186
187
188
189
            // Если размеры уже заданы в атрибутах, пропускаем инициализацию
            if (node.attrs.width && node.attrs.height) {
                isInitialized.current = true;
                return;
            }
Яков's avatar
fix    
Яков committed
190
191
192
            setTimeout(initImageSize, 50);
        };

Яков's avatar
fix    
Яков committed
193
        if (imgRef.current.complete) {
Яков's avatar
fix    
Яков committed
194
            handleLoad();
Яков's avatar
fix    
Яков committed
195
        } else {
Яков's avatar
fix    
Яков committed
196
            imgRef.current.addEventListener('load', handleLoad);
197
        }
Яков's avatar
fix    
Яков committed
198
199

        return () => {
Яков's avatar
fix    
Яков committed
200
201
202
            if (imgRef.current) {
                imgRef.current.removeEventListener('load', handleLoad);
            }
Яков's avatar
fix    
Яков committed
203
        };
Яков's avatar
fix    
Яков committed
204
    }, [node.attrs.width, node.attrs.height, node.attrs['data-node-id']]);
205

Яков's avatar
fix    
Яков committed
206
    // Обработка ресайза изображения
207
208
209
210
    const handleResizeStart = (direction) => (e) => {
        e.preventDefault();
        e.stopPropagation();

Яков's avatar
update    
Яков committed
211
        setIsResizing(true);
Яков's avatar
update    
Яков committed
212
213
214
215
216
217
218
219
220
        try {
            const pos = getPos?.()
            if (typeof pos === 'number') {
                editor.commands.setNodeSelection(pos)
            }
        } catch (e) {
            console.warn('getPos() failed:', e)
        }
        // editor.commands.setNodeSelection(getPos());
Яков's avatar
update    
Яков committed
221

Яков's avatar
fix    
Яков committed
222
223
224
225
226
        const startWidth = node.attrs.width || imgRef.current.naturalWidth;
        const startHeight = node.attrs.height || imgRef.current.naturalHeight;
        const aspectRatio = startWidth / startHeight;
        const startX = e.clientX;
        const startY = e.clientY;
Яков's avatar
Яков committed
227
        const { width: initialEditorWidth, availableSpace: initialAvailableSpace } = getEditorDimensions();
228
229

        const onMouseMove = (e) => {
Яков's avatar
Яков committed
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
            requestAnimationFrame(() => {
                const maxWidth = node.attrs.align === 'center' ? initialEditorWidth : initialAvailableSpace;

                const deltaX = e.clientX - startX;
                const deltaY = e.clientY - startY;

                let newWidth, newHeight;

                if (node.attrs.align === 'center') {
                    if (direction.includes('n') || direction.includes('s')) {
                        const scale = direction.includes('s') ? 1 : -1;
                        newHeight = Math.max(startHeight + deltaY * scale, MIN_WIDTH);
                        newWidth = Math.min(Math.round(newHeight * aspectRatio), maxWidth);
                        newHeight = Math.round(newWidth / aspectRatio);
                    } else {
                        const scale = direction.includes('e') ? 1 : -1;
                        newWidth = Math.min(
                            Math.max(startWidth + deltaX * scale, MIN_WIDTH),
                            maxWidth
                        );
                        newHeight = Math.round(newWidth / aspectRatio);
                    }
252
                } else {
Яков's avatar
Яков committed
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
                    if (direction.includes('e') || direction.includes('w')) {
                        const scale = direction.includes('e') ? 1 : -1;
                        newWidth = Math.min(
                            Math.max(startWidth + deltaX * scale, MIN_WIDTH),
                            maxWidth
                        );
                        newHeight = Math.round(newWidth / aspectRatio);
                    } else {
                        const scale = direction.includes('s') ? 1 : -1;
                        newHeight = Math.max(startHeight + deltaY * scale, MIN_WIDTH);
                        newWidth = Math.min(
                            Math.round(newHeight * aspectRatio),
                            maxWidth
                        );
                        newHeight = Math.round(newWidth / aspectRatio);
                    }
269
270
                }

Яков's avatar
Яков committed
271
272
                safeUpdateAttributes({ width: newWidth, height: newHeight });
            });
yakoff94's avatar
yakoff94 committed
273
274
        };

Яков's avatar
Яков committed
275

276
277
278
        const onMouseUp = () => {
            window.removeEventListener('mousemove', onMouseMove);
            window.removeEventListener('mouseup', onMouseUp);
Яков's avatar
update    
Яков committed
279
            setIsResizing(false);
Яков's avatar
update    
Яков committed
280
281
282
283
284
285
286
287
288
            try {
                const pos = getPos?.()
                if (typeof pos === 'number') {
                    editor.commands.setNodeSelection(pos)
                }
            } catch (e) {
                console.warn('getPos() failed:', e)
            }
            // editor.commands.setNodeSelection(getPos());
Яков's avatar
fix    
Яков committed
289
            editor.commands.focus();
yakoff94's avatar
yakoff94 committed
290
291
        };

292
293
294
        window.addEventListener('mousemove', onMouseMove);
        window.addEventListener('mouseup', onMouseUp);
    };
yakoff94's avatar
yakoff94 committed
295

Яков's avatar
fix    
Яков committed
296
    // Изменение выравнивания с автоматическим масштабированием
297
    const handleAlign = (align) => {
Яков's avatar
Яков committed
298
299
300
301
        safeUpdateAttributes({ align }); // первый вызов
        setTimeout(() => {
            safeUpdateAttributes({ align }); // повторный вызов с обновлёнными размерами
        }, 50);
302
        setShowAlignMenu(false);
Яков's avatar
fix    
Яков committed
303
        editor.commands.focus();
304
305
    };

Яков's avatar
fix    
Яков committed
306
    // Стили для обертки изображения
Яков's avatar
update    
Яков committed
307
308
    const getWrapperStyle = () => {
        const baseStyle = {
309
            display: 'inline-block',
Яков's avatar
update    
Яков committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
            lineHeight: 0,
            position: 'relative',
            outline: (selected || isResizing) ? `1px dashed ${BORDER_COLOR}` : 'none',
            verticalAlign: 'top',
            margin: '0.5rem 0',
        };

        if (node.attrs.align === 'center') {
            return {
                ...baseStyle,
                display: 'block',
                marginLeft: 'auto',
                marginRight: 'auto',
                width: node.attrs.width ? `${node.attrs.width}px` : 'fit-content',
                maxWidth: '100%',
                textAlign: 'center'
            };
        }

        return {
            ...baseStyle,
            ...(node.attrs.align === 'left' && {
                float: 'left',
                marginRight: '1rem',
                width: node.attrs.width ? `${node.attrs.width}px` : 'auto',
            }),
            ...(node.attrs.align === 'right' && {
                float: 'right',
                marginLeft: '1rem',
                width: node.attrs.width ? `${node.attrs.width}px` : 'auto',
            }),
            ...(node.attrs.align === 'text' && {
                display: 'inline-block',
                float: 'none',
                margin: '0 0.2rem',
                verticalAlign: 'middle',
                width: node.attrs.width ? `${node.attrs.width}px` : 'auto',
            }),
        };
    };
yakoff94's avatar
yakoff94 committed
350

Яков's avatar
fix    
Яков committed
351
    // Стили для самого изображения
352
353
    const getImageStyle = () => ({
        width: node.attrs.width ? `${node.attrs.width}px` : 'auto',
Яков's avatar
fix    
Яков committed
354
        height: 'auto',
355
356
357
358
        maxWidth: '100%',
        display: 'block',
        cursor: 'default',
        userSelect: 'none',
Яков's avatar
Яков committed
359
        margin: node.attrs.align === 'center' ? '0 auto' : '0',
Яков's avatar
update    
Яков committed
360
        verticalAlign: node.attrs.align === 'text' ? 'middle' : 'top',
Яков's avatar
fix    
Яков committed
361
        objectFit: 'contain'
362
363
    });

yakoff94's avatar
yakoff94 committed
364
365
    return (
        <NodeViewWrapper
366
            as="div"
367
368
369
370
            style={getWrapperStyle()}
            ref={wrapperRef}
            onClick={(e) => {
                e.stopPropagation();
Яков's avatar
update    
Яков committed
371
372
373
374
375
376
377
378
379
                try {
                    const pos = getPos?.()
                    if (typeof pos === 'number') {
                        editor.commands.setNodeSelection(pos)
                    }
                } catch (e) {
                    console.warn('getPos() failed:', e)
                }
                // editor.commands.setNodeSelection(getPos());
yakoff94's avatar
yakoff94 committed
380
            }}
381
382
            contentEditable={false}
            data-image-wrapper
yakoff94's avatar
yakoff94 committed
383
384
        >
            <img
385
386
                {...node.attrs}
                ref={imgRef}
Яков's avatar
update    
Яков committed
387
                draggable={true}
388
                style={getImageStyle()}
389
                onLoad={() => {
Яков's avatar
fix    
Яков committed
390
                    if (imgRef.current && !isInitialized.current && !node.attrs.width && !node.attrs.height) {
Яков's avatar
fix    
Яков committed
391
                        const { width: editorWidth } = getEditorDimensions();
Яков's avatar
update    
Яков committed
392
393
394
                        const naturalWidth = imgRef.current.naturalWidth;
                        const naturalHeight = imgRef.current.naturalHeight;

Яков's avatar
fix    
Яков committed
395
396
397
                        safeUpdateAttributes({
                            width: naturalWidth,
                            height: naturalHeight,
Яков's avatar
fix    
Яков committed
398
                            'data-node-id': node.attrs['data-node-id'] || Math.random().toString(36).substr(2, 9)
399
400
401
                        });
                        isInitialized.current = true;
                    }
yakoff94's avatar
yakoff94 committed
402
403
                }}
            />
404

Яков's avatar
update    
Яков committed
405
            {(selected || isResizing) && (
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
                <Fragment>
                    {['nw', 'ne', 'sw', 'se'].map(dir => (
                        <div
                            key={dir}
                            onMouseDown={handleResizeStart(dir)}
                            style={{
                                position: 'absolute',
                                width: 12,
                                height: 12,
                                backgroundColor: BORDER_COLOR,
                                border: '1px solid white',
                                [dir[0] === 'n' ? 'top' : 'bottom']: -6,
                                [dir[1] === 'w' ? 'left' : 'right']: node.attrs.align === 'center' ? '50%' : -6,
                                transform: node.attrs.align === 'center' ?
                                    `translateX(${dir[1] === 'w' ? '-100%' : '0%'})` : 'none',
                                cursor: `${dir}-resize`,
                                zIndex: 10
                            }}
                        />
yakoff94's avatar
yakoff94 committed
425
                    ))}
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441

                    {showAlignMenu && (
                        <div style={{
                            position: 'absolute',
                            top: -40,
                            left: '50%',
                            transform: 'translateX(-50%)',
                            backgroundColor: 'white',
                            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
                            borderRadius: 4,
                            padding: 4,
                            zIndex: 20,
                            display: 'flex'
                        }}>
                            {ALIGN_OPTIONS.map(align => (
                                <button
Яков's avatar
update    
Яков committed
442
                                    type="button"
443
444
445
446
                                    key={align}
                                    onClick={() => handleAlign(align)}
                                    style={{
                                        margin: '0 2px',
447
                                        padding: '10px 8px',
448
449
450
451
452
453
454
455
456
457
458
459
460
                                        background: node.attrs.align === align ? '#e6f7ff' : 'transparent',
                                        border: '1px solid #d9d9d9',
                                        borderRadius: 2,
                                        cursor: 'pointer'
                                    }}
                                >
                                    {align}
                                </button>
                            ))}
                        </div>
                    )}

                    <button
Яков's avatar
update    
Яков committed
461
                        type="button"
462
463
464
465
466
467
468
                        onClick={(e) => {
                            e.stopPropagation();
                            setShowAlignMenu(!showAlignMenu);
                        }}
                        style={{
                            position: 'absolute',
                            top: -30,
Яков's avatar
update    
Яков committed
469
                            left: 'calc(50% - 6px)',
470
471
472
473
                            transform: 'translateX(-50%)',
                            backgroundColor: 'white',
                            border: `1px solid ${BORDER_COLOR}`,
                            borderRadius: 4,
474
                            padding: '8px 8px',
475
476
477
478
479
480
481
                            cursor: 'pointer',
                            fontSize: 12,
                            zIndex: 10
                        }}
                    >
                        Align
                    </button>
Яков's avatar
update    
Яков committed
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
                    <Button
                        size="default"
                        shape={'circle'}
                        type={node.attrs.alt?.length > 0 ? 'primary' : 'default'}
                        onClick={(e) => {
                            e.stopPropagation();
                            setTempAlt(node.attrs.alt || '');
                            setAltModalVisible(true);
                        }}
                        style={{
                            position: 'absolute',
                            top: 4,
                            right: 4,
                            zIndex: 15,
                        }}
                    >
                        <FontSizeOutlined />
                    </Button>

501
                </Fragment>
yakoff94's avatar
yakoff94 committed
502
            )}
Яков's avatar
update    
Яков committed
503
            <Modal
Яков's avatar
update    
Яков committed
504
                title="Текстовая сторона"
Яков's avatar
update    
Яков committed
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
                open={altModalVisible}
                onOk={() => {
                    updateAttributes({ alt: tempAlt });
                    setAltModalVisible(false);
                }}
                onCancel={() => setAltModalVisible(false)}
                okText="Применить"
                cancelText="Отмена"
            >
                <TextArea
                    value={tempAlt}
                    onChange={(e) => setTempAlt(e.target.value)}
                    rows={4}
                    placeholder="Введите alt-текст изображения..."
                />
            </Modal>
yakoff94's avatar
yakoff94 committed
521
522
523
524
525
526
527
528
        </NodeViewWrapper>
    );
};

const ResizableImageExtension = TipTapImage.extend({
    addAttributes() {
        return {
            ...this.parent?.(),
Яков's avatar
fix    
Яков committed
529
            src: { default: null },
Яков's avatar
update    
Яков committed
530
531
532
533
534
535
536
537
538
539
540
541
            alt: {
                default: null,
                parseHTML: element => {
                    const raw = element.getAttribute('alt')
                    return raw?.replace(/&#10;/g, '\n') || null
                },
                renderHTML: attributes => {
                    return attributes.alt
                        ? { alt: attributes.alt.replace(/\n/g, '&#10;') }
                        : {}
                }
            },
Яков's avatar
fix    
Яков committed
542
            title: { default: null },
543
544
            width: {
                default: null,
Яков's avatar
fix    
Яков committed
545
                parseHTML: element => parseInt(element.getAttribute('width'), 10) || null,
546
547
548
549
                renderHTML: attributes => attributes.width ? { width: attributes.width } : {}
            },
            height: {
                default: null,
Яков's avatar
fix    
Яков committed
550
                parseHTML: element => parseInt(element.getAttribute('height'), 10) || null,
551
552
553
554
555
556
                renderHTML: attributes => attributes.height ? { height: attributes.height } : {}
            },
            align: {
                default: 'left',
                parseHTML: element => element.getAttribute('data-align') || 'left',
                renderHTML: attributes => ({ 'data-align': attributes.align })
Яков's avatar
fix    
Яков committed
557
558
559
560
561
            },
            'data-node-id': {
                default: null,
                parseHTML: element => element.getAttribute('data-node-id'),
                renderHTML: attributes => ({ 'data-node-id': attributes['data-node-id'] })
562
            }
yakoff94's avatar
yakoff94 committed
563
564
        };
    },
Яков's avatar
update    
Яков committed
565
566
567
568
569
570
571
572
573
574
575
576
577
578
    renderHTML({ node, HTMLAttributes }) {
        const {
            src,
            alt = '',
            title = '',
            width,
            height,
            ...rest
        } = HTMLAttributes;

        const align = node.attrs.align || 'left';

        const style = [];

Яков's avatar
fix    
Яков committed
579

Яков's avatar
update    
Яков committed
580
581
582
        if (align === 'center') {
            style.push('display: block', 'margin-left: auto', 'margin-right: auto');
        } else if (align === 'left') {
Яков's avatar
fix    
Яков committed
583
            style.push('float: left', 'margin-right: 1rem');
Яков's avatar
update    
Яков committed
584
        } else if (align === 'right') {
Яков's avatar
fix    
Яков committed
585
            style.push('float: right', 'margin-left: 1rem');
Яков's avatar
update    
Яков committed
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
        } else if (align === 'text') {
            style.push('display: inline-block', 'vertical-align: middle', 'margin: 0 0.2rem');
        }

        if (width) style.push(`width: ${width}px`);
        if (height) style.push(`height: ${height}px`);

        return [
            'img',
            {
                src,
                alt,
                title,
                width,
                height,
                'data-align': align,
                style: style.join('; '),
                ...rest,
            }
        ];
    },
607

yakoff94's avatar
yakoff94 committed
608
609
    addNodeView() {
        return ReactNodeViewRenderer(ResizableImageTemplate);
Яков's avatar
fix    
Яков committed
610
611
612
613
614
615
616
617
    },

    addKeyboardShortcuts() {
        return {
            'Mod-ArrowLeft': () => this.editor.commands.updateAttributes(this.type.name, { align: 'left' }),
            'Mod-ArrowRight': () => this.editor.commands.updateAttributes(this.type.name, { align: 'right' }),
            'Mod-ArrowDown': () => this.editor.commands.updateAttributes(this.type.name, { align: 'center' }),
        };
618
619
620
621
622
    }
}).configure({
    inline: true,
    group: 'inline',
    draggable: true,
Яков's avatar
fix    
Яков committed
623
    selectable: true
624
});
yakoff94's avatar
yakoff94 committed
625
626

export default ResizableImageExtension;