Image.jsx 27.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, Typography } from 'antd';
Яков's avatar
update    
Яков committed
5
import {FontSizeOutlined} from "@ant-design/icons";
Яков's avatar
update    
Яков committed
6
const { TextArea } = Input;
Яков's avatar
update    
Яков committed
7
const {Text} = Typography;
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
    const [altModalVisible, setAltModalVisible] = useState(false);
    const [tempAlt, setTempAlt] = useState(node.attrs.alt || '');
Яков's avatar
update    
Яков committed
21
    const [tempFrontAlt, setTempFrontAlt] = useState(node.attrs.frontAlt || '');
Яков's avatar
update    
Яков committed
22

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

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

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

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

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

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

Яков's avatar
fix    
Яков committed
48
49
    // Получаем текущую ширину редактора и доступное пространство
    const getEditorDimensions = () => {
Яков's avatar
Яков committed
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
        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
67
68
        }

Яков's avatar
Яков committed
69
70
71
72
73
74
75
76
77
        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
78
79
    };

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

Яков's avatar
fix    
Яков committed
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
112
    // Безопасное обновление атрибутов с учетом выравнивания и границ
    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
113
    };
114

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

Яков's avatar
fix    
Яков committed
124
    // Обработка кликов вне изображения
Яков's avatar
fix    
Яков committed
125
126
127
    useEffect(() => {
        const handleClickOutside = (event) => {
            if (wrapperRef.current && !wrapperRef.current.contains(event.target) && selected) {
Яков's avatar
update    
Яков committed
128
129
130
131
132
133
134
135
136
                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
137
138
            }
        };
Яков's avatar
fix    
Яков committed
139
140
141
        document.addEventListener('mousedown', handleClickOutside);
        return () => document.removeEventListener('mousedown', handleClickOutside);
    }, [selected, editor, getPos]);
yakoff94's avatar
yakoff94 committed
142

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

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

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

Яков's avatar
fix    
Яков committed
159
160
                if (naturalWidth <= 0 || naturalHeight <= 0) {
                    console.warn('Image has invalid natural dimensions, retrying...');
Яков's avatar
fix    
Яков committed
161
                    setTimeout(initImageSize, 100);
Яков's avatar
fix    
Яков committed
162
163
164
165
166
167
168
169
170
171
172
173
                    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
174
                safeUpdateAttributes({
Яков's avatar
fix    
Яков committed
175
176
177
                    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
178
179
                });
                isInitialized.current = true;
Яков's avatar
fix    
Яков committed
180
181
182
183
184
            } catch (error) {
                console.warn('Error initializing image size:', error);
            }
        };

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

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

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

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

Яков's avatar
update    
Яков committed
212
        setIsResizing(true);
Яков's avatar
update    
Яков committed
213
214
215
216
217
218
219
220
221
        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
222

Яков's avatar
fix    
Яков committed
223
224
225
226
227
        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
228
        const { width: initialEditorWidth, availableSpace: initialAvailableSpace } = getEditorDimensions();
229
230

        const onMouseMove = (e) => {
Яков's avatar
Яков committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
            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);
                    }
253
                } else {
Яков's avatar
Яков committed
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
                    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);
                    }
270
271
                }

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

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

277
278
279
        const onMouseUp = () => {
            window.removeEventListener('mousemove', onMouseMove);
            window.removeEventListener('mouseup', onMouseUp);
Яков's avatar
update    
Яков committed
280
            setIsResizing(false);
Яков's avatar
update    
Яков committed
281
282
283
284
285
286
287
288
289
            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
290
            editor.commands.focus();
yakoff94's avatar
yakoff94 committed
291
292
        };

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

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

Яков's avatar
fix    
Яков committed
307
    // Стили для обертки изображения
