QEditor.jsx 46.2 KB
Newer Older
Рамис's avatar
Рамис committed
1
import React, { Fragment, useEffect, useState, useRef } from 'react'
Рамис's avatar
Рамис committed
2
import './index.scss'
Рамис's avatar
Рамис committed
3
4
5
// import EditorModal from "./components/EditorModal"
// import Uploader from "./components/Uploader"

Рамис's avatar
Рамис committed
6
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
Рамис's avatar
Рамис committed
7
8
9
10
11
12
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'
import Table from '@tiptap/extension-table'
import TableCell from '@tiptap/extension-table-cell'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
Рамис's avatar
bug fix    
Рамис committed
13
import Focus from '@tiptap/extension-focus'
Рамис's avatar
Рамис committed
14
// import Link from '@tiptap/extension-link'
Рамис's avatar
Рамис committed
15
16
import Image from '@tiptap/extension-image'
import TextAlign from '@tiptap/extension-text-align';
Рамис's avatar
Рамис committed
17
18
19
import { Color } from '@tiptap/extension-color';
import Highlight from '@tiptap/extension-highlight';
import TextStyle from '@tiptap/extension-text-style';
Рамис's avatar
Рамис committed
20
21

import ToolBar from "./components/ToolBar"
Рамис's avatar
fix bug    
Рамис committed
22
23
import EditorModal from "./components/EditorModal"
import Uploader from "./components/Uploader"
Рамис's avatar
Рамис committed
24
25
import Video from './extensions/Video'
import Iframe from './extensions/Iframe'
Рамис's avatar
Рамис committed
26
import CustomLink from './extensions/CustomLink'
Sergey's avatar
Sergey committed
27
import DragAndDrop from "./extensions/DragAndDrop";
Sergey's avatar
Sergey committed
28
29
import { useReactMediaRecorder } from "react-media-recorder";
import axios from "axios";
30
import ReactStopwatch from 'react-stopwatch';
Sergey's avatar
Sergey committed
31
import Audio from "./extensions/Audio";
Рамис's avatar
Рамис committed
32

Яков's avatar
Яков committed
33
34
import { isMobile } from 'react-device-detect';

Рамис's avatar
Рамис committed
35
const initialBubbleItems = ['bold', 'italic', 'underline', 'strike', '|', 'colorText', 'highlight'];
Рамис's avatar
Рамис committed
36