Яков's avatar
update    
Яков committed
308
309
    const getWrapperStyle = () => {
        const baseStyle = {
310
            display: 'inline-block',
Яков's avatar
update    
Яков committed
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
350
            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
351

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

Яков's avatar
update    
Яков committed
365
    console.log(node.attrs.frontAlt);
yakoff94's avatar
yakoff94 committed
366
367
    return (
        <NodeViewWrapper
368
            as="div"
369
370
371
372
            style={getWrapperStyle()}
            ref={wrapperRef}
            onClick={(e) => {
                e.stopPropagation();
Яков's avatar
update    
Яков committed
373
374
375
376
377
378
379
380
381
                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
382
            }}
383
384
            contentEditable={false}
            data-image-wrapper
yakoff94's avatar
yakoff94 committed
385
386
        >
            <img
387
388
                {...node.attrs}
                ref={imgRef}
Яков's avatar
update    
Яков committed
389
                draggable={true}
390
                style={getImageStyle()}
391
                onLoad={() => {
Яков's avatar
fix    
Яков committed
392
                    if (imgRef.current && !isInitialized.current && !node.attrs.width && !node.attrs.height) {
Яков's avatar
fix    
Яков committed
393
                        const { width: editorWidth } = getEditorDimensions();
Яков's avatar
update    
Яков committed
394
395
396
                        const naturalWidth = imgRef.current.naturalWidth;
                        const naturalHeight = imgRef.current.naturalHeight;

Яков's avatar
fix    
Яков committed
397
398
399
                        safeUpdateAttributes({
                            width: naturalWidth,
                            height: naturalHeight,
Яков's avatar
fix    
Яков committed
400
                            'data-node-id': node.attrs['data-node-id'] || Math.random().toString(36).substr(2, 9)
401
402
403
                        });
                        isInitialized.current = true;
                    }
yakoff94's avatar
yakoff94 committed
404
405
                }}
            />
Яков's avatar
update    
Яков committed
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
            {
                node.attrs.frontAlt?.length > 0 &&
                <div
                    style={{
                        backgroundColor: '#FDE674',
                        borderRadius: '35px',
                        padding: '5px 25px',
                        textAlign: 'center',
                        color: '#000000D9',
                        position: 'absolute',
                        left: '50%',
                        transform: 'translateX(-50%)',
                        fontSize: '12px',
                        lineHeight: '16px',
                        letterSpacing: '2%',
                        fontWeight: '500',
                        bottom: '10px',
                        whiteSpace: 'pre-line'
                    }}
                >{node.attrs.frontAlt}</div>
            }
Яков's avatar
update    
Яков committed
427
428
429
            <Button
                size="default"
                shape={'circle'}
Яков's avatar
update    
Яков committed
430
                type={node.attrs.alt?.length > 0 || node.attrs.frontAlt?.length ? 'primary' : 'default'}
Яков's avatar
update    
Яков committed
431
432
433
                onClick={(e) => {
                    e.stopPropagation();
                    setTempAlt(node.attrs.alt || '');
Яков's avatar
update    
Яков committed
434
                    setTempFrontAlt(node.attrs.frontAlt || '');
Яков's avatar
update    
Яков committed
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
                    setAltModalVisible(true);
                }}
                style={{
                    position: 'absolute',
                    top: 4,
                    right: '30px',
                    zIndex: 15,
                }}
            >
                <FontSizeOutlined />
            </Button>
            {selected && (
                <Button
                    type="text"
                    danger
                    size="small"
                    onClick={(e) => {
                        e.stopPropagation()

                        const pos = getPos?.()
                        if (typeof pos === 'number') {
                            editor.view.dispatch(
                                editor.view.state.tr.delete(pos, pos + node.nodeSize)
                            )
                        }
                    }}
                    style={{
                        position: 'absolute',
                        top: 4,
                        right: 4,
                        zIndex: 30,
                        backgroundColor: 'white',
                        border: '1px solid #d9d9d9',
                        borderRadius: '50%',
                        width: 20,
                        height: 20,
                        fontSize: 12,
                        lineHeight: 1,
                        padding: '0px 0px 2px 0px',
                        cursor: 'pointer'
                    }}
                >
                    ×
                </Button>
            )}
Яков's avatar
update    
Яков committed
480
            {(selected || isResizing) && (
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
                <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
500
                    ))}
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516

                    {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
517
                                    type="button"
518
519
520
521
                                    key={align}
                                    onClick={() => handleAlign(align)}
                                    style={{
                                        margin: '0 2px',
522
                                        padding: '10px 8px',
523
524
525
526
527
528
529
530
531
532
533
534
535
                                        background: node.attrs.align === align ? '#e6f7ff' : 'transparent',
                                        border: '1px solid #d9d9d9',
                                        borderRadius: 2,
                                        cursor: 'pointer'
                                    }}
                                >
                                    {align}
                                </button>
                            ))}
                        </div>
                    )}

                    <button
Яков's avatar
update    
Яков committed
536
                        type="button"
537
538
539
540
541
542
543
                        onClick={(e) => {
                            e.stopPropagation();
                            setShowAlignMenu(!showAlignMenu);
                        }}
                        style={{
                            position: 'absolute',
                            top: -30,
Яков's avatar
update    
Яков committed
544
                            left: 'calc(50% - 6px)',
545
546
547
548
                            transform: 'translateX(-50%)',
                            backgroundColor: 'white',
                            border: `1px solid ${BORDER_COLOR}`,
                            borderRadius: 4,
549
                            padding: '8px 8px',
550
551
552
553
554
555
556
557
                            cursor: 'pointer',
                            fontSize: 12,
                            zIndex: 10
                        }}
                    >
                        Align
                    </button>
                </Fragment>
yakoff94's avatar
yakoff94 committed
558
            )}
Яков's avatar
update    
Яков committed
559
            <Modal
Яков's avatar
update    
Яков committed
560
                title="Текст на картинке"
Яков's avatar
update    
Яков committed
561
562
                open={altModalVisible}
                onOk={() => {
Яков's avatar
update    
Яков committed
563
                    updateAttributes({ alt: tempAlt, frontAlt: tempFrontAlt });
Яков's avatar
update    
Яков committed
564
565
566
567
568
569
                    setAltModalVisible(false);
                }}
                onCancel={() => setAltModalVisible(false)}
                okText="Применить"
                cancelText="Отмена"
            >
Яков's avatar
update    
Яков committed
570
571
572
573
574
575
576
577
                <div style={{marginBottom: '5px'}}><Text>Лицевая сторона</Text></div>
                <TextArea
                    value={tempFrontAlt}
                    onChange={(e) => setTempFrontAlt(e.target.value)}
                    rows={4}
                    placeholder="Введите текст"
                />
                <div style={{marginTop: '15px', marginBottom: '5px'}}><Text>Обратная сторона</Text></div>
Яков's avatar
update    
Яков committed
578
579
580
581
                <TextArea
                    value={tempAlt}
                    onChange={(e) => setTempAlt(e.target.value)}
                    rows={4}
Яков's avatar
update    
Яков committed
582
                    placeholder="Введите текст"
Яков's avatar
update    
Яков committed
583
584
                />
            </Modal>
yakoff94's avatar
yakoff94 committed
585
586
587
588
589
590
591
592
        </NodeViewWrapper>
    );
};

const ResizableImageExtension = TipTapImage.extend({
    addAttributes() {
        return {
            ...this.parent?.(),
Яков's avatar
fix    
Яков committed
593
            src: { default: null },
Яков's avatar
update    
Яков committed
594
595
596
597
598
599
600
601
602
603
604
605
            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
update    
Яков committed
606
607
608
609
610
611
612
613
614
615
616
617
            frontAlt: {
                default: null,
                parseHTML: element => {
                    const raw = element.getAttribute('frontAlt')
                    return raw?.replace(/&#10;/g, '\n') || null
                },
                renderHTML: attributes => {
                    return attributes.frontAlt
                        ? { frontAlt: attributes.frontAlt.replace(/\n/g, '&#10;') }
                        : {}
                }
            },
Яков's avatar
fix    
Яков committed
618
            title: { default: null },
619
620
            width: {
                default: null,
Яков's avatar
fix    
Яков committed
621
                parseHTML: element => parseInt(element.getAttribute('width'), 10) || null,
622
623
624
625
                renderHTML: attributes => attributes.width ? { width: attributes.width } : {}
            },
            height: {
                default: null,
Яков's avatar
fix    
Яков committed
626
                parseHTML: element => parseInt(element.getAttribute('height'), 10) || null,
627
628
629
630
631
632
                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
633
634
635
636
637
            },
            'data-node-id': {
                default: null,
                parseHTML: element => element.getAttribute('data-node-id'),
                renderHTML: attributes => ({ 'data-node-id': attributes['data-node-id'] })
638
            }
yakoff94's avatar
yakoff94 committed
639
640
        };
    },
Яков's avatar
update    
Яков committed
641
642
643
644
645
646
647
648
649
650
651
652
653
654
    renderHTML({ node, HTMLAttributes }) {
        const {
            src,
            alt = '',
            title = '',
            width,
            height,
            ...rest
        } = HTMLAttributes;

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

        const style = [];

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

Яков's avatar
update    
Яков committed
656
657
658
        if (align === 'center') {
            style.push('display: block', 'margin-left: auto', 'margin-right: auto');
        } else if (align === 'left') {
Яков's avatar
fix    
Яков committed
659
            style.push('float: left', 'margin-right: 1rem');
Яков's avatar
update    
Яков committed
660
        } else if (align === 'right') {
Яков's avatar
fix    
Яков committed
661
            style.push('float: right', 'margin-left: 1rem');
Яков's avatar
update    
Яков committed
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
        } 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,
            }
        ];
    },
683

yakoff94's avatar
yakoff94 committed
684
685
    addNodeView() {
        return ReactNodeViewRenderer(ResizableImageTemplate);
Яков's avatar
fix    
Яков committed
686
687
688
689
690
691
692
693
    },

    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' }),
        };
694
695
696
697
698
    }
}).configure({
    inline: true,
    group: 'inline',
    draggable: true,
Яков's avatar
fix    
Яков committed
699
    selectable: true
700
});
yakoff94's avatar
yakoff94 committed
701
702

export default ResizableImageExtension;