Яков's avatar
Яков committed
37
38
39
40
41
42
43
const QEditor = ({
    value,
    onChange = () => {},
    style,
    uploadOptions = {url: "", errorMessage: ""},
    toolsOptions = {type: 'all'}
}) => {
Sergey's avatar
Sergey committed
44
45
    global.uploadUrl = uploadOptions.url;

Рамис's avatar
Рамис committed
46
47
48
49
50
51
52
    const [innerModalType, setInnerModalType] = useState(null);
    const [embedContent, setEmbedContent] = useState('');
    const [uploaderUid, setUploaderUid] = useState('uid' + new Date());
    const [uploadedPaths, setUploadedPaths] = useState([]);
    const [modalIsOpen, setModalIsOpen] = useState(false);
    const [modalTitle, setModalTitle] = useState('');
    const [bubbleItems, setBubbleItems] = useState(initialBubbleItems);
Рамис's avatar
Рамис committed
53
    const [colorsSelected, setColorsSelected] = useState(null);
Рамис's avatar
Рамис committed
54
55
    const [focusFromTo, setFocusFromTo] = useState(null);
    const [oldFocusFromTo, setOldFocusFromTo] = useState(null);
Sergey's avatar
Sergey committed
56
    const [isUploading, setIsUploading] = useState(false);
Яков's avatar
Яков committed
57
    const [recordType, setRecordType] = useState({video: true})
Sergey's avatar
Sergey committed
58

Рамис's avatar
Рамис committed
59
60
61
62
    const getRgb = (hex) => {
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)})` : null;
    }
Яков's avatar
Яков committed
63
64
65
66
67
68
69
70
71
72
73
    const {
        status,
        startRecording,
        stopRecording,
        mediaBlobUrl,
        previewStream,
        muteAudio,
        unMuteAudio,
        isAudioMuted,
        clearBlobUrl
    } = useReactMediaRecorder(recordType);
Sergey's avatar
Sergey committed
74
75
76
77
78
79
80
81

    const videoRef = useRef(null);

    useEffect(() => {
        if (videoRef.current && previewStream) {
            videoRef.current.srcObject = previewStream;
        }
    }, [previewStream]);
Рамис's avatar
Рамис committed
82

Рамис's avatar
Рамис committed
83
    useEffect(() => {
Яков's avatar
Яков committed
84
        if (focusFromTo !== oldFocusFromTo) {
Рамис's avatar
Рамис committed
85
86
87
88
89
            setColorsSelected(null)
            setOldFocusFromTo(focusFromTo);
        }
    }, [focusFromTo])

Рамис's avatar
Рамис committed
90
91
92
93
    const modalOpener = (type, title) => {
        setModalTitle(title);
        setInnerModalType(type);
        setModalIsOpen(true);
Рамис's avatar
Рамис committed
94
    }
Рамис's avatar
Рамис committed
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
    const colors = {
        color: [
            'none',
            '#8a8a8a',
            '#afafaf',
            '#44d724',
            '#0bd9b2',
            '#4fb7ff',
            '#226aff',
            '#b153e5',
            '#f54f8e',
            '#f34c37',
            '#ee7027',
            '#d27303',
            '#ffd102'
        ],
        highlight: [
            'none',
            '#9B9B9B',
            '#CCCCCC',
            '#9ee191',
            '#43e7bf',
            '#4fb7ff',
            '#6d9ef5',
            '#cd92e8',
            '#f597bc',
            '#fa9084',
            '#ef9558',
            '#dea75b',
            '#ffe672'
        ]
    };
Рамис's avatar
Рамис committed
127

Рамис's avatar
Рамис committed
128
    const toolsLib = {
Рамис's avatar
Рамис committed
129
130
131
132
133
134
135
136
137
138
139
140
141
        link: {
            title: 'Вставить ссылку',
            onClick: () => {
                const previousUrl = editor.getAttributes('link').href
                const url = window.prompt('Введите URL', previousUrl);

                // cancelled
                if (url === null) {
                    return
                }

                // empty
                if (url === '') {
Яков's avatar
Яков committed
142
                    editor.chain().focus().extendMarkRange('link').unsetLink().run();
Рамис's avatar
Рамис committed
143
144
145
146
147

                    return
                }

                // update link
Яков's avatar
Яков committed
148
                editor.chain().focus().extendMarkRange('link').setLink({href: url, target: '_blank'}).run();
Рамис's avatar
Рамис committed
149
150
            }
        },
Рамис's avatar
Рамис committed
151
152
153
154
        file: {
            title: 'Прикрепить файл',
            onClick: () => modalOpener('file', 'Прикрепить файл')
        },
Рамис's avatar
Рамис committed
155
156
157
158
159
160
161
162
        video: {
            title: 'Загрузить видео',
            onClick: () => modalOpener('video', 'Загрузить видео')
        },
        iframe: {
            title: 'Видео по ссылке',
            onClick: () => modalOpener('iframe', 'Видео по ссылке')
        },
Яков's avatar
Яков committed
163
164
165
166
        iframe_custom: {
            title: 'Вставить iframe',
            onClick: () => modalOpener('iframe_custom', 'Вставить iframe')
        },
Яков's avatar
Яков committed
167
168
169
170
        iframe_pptx: {
            title: 'Вставить презентацию pptx',
            onClick: () => modalOpener('iframe_pptx', 'Вставить презентацию pptx')
        },
Рамис's avatar
Рамис committed
171
172
173
174
175
176
        image: {
            title: 'Загрузить изображение',
            onClick: () => modalOpener('image', 'Загрузить изображение')
        },
        h2: {
            title: 'Заголовок 2',
Яков's avatar
Яков committed
177
            onClick: () => editor.chain().focus().toggleHeading({level: 2}).run()
Рамис's avatar
Рамис committed
178
179
180
        },
        h3: {
            title: 'Заголовок 3',
Яков's avatar
Яков committed
181
            onClick: () => editor.chain().focus().toggleHeading({level: 3}).run()
Рамис's avatar
Рамис committed
182
183
184
        },
        h4: {
            title: 'Заголовок 4',
Яков's avatar
Яков committed
185
            onClick: () => editor.chain().focus().toggleHeading({level: 4}).run()
Рамис's avatar
Рамис committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
        },
        paragraph: {
            title: 'Обычный',
            onClick: () => editor.chain().focus().setParagraph().run()
        },
        bold: {
            title: 'Жирный',
            onClick: () => editor.chain().focus().toggleBold().run()
        },
        italic: {
            title: 'Курсив',
            onClick: () => editor.chain().focus().toggleItalic().run()
        },
        underline: {
            title: 'Подчеркнутый',
            onClick: () => editor.chain().focus().toggleUnderline().run()
        },
        strike: {
            title: 'Зачеркнутый',
            onClick: () => editor.chain().focus().toggleStrike().run()
        },
        codeBlock: {
            title: 'Код',
            onClick: () => editor.chain().focus().toggleCodeBlock().run()
        },
        clearMarks: {
            title: 'Очистить форматирование',
            onClick: () => editor.chain().focus().unsetAllMarks().run()
        },
        bulletList: {
            title: 'Маркированный список',
            onClick: () => editor.chain().focus().toggleBulletList().run()
        },
        orderedList: {
            title: 'Нумированный список',
            onClick: () => editor.chain().focus().toggleOrderedList().run()
        },
        blockquote: {
            title: 'Цитата',
            onClick: () => editor.chain().focus().toggleBlockquote().run()
        },
        hardBreak: {
            title: 'Перенос строки',
            onClick: () => editor.chain().focus().setHardBreak().run()
        },
        hr: {
            title: 'Горизонтальная линия',
            onClick: () => editor.chain().focus().setHorizontalRule().run()
        },
        undo: {
            title: 'Действие назад',
            onClick: () => editor.chain().focus().undo().run()
        },
        redo: {
            title: 'Действие вперед',
            onClick: () => editor.chain().focus().redo().run()
        },
        alignLeft: {
            title: 'По левому краю',
Рамис's avatar
Рамис committed
245
246
247
248
            onClick: () => {
                editor.commands.setTextAlign('left');
                editor.chain().focus();
            }
Рамис's avatar
Рамис committed
249
250
251
        },
        alignCenter: {
            title: 'По центру',
Рамис's avatar
Рамис committed
252
253
254
255
            onClick: () => {
                editor.commands.setTextAlign('center')
                editor.chain().focus();
            }
Рамис's avatar
Рамис committed
256
257
258
        },
        alignRight: {
            title: 'По правому краю',
Рамис's avatar
Рамис committed
259
260
261
262
            onClick: () => {
                editor.commands.setTextAlign('right');
                editor.chain().focus();
            }
Рамис's avatar
Рамис committed
263
264
265
        },
        insertTable: {
            title: 'Вставить таблицу',
Яков's avatar
Яков committed
266
            onClick: () => editor.chain().focus().insertTable({rows: 2, cols: 2}).run()
Рамис's avatar
Рамис committed
267
268
269
270
271
272
273
274
275
276
277
        },
        deleteTable: {
            title: 'Удалить таблицу',
            onClick: () => editor.chain().focus().deleteTable().run()
        },
        addRowBefore: {
            title: 'Вставить строку перед',
            onClick: () => editor.chain().focus().addRowBefore().run()
        },
        addRowAfter: {
            title: 'Вставить строку после',
Рамис's avatar
Рамис committed
278
279
280
281
282
            onClick: () => editor.chain().focus().addRowAfter().run()
        },
        deleteRow: {
            title: 'Удалить строку',
            onClick: () => editor.chain().focus().deleteRow().run()
Рамис's avatar
Рамис committed
283
284
285
286
287
288
289
290
        },
        addColumnBefore: {
            title: 'Вставить столбец перед',
            onClick: () => editor.chain().focus().addColumnBefore().run()
        },
        addColumnAfter: {
            title: 'Вставить столбец после',
            onClick: () => editor.chain().focus().addColumnAfter().run()
Рамис's avatar
Рамис committed
291
        },
Рамис's avatar
Рамис committed
292
293
294
295
296
297
298
299
300
301
302
        deleteColumn: {
            title: 'Удалить столбец',
            onClick: () => editor.chain().focus().deleteColumn().run()
        },
        mergeOrSplit: {
            title: 'Объединить/разъединить ячейки',
            onClick: () => editor.chain().focus().mergeOrSplit().run()
        },
        toggleHeaderCell: {
            title: 'Добавить/удалить заголовок',
            onClick: () => editor.chain().focus().toggleHeaderCell().run()
Рамис's avatar
Рамис committed
303
304
305
        },
        colorText: {
            title: 'Цвет текста',
Рамис's avatar
Рамис committed
306
307
308
309
            onClick: () => {
                setColorsSelected('color')
                editor.chain().focus();
            }
Рамис's avatar
Рамис committed
310
311
312
313
314
        },
        highlight: {
            title: 'Цвет фона',
            onClick: () => setColorsSelected('highlight')
        },
315
316
317
318
        voicemessage: {
            title: 'Записать голосовое сообщение',
            onClick: () => {
                setRecordType({audio: true})
Sergey's avatar
Sergey committed
319
                clearBlobUrl()
Sergey's avatar
Sergey committed
320
                modalOpener('voicemessage', 'Записать голосовое сообщение')
321
322
323
324
325
326
            }
        },
        webcamera: {
            title: 'Записать с камеры',
            onClick: () => {
                setRecordType({video: true})
Sergey's avatar
Sergey committed
327
                clearBlobUrl()
Sergey's avatar
Sergey committed
328
                modalOpener('webcamera', 'Записать с камеры')
329
330
331
332
333
            }
        },
        screencust: {
            title: 'Записать экран',
            onClick: () => {
Яков's avatar
Яков committed
334
335
336
337
338
                if (isMobile) {
                    setRecordType({video: true})
                } else {
                    setRecordType({screen: true})
                }
Sergey's avatar
Sergey committed
339
                clearBlobUrl()
Sergey's avatar
Sergey committed
340
                modalOpener('screencust', 'Записать экран')
341
342
            }
        },
Рамис's avatar
Рамис committed
343
344
345
346
347
348
349
350
351
        // katex: {
        //     title: 'Вставить формулу',
        //     onClick: () => {
        //
        //         console.log(katex.renderToString(String.raw`c = \pm\sqrt{a^2 + b^2}`));
        //
        //         // editor.chain().focus().insertContent()
        //     }
        // }
Рамис's avatar
Рамис committed
352
353
    }

Рамис's avatar
Рамис committed
354
355
356
357
    const editor = useEditor({
        extensions: [
            StarterKit,
            Underline,
Рамис's avatar
Рамис committed
358
359
360
            Image.configure({
                inline: true
            }),
Рамис's avatar
Рамис committed
361
362
363
364
365
            // Link.configure({
            //     autolink: true,
            //     linkOnPaste: true,
            //     validate: (href)=> console.log(href),
            // }),
Рамис's avatar
Рамис committed
366
367
368
369
            Video,
            Iframe,
            Table.configure({
                resizable: true,
Рамис's avatar
bug fix    
Рамис committed
370
                allowTableNodeSelection: true
Рамис's avatar
Рамис committed
371
372
373
374
375
376
377
378
379
            }),
            TableRow,
            TableHeader,
            TableCell,
            BubbleMenu,
            TextAlign.configure({
                defaultAlignment: 'left',
                types: ['heading', 'paragraph'],
                alignments: ['left', 'center', 'right', 'justify'],
Рамис's avatar
Рамис committed
380
381
382
383
384
385
386
            }),
            TextStyle,
            Color.configure({
                types: ['textStyle'],
            }),
            Highlight.configure({
                multicolor: true
Рамис's avatar
Рамис committed
387
            }),
Рамис's avatar
Рамис committed
388
389
390
            CustomLink.configure({
                linkOnPaste: false,
                openOnClick: false
Рамис's avatar
bug fix    
Рамис committed
391
392
393
394
            }),
            Focus.configure({
                className: 'atma-editor-focused',
                mode: "all"
Sergey's avatar
Sergey committed
395
            }),
Sergey's avatar
Sergey committed
396
397
398
            DragAndDrop.configure({
                linkUpload: uploadOptions.url
            }),
Sergey's avatar
Sergey committed
399
            Audio
Рамис's avatar
Рамис committed
400
401
        ],
        content: value,
Рамис's avatar
bug fix    
Рамис committed
402
        onUpdate: ({editor}) => onChange(editor.getHTML()),
Яков's avatar
Яков committed
403
        onFocus: ({editor}) => {
Рамис's avatar
bug fix    
Рамис committed
404
405
            let wrap = editor.options.element.closest('.atma-editor-wrap');

Яков's avatar
Яков committed
406
            wrap.querySelectorAll('.atma-editor-toolbar-s').forEach(function (s) {
Рамис's avatar
Рамис committed
407
408
409
                s.classList.remove('show');
            });

Рамис's avatar
bug fix    
Рамис committed
410
        }
Рамис's avatar
Рамис committed
411
412
413
    })

    const buildActionsModal = (buttons = []) => {
Рамис's avatar
Рамис committed
414
415
416
417
418
419
420
421
        if (buttons.length === 0) {
            return null;
        }

        return (
            <div className={'atma-editor-modal-action'}>
                {
                    buttons.map((btn, i) => (
Яков's avatar
Яков committed
422
423
                        <button disabled={btn.disabled} type={'button'} key={'mAction' + i}
                                className={'atma-editor-btn' + btn.className}
Рамис's avatar
Рамис committed
424
425
426
427
428
429
430
                                onClick={btn.onClick}>{btn.title}</button>
                    ))
                }
            </div>
        )
    }

Яков's avatar
Яков committed
431
    const getUploader = ({accept = '*', ...o}) => {
Яков's avatar
Яков committed
432
433
        let url = uploadOptions.url,
            multiple = true;
Яков's avatar
Яков committed
434
435
        if (o.afterParams && o.afterParams.length > 0) {
            if (uploadOptions.url.indexOf('?') !== -1) {
Рамис's avatar
Рамис committed
436
                url = uploadOptions.url + '&' + o.afterParams.join('&');
Яков's avatar
Яков committed
437
            } else {
Рамис's avatar
Рамис committed
438
439
440
441
                url = uploadOptions.url + '?' + o.afterParams.join('&');
            }
        }

Яков's avatar
Яков committed
442
443
444
445
        if (typeof o.multiple !== 'undefined') {
            multiple = o.multiple;
        }

Рамис's avatar
Рамис committed
446
447
        return <Uploader
            key={uploaderUid}
Яков's avatar
Яков committed
448
449
450
            accept={accept}
            action={url}
            errorMessage={uploadOptions.errorMessage}
Рамис's avatar
Рамис committed
451
452
453
454
455
456
            onSuccess={(file) => {
                let _uploadedPaths = [...uploadedPaths];

                _uploadedPaths.push(file);
                setUploadedPaths(_uploadedPaths)
            }}
Яков's avatar
Яков committed
457
            onDelete={(deleteFile) => {
Рамис's avatar
Рамис committed
458
459
460
                let deleteIdx = null;
                let _uploadedPaths = [...uploadedPaths];

Яков's avatar
Яков committed
461
462
                _uploadedPaths.map((f, i) => {
                    if (f.uid === deleteFile.uid) {
Рамис's avatar
Рамис committed
463
464
465
466
467
468
                        deleteIdx = i;
                    }
                });
                _uploadedPaths.splice(deleteIdx, 1);
                setUploadedPaths(_uploadedPaths)
            }}
Яков's avatar
Яков committed
469
            multiple={multiple}
Яков's avatar
Яков committed
470
            modalType={innerModalType}
Рамис's avatar
Рамис committed
471
472
473
        />
    }

Sergey's avatar
Sergey committed
474
    const saveScreenCust = async (fileBlob) => {
475
476
477
478
479
480
481
482
483
        if (fileBlob) {
            setIsUploading(true)
            let blobData = await fetch(fileBlob).then((res) => res.blob());

            const data = new FormData();
            let file = new File([blobData], "name." + (recordType?.audio ? "mp3" : "webm"));
            data.append("file", file);

            const headers = {'Content-Type': 'multipart/form-data'};
Sergey's avatar
Sergey committed
484
485
486
487
488
489
490
491
492

            return new Promise(function (resolve) {
                axios.post(uploadOptions.url, data, {headers: headers}).then(response => {
                    if (response.data.state === "success") {
                        resolve(response.data)
                    }
                    setIsUploading(false)
                });
            })
493
        }
Sergey's avatar
Sergey committed
494
495
    };

Рамис's avatar
Рамис committed
496
497
498
    const getInnerModal = () => {
        switch (innerModalType) {
            case 'iframe':
Рамис's avatar
Рамис committed
499
500
                return (
                    <Fragment>
Яков's avatar
Яков committed
501
502
503
                        <input type="text" value={embedContent} placeholder={'https://'}
                               onInput={(e) => setEmbedContent(e.target.value)
                               }/>
Рамис's avatar
Рамис committed
504
505
506
                        <ul className={'atma-editor-soc-video'}>
                            <li className={'youtube'}/>
                            <li className={'vimeo'}/>
Рамис's avatar
Рамис committed
507
                            {/* <li className={'vk'}/> */}
Рамис's avatar
Рамис committed
508
                            <li className={'ok'}/>
Рамис's avatar
fix bug    
Рамис committed
509
                            <li className={'rutube'}/>
Рамис's avatar
Рамис committed
510
511
512
                        </ul>
                    </Fragment>
                )
Яков's avatar
Яков committed
513
514
515
            case 'iframe_custom':
                return (
                    <Fragment>
Яков's avatar
Яков committed
516
                        <textarea style={{width: '100%', height: '100%'}} rows={18} value={embedContent} placeholder={'<iframe></iframe>'}
Яков's avatar
Яков committed
517
518
519
520
                               onInput={(e) => setEmbedContent(e.target.value)}
                        />
                    </Fragment>
                )
Яков's avatar
Яков committed
521
522
523
524
            case 'iframe_pptx':
                return (
                    <Fragment>{getUploader({accept: 'application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.slideshow, application/vnd.openxmlformats-officedocument.presentationml.presentation', afterParams: ['no_convert=1']})}</Fragment>
                )
Рамис's avatar
Рамис committed
525
            case 'video':
Рамис's avatar
Рамис committed
526
                return (
Яков's avatar
Яков committed
527
                    <Fragment>{getUploader({accept: 'video/*'})}</Fragment>
Рамис's avatar
Рамис committed
528
529
530
                )
            case 'image':
                return (
Яков's avatar
Яков committed
531
                    <Fragment>{getUploader({accept: 'image/*'})}</Fragment>
Рамис's avatar
Рамис committed
532
                )
Рамис's avatar
Рамис committed
533
            case 'file':
Рамис's avatar
Рамис committed
534
                return (
Яков's avatar
Яков committed
535
                    <Fragment>{getUploader({accept: '*', afterParams: ['no_convert=1']})}</Fragment>
Рамис's avatar
Рамис committed
536
                )
537
538
539
540
            case 'voicemessage':
                return (
                    <>
                        <Fragment>
Яков's avatar
Яков committed
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
                            {
                                isMobile &&
                                <>
                                    <div className={"webwrap"}>
                                        <div>Аудиозапись с мобильного устройства недоступна, <br/> запишите стандартными
                                            функциями устройства и воспользуйтесь кнопкой «Прикрепить файл»
                                        </div>
                                    </div>
                                </>
                            }
                            {
                                ! isMobile &&
                                <div className={"audio-player"}>
                                    <div className={"audio-player-start audio-player-margin"}>
                                        {
                                            status === 'recording' && ! mediaBlobUrl ?
                                                <div onClick={stopRecording}
                                                     className={"audio-player-center-recording"}/> :
                                                <div onClick={startRecording} className={"audio-player-center-start"}/>
                                        }
                                    </div>
                                    <div className={"audio-player-voice audio-player-margin"}/>
563
                                    {
Яков's avatar
Яков committed
564
565
566
567
568
569
570
571
572
573
574
575
                                        status === 'recording' && ! mediaBlobUrl ?
                                            <ReactStopwatch
                                                seconds={0}
                                                minutes={0}
                                                hours={0}
                                                render={({formatted}) => {
                                                    return (
                                                        <span
                                                            className={"audio-player-timer audio-player-margin"}>{formatted}</span>
                                                    )
                                                }}
                                            /> : <span className={"audio-player-timer audio-player-margin"}/>
576
577
                                    }
                                </div>
Яков's avatar
Яков committed
578
                            }
579
580
581
582
583
584
585
                        </Fragment>
                    </>
                )
            case 'screencust':
                return (
                    <>
                        <Fragment>
Яков's avatar
Яков committed
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
                            {
                                isMobile &&
                                <>
                                    <div className={"webwrap"}>
                                        <div>Запись экрана с мобильного устройства недоступна, <br/> запишите
                                            стандартными функциями устройства и воспользуйтесь кнопкой «Загрузить видео»
                                        </div>
                                    </div>
                                </>
                            }
                            {
                                ! isMobile &&
                                <>
                                    <div className={"webwrap"}>
                                        <div className={"webwrap-content"}>
                                            {
                                                mediaBlobUrl ?
                                                    <video className={"webwrap-video"} id={"id-video"}
                                                           src={mediaBlobUrl} controls/> : status === "recording" &&
                                                    <video className={"webwrap-video"} ref={videoRef}
                                                           src={previewStream} autoPlay controls={false}/>
                                            }
                                            {
                                                status === 'recording' && ! mediaBlobUrl ?
                                                    <ReactStopwatch
                                                        seconds={0}
                                                        minutes={0}
                                                        hours={0}
                                                        render={({formatted}) => {
                                                            return (
                                                                <span className={"webwrap-timer"}>
617
618
                                                    {formatted}
                                                </span>
Яков's avatar
Яков committed
619
620
621
622
623
624
625
626
627
628
629
630
                                                            )
                                                        }}
                                                    /> : <span className={"webwrap-timer"}>00:00:00</span>
                                            }
                                            {
                                                ! mediaBlobUrl &&
                                                <div className={"webwrap-start-border"}>
                                                    <button
                                                        onClick={status === 'recording' ? stopRecording : startRecording}
                                                        className={status === 'recording' ? "webwrap-record-center" : "webwrap-start-center"}/>
                                                </div>
                                            }
631
632
                                        </div>
                                    </div>
Яков's avatar
Яков committed
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
                                    <div className={"web-bottom-elements"}>
                                        {mediaBlobUrl &&
                                            <div onClick={clearBlobUrl} className={"web-button-wrap"}>
                                                <div className={"web-button-rerecord"}/>
                                                <span className={"web-button-rerecord-text"}>Перезаписать</span>
                                            </div>
                                        }
                                        {
                                            ! mediaBlobUrl &&
                                            <div className={"web-button-spacer"}/>
                                        }
                                        {
                                            ! mediaBlobUrl &&
                                            <div onClick={isAudioMuted ? unMuteAudio : muteAudio}
                                                 className={isAudioMuted ? "web-button-unmute" : "web-button-mute"}/>
                                        }
                                        <div className={"web-button-spacer"}/>
                                    </div>
                                </>
                            }
653
654
655
                        </Fragment>
                    </>
                )
Sergey's avatar
Sergey committed
656
657
658
            case 'webcamera':
                return (
                    <>
Яков's avatar
Яков committed
659
                        <Fragment>
660
                            {
Яков's avatar
Яков committed
661
662
663
664
665
666
667
668
                                isMobile &&
                                <>
                                    <div className={"webwrap"}>
                                        <div>Видеозапись с мобильного устройства недоступна, <br/> запишите стандартными
                                            функциями устройства и воспользуйтесь кнопкой «Загрузить видео»
                                        </div>
                                    </div>
                                </>
669
670
                            }
                            {
Яков's avatar
Яков committed
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
                                ! isMobile &&
                                <>
                                    <div className={"webwrap"}>
                                        <div className={"webwrap-content"}>
                                            {
                                                mediaBlobUrl ?
                                                    <video className={"webwrap-video"} id={"id-video"}
                                                           src={mediaBlobUrl}
                                                           controls/> : status === "recording" &&
                                                    <video className={"webwrap-video"} ref={videoRef}
                                                           src={previewStream}
                                                           autoPlay controls={false}/>
                                            }
                                            {
                                                status === 'recording' && ! mediaBlobUrl ?
                                                    <ReactStopwatch
                                                        seconds={0}
                                                        minutes={0}
                                                        hours={0}
                                                        render={({formatted}) => {
                                                            return (
                                                                <span className={"webwrap-timer"}>
                                                    {formatted}
                                                </span>
                                                            )
                                                        }}
                                                    /> : <span className={"webwrap-timer"}>00:00:00</span>
                                            }
                                            {
                                                ! mediaBlobUrl &&
                                                <div className={"webwrap-start-border"}>
                                                    <button
                                                        onClick={status === 'recording' ? stopRecording : startRecording}
                                                        className={status === 'recording' ? "webwrap-record-center" : "webwrap-start-center"}/>
                                                </div>
                                            }
                                        </div>
                                    </div>
                                    <div className={"web-bottom-elements"}>
                                        {mediaBlobUrl &&
                                            <div onClick={clearBlobUrl} className={"web-button-wrap"}>
                                                <div className={"web-button-rerecord"}/>
                                                <span className={"web-button-rerecord-text"}>Перезаписать</span>
                                            </div>
                                        }
                                        {
                                            ! mediaBlobUrl &&
                                            <div className={"web-button-spacer"}/>
                                        }
                                        {
                                            ! mediaBlobUrl &&
                                            <div onClick={isAudioMuted ? unMuteAudio : muteAudio}
                                                 className={isAudioMuted ? "web-button-unmute" : "web-button-mute"}/>
                                        }
                                        <div className={"web-button-spacer"}/>
                                    </div>
                                </>
728
                            }
Яков's avatar
Яков committed
729
730
731
                        </Fragment>
                    </>
                )
Рамис's avatar
Рамис committed
732
733
734
735
736
            default:
                return <div>Пусто</div>
        }
    }

Рамис's avatar
Рамис committed
737
    const isDisabledAction = () => {
738
739
        let isDisabled = false;

Яков's avatar
Яков committed
740
        switch (innerModalType) {
Рамис's avatar
Рамис committed
741
            case 'video':
742
            case 'image':
Яков's avatar
Яков committed
743
                if (uploadOptions.url === null || uploadedPaths.length === 0) {
744
745
746
                    isDisabled = true;
                }
                break;
Sergey's avatar
Sergey committed
747
            case 'screencust':
Яков's avatar
Яков committed
748
                if (status === 'recording' || isUploading || ! mediaBlobUrl) {
Sergey's avatar
Sergey committed
749
750
751
752
                    isDisabled = true;
                }
                break;
            case 'voicemessage':
Яков's avatar
Яков committed
753
                if (status === 'recording' || isUploading || ! mediaBlobUrl) {
Sergey's avatar
Sergey committed
754
755
756
757
                    isDisabled = true;
                }
                break;
            case 'webcamera':
Яков's avatar
Яков committed
758
                if (status === 'recording' || isUploading || ! mediaBlobUrl) {
Sergey's avatar
Sergey committed
759
760
761
                    isDisabled = true;
                }
                break;
Рамис's avatar
Рамис committed
762
            case 'iframe':
Яков's avatar
Яков committed
763
                try {
764
765
                    let url = new URL(embedContent);

Яков's avatar
Яков committed
766
                    switch (url.hostname) {
767
768
769
770
771
772
773
774
775
776
777
778
779
                        case 'rutube.ru':
                        case 'www.rutube.ru':
                        case 'vimeo.com':
                        case 'ok.ru':
                        case 'www.ok.ru':
                        case 'youtu.be':
                        case 'youtube.com':
                        case 'www.youtube.com':
                            break;
                        default:
                            isDisabled = true;
                    }

Яков's avatar
Яков committed
780
                } catch (error) {
781
782
783
                    isDisabled = true;
                }
                break;
Яков's avatar
Яков committed
784
785
786
787
            case 'iframe_custom':
                let regex = new RegExp('(?:<iframe[^>]*)(?:(?:\\/>)|(?:>.*?<\\/iframe>))');
                isDisabled = !regex.test(embedContent);
                break;
788
789
790
791
792
        }

        return isDisabled;
    }

Яков's avatar
Яков committed
793
    if ( ! editor) {
Рамис's avatar
Рамис committed
794
795
        return null
    }
Рамис's avatar
Рамис committed
796

Рамис's avatar
Рамис committed
797
    return (
Рамис's avatar
Рамис committed
798
799
800
801
        <div
            className="atma-editor-wrap"
            style={style}
        >
Рамис's avatar
Рамис committed
802
803
804
            <div className="atma-editor">
                <ToolBar
                    editor={editor}
Рамис's avatar
Рамис committed
805
                    {...{toolsOptions}}
Рамис's avatar
Рамис committed
806
                    {...{toolsLib}}
Рамис's avatar
Рамис committed
807

Рамис's avatar
Рамис committed
808
                />
Рамис's avatar
bug fix    
Рамис committed
809
                <BubbleMenu typpyOptions={{followCursor: true,}} editor={editor} shouldShow={({...o}) => {
Рамис's avatar
Рамис committed
810
811
                    let items = [];

Яков's avatar
Яков committed
812
                    if (o.from !== o.to && editor.isActive('paragraph') && editor.isActive('image') === false && document.querySelectorAll('.selectedCell').length === 0) {
Рамис's avatar
Рамис committed
813
814
815
                        items = initialBubbleItems;
                    }

Яков's avatar
Яков committed
816
                    if (editor.isActive('image') === true) {
Рамис's avatar
Рамис committed
817
                        items = ['alignLeft', 'alignCenter', 'alignRight'];
Рамис's avatar
Рамис committed
818
                    }
Рамис's avatar
Рамис committed
819
820
                    setFocusFromTo([o.from, o.to].join(':'));

Яков's avatar
Яков committed
821
                    if (items.length > 0) {
Рамис's avatar
Рамис committed
822
823
824
                        setBubbleItems(items);
                        return true;
                    }
Яков's avatar
Яков committed
825
                }} tippyOptions={{duration: 100}}>
Рамис's avatar
Рамис committed
826
                    <div className={"atma-editor-bubble"} onClick={e => e.stopPropagation()}>
Рамис's avatar
Рамис committed
827
                        {
Рамис's avatar
Рамис committed
828
                            colorsSelected !== null ?
Рамис's avatar
Рамис committed
829
                                colors[colorsSelected].map((itemColor, i) => {
Яков's avatar
Яков committed
830
831
832
                                    return (<div key={'colors' + colorsSelected + i}
                                                 className={'qcolors' + (itemColor === 'none' ? ' unset' : '')}
                                                 style={{background: itemColor}} onClick={() => {
Рамис's avatar
Рамис committed
833

Яков's avatar
Яков committed
834
                                        if (itemColor === 'none') {
Рамис's avatar
Рамис committed
835
836
837
                                            colorsSelected === 'color' ?
                                                editor.chain().focus().unsetHighlight().unsetColor().run() :
                                                editor.chain().focus().unsetColor().unsetHighlight().run();
Яков's avatar
Яков committed
838
                                        } else {
Рамис's avatar
Рамис committed
839
840
                                            colorsSelected === 'color' ?
                                                editor.chain().focus().unsetHighlight().setColor(itemColor).run() :
Яков's avatar
Яков committed
841
                                                editor.chain().focus().unsetColor().toggleHighlight({color: itemColor}).run();
Рамис's avatar
Рамис committed
842
843
                                        }

Рамис's avatar
Рамис committed
844
                                        setColorsSelected(null);
Яков's avatar
Яков committed
845
                                    }}/>)
Рамис's avatar
Рамис committed
846
                                }) : bubbleItems.map((type, i) => {
Яков's avatar
Яков committed
847
848
849
                                    if (type === '|') {
                                        return (<div key={'bubbleSeparator' + i} className={'qseparator'}/>)
                                    } else {
Рамис's avatar
Рамис committed
850
851
                                        return (
                                            <div
Яков's avatar
Яков committed
852
                                                key={'bubbleItems' + i}
Рамис's avatar
Рамис committed
853
                                                className={'qicon q' + type + (editor.isActive(type) ? ' active' : '')}
Яков's avatar
Яков committed
854
855
                                                title={toolsLib[type] ? toolsLib[type].title : ''}
                                                onClick={toolsLib[type].onClick}
Рамис's avatar
Рамис committed
856
857
858
859
                                            />
                                        )
                                    }
                                })
Рамис's avatar
Рамис committed
860
                        }
Рамис's avatar
Рамис committed
861
                    </div>
Рамис's avatar
Рамис committed
862
863
864
865
866
867
868
869
870
871
                </BubbleMenu>
                <EditorContent
                    editor={editor}
                    className={'atma-editor-content'}
                />
            </div>
            <EditorModal
                isOpen={modalIsOpen}
                title={modalTitle}
            >
Рамис's avatar
Рамис committed
872
873
874
875
                {
                    getInnerModal()
                }
                {
Рамис's avatar
Рамис committed
876
                    buildActionsModal([
Рамис's avatar
Рамис committed
877
878
879
880
                        {
                            title: 'Отмена',
                            className: ' atma-editor-cancel',
                            onClick: () => {
Sergey's avatar
Sergey committed
881
882
                                stopRecording();
                                unMuteAudio();
Sergey's avatar
Sergey committed
883
                                clearBlobUrl();
Рамис's avatar
Рамис committed
884
885
                                setUploaderUid(`uid${new Date()}`);
                                setUploadedPaths([]);
886
                                setModalIsOpen(false);
Рамис's avatar
Рамис committed
887
                            }
Рамис's avatar
Рамис committed
888
889
                        },
                        {
Sergey's avatar
Sergey committed
890
891
                            title: (mediaBlobUrl && uploadedPaths.length === 0) ? (isUploading ? 'Сохранение...' : 'Вставить') : 'Вставить',
                            className: ' atma-editor-complete',
Яков's avatar
update    
Яков committed
892
                            onClick: async () => {
Яков's avatar
update    
Яков committed
893

Sergey's avatar
Sergey committed
894
                                if ((status === 'recording' || isUploading)) {
Яков's avatar
update    
Яков committed
895
                                    return false;
896
897
898
899
900
901
                                } else {
                                    if (document.querySelectorAll('.atma-editor-uploader-progress').length > 0) {
                                        if ( ! confirm('Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?')) {
                                            return false;
                                        }
                                    }
Sergey's avatar
Sergey committed
902

903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
                                    try {
                                        switch (innerModalType) {
                                            case 'image':
                                                uploadedPaths.map((file, i) => {
                                                    editor.chain().focus().setImage({src: file.path}).run();
                                                });
                                                break
                                            case 'video':
                                                uploadedPaths.map((file, i) => {
                                                    editor.chain().focus().setVideo({
                                                        src: file.path,
                                                        poster: file.path + '.jpg'
                                                    }).run();
                                                });
                                                break
                                            case 'voicemessage':
Sergey's avatar
Sergey committed
919
920
921
922
923
924
925
926
                                                if (mediaBlobUrl && uploadedPaths.length === 0) {
                                                    if ( ! isUploading) {
                                                        await saveScreenCust(mediaBlobUrl).then(data => {
                                                            if (data?.file_path) {
                                                                editor.chain().focus().addVoiceMessage({src: data.file_path}).run();
                                                            }
                                                        });
                                                    }
927
928
929
                                                }
                                                break
                                            case 'screencust':
Sergey's avatar
Sergey committed
930
931
932
933
934
935
936
937
                                                if (mediaBlobUrl && uploadedPaths.length === 0) {
                                                    if ( ! isUploading) {
                                                        await saveScreenCust(mediaBlobUrl).then(data => {
                                                            if (data?.file_path) {
                                                                editor.chain().focus().setVideo({src: data.file_path}).run();
                                                            }
                                                        });
                                                    }
938
939
940
                                                }
                                                break
                                            case 'webcamera':
Sergey's avatar
Sergey committed
941
942
943
944
945
946
947
948
                                                if (mediaBlobUrl && uploadedPaths.length === 0) {
                                                    if ( ! isUploading) {
                                                        await saveScreenCust(mediaBlobUrl).then(data => {
                                                            if (data?.file_path) {
                                                                editor.chain().focus().setVideo({src: data.file_path}).run();
                                                            }
                                                        });
                                                    }
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
                                                }
                                                break
                                            case 'iframe':
                                                let _url = embedContent;
                                                let reg = /(http|https):\/\/([\w.]+\/?)\S*/;

                                                const url = new URL(reg.test(_url) ? _url : 'https:' + _url);
                                                let urlId = url.pathname.replace(/\/$/ig, '').split('/').pop();

                                                switch (url.hostname) {
                                                    case 'rutube.ru':
                                                    case 'www.rutube.ru':
                                                        _url = `https://rutube.ru/pl/?pl_id&pl_type&pl_video=${urlId}`;
                                                        break
                                                    case 'vimeo.com':
                                                        _url = `https://player.vimeo.com/video/${urlId}`;

                                                        break
                                                    case 'ok.ru':
                                                    case 'www.ok.ru':
                                                        _url = `//ok.ru/videoembed/${urlId}`;
                                                        break
                                                    case 'youtu.be':
                                                    case 'youtube.com':
                                                    case 'www.youtube.com':
                                                        if (url.hostname.indexOf('youtu.be') === -1 && url.search !== '') {
                                                            if (url.searchParams.get('v')) {
                                                                urlId = url.searchParams.get('v');
                                                            }
Рамис's avatar
Рамис committed
978
                                                        }
979
980
981
982
                                                        _url = `https://www.youtube.com/embed/${urlId}`;
                                                        break
                                                }
                                                editor.chain().focus().setIframe({src: _url}).run();
Рамис's avatar
Рамис committed
983

Яков's avatar
Яков committed
984
985
986
                                                break
                                            case 'iframe_custom':
                                                editor.chain().focus().insertContent(embedContent).run();
987
                                                break
Яков's avatar
Яков committed
988
989
990
991
992
                                            case 'iframe_pptx':
                                                uploadedPaths.map((file, i)=>{
                                                    editor.chain().focus().insertContent(`<iframe src="https://view.officeapps.live.com/op/embed.aspx?src=${file.path}" width="100%" height="600px" frameBorder="0"></iframe>`).run();
                                                })
                                                break
993
994
                                            case 'file':
                                                uploadedPaths.map((file, i) => {
Яков's avatar
Яков committed
995
996
                                                    let exp = file.path.split('.');
                                                    exp = exp[exp.length - 1]
Яков's avatar
Яков committed
997
                                                    editor.chain().focus().insertContent(`<a href="${file.path}" target="_blank" download="${file.name}.${exp}" data-size="${file.size}">${file.name}</a>`).run();
998
999
1000
                                                });
                                                break
                                        }
Рамис's avatar
Рамис committed
1001

1002
                                        setModalIsOpen(false);
Sergey's avatar
Sergey committed
1003
                                        clearBlobUrl();
1004
1005
1006
1007
1008
                                        setUploaderUid(`uid${new Date()}`);
                                        setEmbedContent('');
                                        setUploadedPaths([]);
                                        setModalTitle('');
                                    } catch (err) {
Яков's avatar
update    
Яков committed
1009
1010
1011
1012
1013
1014
1015
                                        console.log(err);
                                        setModalIsOpen(false);
                                        clearBlobUrl();
                                        setUploaderUid(`uid${new Date()}`);
                                        setEmbedContent('');
                                        setUploadedPaths([]);
                                        setModalTitle('');
1016
                                    }
Рамис's avatar
Рамис committed
1017
1018
1019
1020
1021
                                }
                            },
                            disabled: isDisabledAction()
                        }
                    ])
Рамис's avatar
Рамис committed
1022
1023
1024
1025
                }
            </EditorModal>
        </div>
    )
Рамис's avatar
Рамис committed
1026
}
Рамис's avatar
Рамис committed
1027
1028

export default QEditor;