Commit 5101cee0 by DenSakh

feat: refactoring

parent 5499f2c8
/* eslint-disable no-undef */
/* eslint-disable no-case-declarations */
import React, { Fragment, useEffect, useState, useRef } from 'react' import React, { Fragment, useEffect, useState, useRef } from 'react'
import './index.scss' import './index.scss'
// import EditorModal from "./components/EditorModal" // import EditorModal from "./components/EditorModal"
...@@ -13,1037 +15,1180 @@ import TableHeader from '@tiptap/extension-table-header' ...@@ -13,1037 +15,1180 @@ import TableHeader from '@tiptap/extension-table-header'
import Focus from '@tiptap/extension-focus' import Focus from '@tiptap/extension-focus'
// import Link from '@tiptap/extension-link' // import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image' import Image from '@tiptap/extension-image'
import TextAlign from '@tiptap/extension-text-align'; import TextAlign from '@tiptap/extension-text-align'
import { Color } from '@tiptap/extension-color'; import { Color } from '@tiptap/extension-color'
import Highlight from '@tiptap/extension-highlight'; import Highlight from '@tiptap/extension-highlight'
import TextStyle from '@tiptap/extension-text-style'; import TextStyle from '@tiptap/extension-text-style'
import ToolBar from "./components/ToolBar" import ToolBar from './components/ToolBar'
import EditorModal from "./components/EditorModal" import EditorModal from './components/EditorModal'
import Uploader from "./components/Uploader" import Uploader from './components/Uploader'
import Video from './extensions/Video' import Video from './extensions/Video'
import Iframe from './extensions/Iframe' import Iframe from './extensions/Iframe'
import CustomLink from './extensions/CustomLink' import CustomLink from './extensions/CustomLink'
import DragAndDrop from "./extensions/DragAndDrop"; import DragAndDrop from './extensions/DragAndDrop'
import { useReactMediaRecorder } from "react-media-recorder"; import { useReactMediaRecorder } from 'react-media-recorder'
import axios from "axios"; import axios from 'axios'
import ReactStopwatch from 'react-stopwatch'; import ReactStopwatch from 'react-stopwatch'
import Audio from "./extensions/Audio"; import Audio from './extensions/Audio'
import { isMobile } from 'react-device-detect'; import IframeModal from './modals/IframeModal'
import IframeModal from "./modals/IframeModal"; import IframeCustomModal from './modals/IframeCustomModal'
import IframeCustomModal from "./modals/IframeCustomModal"; import { isMobile } from 'react-device-detect'
import RemoveIframeModal from "./modals/RemoveIframeModal";
const initialBubbleItems = ['bold', 'italic', 'underline', 'strike', '|', 'colorText', 'highlight']; const initialBubbleItems = [
'bold',
'italic',
'underline',
'strike',
'|',
'colorText',
'highlight'
]
const QEditor = ({ const QEditor = ({
value, value,
onChange = () => {}, onChange = () => {},
style, style,
uploadOptions = {url: "", errorMessage: ""}, uploadOptions = { url: '', errorMessage: '' },
toolsOptions = {type: 'all'} toolsOptions = { type: 'all' }
}) => { }) => {
global.uploadUrl = uploadOptions.url; global.uploadUrl = uploadOptions.url
const [innerModalType, setInnerModalType] = useState(null); const [innerModalType, setInnerModalType] = useState(null)
const [embedContent, setEmbedContent] = useState(''); const [embedContent, setEmbedContent] = useState('')
const [uploaderUid, setUploaderUid] = useState('uid' + new Date()); const [uploaderUid, setUploaderUid] = useState('uid' + new Date())
const [uploadedPaths, setUploadedPaths] = useState([]); const [uploadedPaths, setUploadedPaths] = useState([])
const [modalIsOpen, setModalIsOpen] = useState(false); const [modalIsOpen, setModalIsOpen] = useState(false)
const [modalTitle, setModalTitle] = useState(''); const [modalTitle, setModalTitle] = useState('')
const [bubbleItems, setBubbleItems] = useState(initialBubbleItems); const [bubbleItems, setBubbleItems] = useState(initialBubbleItems)
const [colorsSelected, setColorsSelected] = useState(null); const [colorsSelected, setColorsSelected] = useState(null)
const [focusFromTo, setFocusFromTo] = useState(null); const [focusFromTo, setFocusFromTo] = useState(null)
const [oldFocusFromTo, setOldFocusFromTo] = useState(null); const [oldFocusFromTo, setOldFocusFromTo] = useState(null)
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false)
const [recordType, setRecordType] = useState({video: true}) const [recordType, setRecordType] = useState({ video: true })
const [currentRemoveIframe, setCurrentRemoveIframe] = useState(null);
const getRgb = (hex) => { // eslint-disable-next-line no-unused-vars
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); const getRgb = (hex) => {
return result ? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)})` : null; var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
} return result
const { ? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
status, result[3],
startRecording, 16
stopRecording, )})`
mediaBlobUrl, : null
previewStream, }
muteAudio, const {
unMuteAudio, status,
isAudioMuted, startRecording,
clearBlobUrl stopRecording,
} = useReactMediaRecorder(recordType); mediaBlobUrl,
previewStream,
const videoRef = useRef(null); muteAudio,
unMuteAudio,
isAudioMuted,
clearBlobUrl
} = useReactMediaRecorder(recordType)
useEffect(() => { const videoRef = useRef(null)
if (videoRef.current && previewStream) {
videoRef.current.srcObject = previewStream;
}
}, [previewStream]);
useEffect(() => {
if (focusFromTo !== oldFocusFromTo) {
setColorsSelected(null)
setOldFocusFromTo(focusFromTo);
}
}, [focusFromTo])
const modalOpener = (type, title) => { useEffect(() => {
setModalTitle(title); if (videoRef.current && previewStream) {
setInnerModalType(type); videoRef.current.srcObject = previewStream
setModalIsOpen(true);
} }
const colors = { }, [previewStream])
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'
]
};
const toolsLib = {
link: {
title: 'Вставить ссылку',
onClick: () => {
const previousUrl = editor.getAttributes('link').href
const url = window.prompt('Введите URL', previousUrl);
// cancelled
if (url === null) {
return
}
// empty useEffect(() => {
if (url === '') { if (focusFromTo !== oldFocusFromTo) {
editor.chain().focus().extendMarkRange('link').unsetLink().run(); setColorsSelected(null)
setOldFocusFromTo(focusFromTo)
return
}
// update link
editor.chain().focus().extendMarkRange('link').setLink({href: url, target: '_blank'}).run();
}
},
file: {
title: 'Прикрепить файл',
onClick: () => modalOpener('file', 'Прикрепить файл')
},
video: {
title: 'Загрузить видео',
onClick: () => modalOpener('video', 'Загрузить видео')
},
iframe: {
title: 'Видео по ссылке',
onClick: () => modalOpener('iframe', 'Видео по ссылке')
},
iframe_custom: {
title: 'Вставить iframe',
onClick: () => modalOpener('iframe_custom', 'Вставить iframe')
},
iframe_pptx: {
title: 'Вставить презентацию pptx',
onClick: () => modalOpener('iframe_pptx', 'Вставить презентацию pptx')
},
image: {
title: 'Загрузить изображение',
onClick: () => modalOpener('image', 'Загрузить изображение')
},
h2: {
title: 'Заголовок 2',
onClick: () => editor.chain().focus().toggleHeading({level: 2}).run()
},
h3: {
title: 'Заголовок 3',
onClick: () => editor.chain().focus().toggleHeading({level: 3}).run()
},
h4: {
title: 'Заголовок 4',
onClick: () => editor.chain().focus().toggleHeading({level: 4}).run()
},
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: 'По левому краю',
onClick: () => {
editor.commands.setTextAlign('left');
editor.chain().focus();
}
},
alignCenter: {
title: 'По центру',
onClick: () => {
editor.commands.setTextAlign('center')
editor.chain().focus();
}
},
alignRight: {
title: 'По правому краю',
onClick: () => {
editor.commands.setTextAlign('right');
editor.chain().focus();
}
},
insertTable: {
title: 'Вставить таблицу',
onClick: () => editor.chain().focus().insertTable({rows: 2, cols: 2}).run()
},
deleteTable: {
title: 'Удалить таблицу',
onClick: () => editor.chain().focus().deleteTable().run()
},
addRowBefore: {
title: 'Вставить строку перед',
onClick: () => editor.chain().focus().addRowBefore().run()
},
addRowAfter: {
title: 'Вставить строку после',
onClick: () => editor.chain().focus().addRowAfter().run()
},
deleteRow: {
title: 'Удалить строку',
onClick: () => editor.chain().focus().deleteRow().run()
},
addColumnBefore: {
title: 'Вставить столбец перед',
onClick: () => editor.chain().focus().addColumnBefore().run()
},
addColumnAfter: {
title: 'Вставить столбец после',
onClick: () => editor.chain().focus().addColumnAfter().run()
},
deleteColumn: {
title: 'Удалить столбец',
onClick: () => editor.chain().focus().deleteColumn().run()
},
mergeOrSplit: {
title: 'Объединить/разъединить ячейки',
onClick: () => editor.chain().focus().mergeOrSplit().run()
},
toggleHeaderCell: {
title: 'Добавить/удалить заголовок',
onClick: () => editor.chain().focus().toggleHeaderCell().run()
},
colorText: {
title: 'Цвет текста',
onClick: () => {
setColorsSelected('color')
editor.chain().focus();
}
},
highlight: {
title: 'Цвет фона',
onClick: () => setColorsSelected('highlight')
},
voicemessage: {
title: 'Записать голосовое сообщение',
onClick: () => {
setRecordType({audio: true})
clearBlobUrl()
modalOpener('voicemessage', 'Записать голосовое сообщение')
}
},
webcamera: {
title: 'Записать с камеры',
onClick: () => {
setRecordType({video: true})
clearBlobUrl()
modalOpener('webcamera', 'Записать с камеры')
}
},
screencust: {
title: 'Записать экран',
onClick: () => {
if (isMobile) {
setRecordType({video: true})
} else {
setRecordType({screen: true})
}
clearBlobUrl()
modalOpener('screencust', 'Записать экран')
}
},
// katex: {
// title: 'Вставить формулу',
// onClick: () => {
//
// console.log(katex.renderToString(String.raw`c = \pm\sqrt{a^2 + b^2}`));
//
// // editor.chain().focus().insertContent()
// }
// }
} }
}, [focusFromTo])
const editor = useEditor({ const modalOpener = (type, title) => {
extensions: [ setModalTitle(title)
StarterKit, setInnerModalType(type)
Underline, setModalIsOpen(true)
Image.configure({ }
inline: true const colors = {
}), color: [
// Link.configure({ 'none',
// autolink: true, '#8a8a8a',
// linkOnPaste: true, '#afafaf',
// validate: (href)=> console.log(href), '#44d724',
// }), '#0bd9b2',
Video, '#4fb7ff',
Iframe, '#226aff',
Table.configure({ '#b153e5',
resizable: true, '#f54f8e',
allowTableNodeSelection: true '#f34c37',
}), '#ee7027',
TableRow, '#d27303',
TableHeader, '#ffd102'
TableCell, ],
BubbleMenu, highlight: [
TextAlign.configure({ 'none',
defaultAlignment: 'left', '#9B9B9B',
types: ['heading', 'paragraph'], '#CCCCCC',
alignments: ['left', 'center', 'right', 'justify'], '#9ee191',
}), '#43e7bf',
TextStyle, '#4fb7ff',
Color.configure({ '#6d9ef5',
types: ['textStyle'], '#cd92e8',
}), '#f597bc',
Highlight.configure({ '#fa9084',
multicolor: true '#ef9558',
}), '#dea75b',
CustomLink.configure({ '#ffe672'
linkOnPaste: false, ]
openOnClick: false }
}),
Focus.configure({
className: 'atma-editor-focused',
mode: "all"
}),
DragAndDrop.configure({
linkUpload: uploadOptions.url
}),
Audio
],
content: value,
onUpdate: ({editor}) => onChange(editor.getHTML()),
onFocus: ({editor}) => {
let wrap = editor.options.element.closest('.atma-editor-wrap');
wrap.querySelectorAll('.atma-editor-toolbar-s').forEach(function (s) { const toolsLib = {
s.classList.remove('show'); link: {
}); title: 'Вставить ссылку',
onClick: () => {
const previousUrl = editor.getAttributes('link').href
const url = window.prompt('Введите URL', previousUrl)
// cancelled
if (url === null) {
return
} }
})
const buildActionsModal = (buttons = []) => { // empty
if (buttons.length === 0) { if (url === '') {
return null; editor.chain().focus().extendMarkRange('link').unsetLink().run()
return
} }
return ( // update link
<div className={'atma-editor-modal-action'}> editor
{ .chain()
buttons.map((btn, i) => ( .focus()
<button disabled={btn.disabled} type={'button'} key={'mAction' + i} .extendMarkRange('link')
className={'atma-editor-btn' + btn.className} .setLink({ href: url, target: '_blank' })
onClick={btn.onClick}>{btn.title}</button> .run()
)) }
} },
</div> file: {
) title: 'Прикрепить файл',
onClick: () => modalOpener('file', 'Прикрепить файл')
},
video: {
title: 'Загрузить видео',
onClick: () => modalOpener('video', 'Загрузить видео')
},
iframe: {
title: 'Видео по ссылке',
onClick: () => modalOpener('iframe', 'Видео по ссылке')
},
iframe_custom: {
title: 'Вставить iframe',
onClick: () => modalOpener('iframe_custom', 'Вставить iframe')
},
iframe_pptx: {
title: 'Вставить презентацию pptx',
onClick: () => modalOpener('iframe_pptx', 'Вставить презентацию pptx')
},
image: {
title: 'Загрузить изображение',
onClick: () => modalOpener('image', 'Загрузить изображение')
},
h2: {
title: 'Заголовок 2',
onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run()
},
h3: {
title: 'Заголовок 3',
onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run()
},
h4: {
title: 'Заголовок 4',
onClick: () => editor.chain().focus().toggleHeading({ level: 4 }).run()
},
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: 'По левому краю',
onClick: () => {
editor.commands.setTextAlign('left')
editor.chain().focus()
}
},
alignCenter: {
title: 'По центру',
onClick: () => {
editor.commands.setTextAlign('center')
editor.chain().focus()
}
},
alignRight: {
title: 'По правому краю',
onClick: () => {
editor.commands.setTextAlign('right')
editor.chain().focus()
}
},
insertTable: {
title: 'Вставить таблицу',
onClick: () =>
editor.chain().focus().insertTable({ rows: 2, cols: 2 }).run()
},
deleteTable: {
title: 'Удалить таблицу',
onClick: () => editor.chain().focus().deleteTable().run()
},
addRowBefore: {
title: 'Вставить строку перед',
onClick: () => editor.chain().focus().addRowBefore().run()
},
addRowAfter: {
title: 'Вставить строку после',
onClick: () => editor.chain().focus().addRowAfter().run()
},
deleteRow: {
title: 'Удалить строку',
onClick: () => editor.chain().focus().deleteRow().run()
},
addColumnBefore: {
title: 'Вставить столбец перед',
onClick: () => editor.chain().focus().addColumnBefore().run()
},
addColumnAfter: {
title: 'Вставить столбец после',
onClick: () => editor.chain().focus().addColumnAfter().run()
},
deleteColumn: {
title: 'Удалить столбец',
onClick: () => editor.chain().focus().deleteColumn().run()
},
mergeOrSplit: {
title: 'Объединить/разъединить ячейки',
onClick: () => editor.chain().focus().mergeOrSplit().run()
},
toggleHeaderCell: {
title: 'Добавить/удалить заголовок',
onClick: () => editor.chain().focus().toggleHeaderCell().run()
},
colorText: {
title: 'Цвет текста',
onClick: () => {
setColorsSelected('color')
editor.chain().focus()
}
},
highlight: {
title: 'Цвет фона',
onClick: () => setColorsSelected('highlight')
},
voicemessage: {
title: 'Записать голосовое сообщение',
onClick: () => {
setRecordType({ audio: true })
clearBlobUrl()
modalOpener('voicemessage', 'Записать голосовое сообщение')
}
},
webcamera: {
title: 'Записать с камеры',
onClick: () => {
setRecordType({ video: true })
clearBlobUrl()
modalOpener('webcamera', 'Записать с камеры')
}
},
screencust: {
title: 'Записать экран',
onClick: () => {
if (isMobile) {
setRecordType({ video: true })
} else {
setRecordType({ screen: true })
}
clearBlobUrl()
modalOpener('screencust', 'Записать экран')
}
} }
// katex: {
// title: 'Вставить формулу',
// onClick: () => {
//
// console.log(katex.renderToString(String.raw`c = \pm\sqrt{a^2 + b^2}`));
//
// // editor.chain().focus().insertContent()
// }
// }
}
const getUploader = ({accept = '*', ...o}) => { const editor = useEditor({
let url = uploadOptions.url, extensions: [
multiple = true; StarterKit,
if (o.afterParams && o.afterParams.length > 0) { Underline,
if (uploadOptions.url.indexOf('?') !== -1) { Image.configure({
url = uploadOptions.url + '&' + o.afterParams.join('&'); inline: true
} else { }),
url = uploadOptions.url + '?' + o.afterParams.join('&'); // Link.configure({
} // autolink: true,
} // linkOnPaste: true,
// validate: (href)=> console.log(href),
// }),
Video,
Iframe,
Table.configure({
resizable: true,
allowTableNodeSelection: true
}),
TableRow,
TableHeader,
TableCell,
BubbleMenu,
TextAlign.configure({
defaultAlignment: 'left',
types: ['heading', 'paragraph'],
alignments: ['left', 'center', 'right', 'justify']
}),
TextStyle,
Color.configure({
types: ['textStyle']
}),
Highlight.configure({
multicolor: true
}),
CustomLink.configure({
linkOnPaste: false,
openOnClick: false
}),
Focus.configure({
className: 'atma-editor-focused',
mode: 'all'
}),
DragAndDrop.configure({
linkUpload: uploadOptions.url
}),
Audio
],
content: value,
onUpdate: ({ editor }) => onChange(editor.getHTML()),
onFocus: ({ editor }) => {
const wrap = editor.options.element.closest('.atma-editor-wrap')
if (typeof o.multiple !== 'undefined') { wrap.querySelectorAll('.atma-editor-toolbar-s').forEach(function (s) {
multiple = o.multiple; s.classList.remove('show')
} })
}
})
return <Uploader const buildActionsModal = (buttons = []) => {
key={uploaderUid} if (buttons.length === 0) {
accept={accept} return null
action={url} }
errorMessage={uploadOptions.errorMessage}
onSuccess={(file) => {
let _uploadedPaths = [...uploadedPaths];
_uploadedPaths.push(file); return (
setUploadedPaths(_uploadedPaths) <div className='atma-editor-modal-action'>
}} {buttons.map((btn, i) => (
onDelete={(deleteFile) => { <button
let deleteIdx = null; disabled={btn.disabled}
let _uploadedPaths = [...uploadedPaths]; type='button'
key={'mAction' + i}
className={'atma-editor-btn' + btn.className}
onClick={btn.onClick}
>
{btn.title}
</button>
))}
</div>
)
}
_uploadedPaths.map((f, i) => { const getUploader = ({ accept = '*', ...o }) => {
if (f.uid === deleteFile.uid) { let url = uploadOptions.url
deleteIdx = i; let multiple = true
} if (o.afterParams && o.afterParams.length > 0) {
}); if (uploadOptions.url.indexOf('?') !== -1) {
_uploadedPaths.splice(deleteIdx, 1); url = uploadOptions.url + '&' + o.afterParams.join('&')
setUploadedPaths(_uploadedPaths) } else {
}} url = uploadOptions.url + '?' + o.afterParams.join('&')
multiple={multiple} }
modalType={innerModalType}
/>
} }
const saveScreenCust = async (fileBlob) => { if (typeof o.multiple !== 'undefined') {
if (fileBlob) { multiple = o.multiple
setIsUploading(true) }
let blobData = await fetch(fileBlob).then((res) => res.blob());
const data = new FormData(); return (
let file = new File([blobData], "name." + (recordType?.audio ? "mp3" : "webm")); <Uploader
data.append("file", file); key={uploaderUid}
accept={accept}
action={url}
errorMessage={uploadOptions.errorMessage}
onSuccess={(file) => {
const _uploadedPaths = [...uploadedPaths]
_uploadedPaths.push(file)
setUploadedPaths(_uploadedPaths)
}}
onDelete={(deleteFile) => {
let deleteIdx = null
const _uploadedPaths = [...uploadedPaths]
const headers = {'Content-Type': 'multipart/form-data'}; _uploadedPaths.map((f, i) => {
if (f.uid === deleteFile.uid) {
deleteIdx = i
}
})
_uploadedPaths.splice(deleteIdx, 1)
setUploadedPaths(_uploadedPaths)
}}
multiple={multiple}
modalType={innerModalType}
/>
)
}
return new Promise(function (resolve) { const saveScreenCust = async (fileBlob) => {
axios.post(uploadOptions.url, data, {headers: headers}).then(response => { if (fileBlob) {
if (response.data.state === "success") { setIsUploading(true)
resolve(response.data) const blobData = await fetch(fileBlob).then((res) => res.blob())
}
setIsUploading(false)
});
})
}
};
const getInnerModal = () => { const data = new FormData()
switch (innerModalType) { const file = new File(
case 'remove_iframe': [blobData],
return <RemoveIframeModal /> 'name.' + (recordType?.audio ? 'mp3' : 'webm')
case 'iframe': )
return <IframeModal data.append('file', file)
embedContent={embedContent}
setEmbedContent={setEmbedContent} const headers = { 'Content-Type': 'multipart/form-data' }
/>
case 'iframe_custom': return new Promise(function (resolve) {
return <IframeCustomModal axios
embedContent={embedContent} .post(uploadOptions.url, data, { headers: headers })
setEmbedContent={setEmbedContent} .then((response) => {
/> if (response.data.state === 'success') {
case 'iframe_pptx': resolve(response.data)
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> setIsUploading(false)
) })
case 'video': })
return ( }
<Fragment>{getUploader({accept: 'video/*'})}</Fragment> }
)
case 'image': const getInnerModal = () => {
return ( switch (innerModalType) {
<Fragment>{getUploader({accept: 'image/*'})}</Fragment> case 'iframe':
) return (
case 'file': <IframeModal
return ( embedContent={embedContent}
<Fragment>{getUploader({accept: '*', afterParams: ['no_convert=1']})}</Fragment> setEmbedContent={setEmbedContent}
) />
case 'voicemessage': )
return ( case 'iframe_custom':
<Fragment> return (
{ <IframeCustomModal
isMobile && embedContent={embedContent}
<> setEmbedContent={setEmbedContent}
<div className={"webwrap"}> />
<div>Аудиозапись с мобильного устройства недоступна, <br/> запишите стандартными )
функциями устройства и воспользуйтесь кнопкой «Прикрепить файл» case 'iframe_pptx':
</div> return (
</div> <Fragment>
</> {getUploader({
} accept:
{ 'application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.slideshow, application/vnd.openxmlformats-officedocument.presentationml.presentation',
! isMobile && afterParams: ['no_convert=1']
<div className={"audio-player"}> })}
<div className={"audio-player-start audio-player-margin"}> </Fragment>
{ )
status === 'recording' && ! mediaBlobUrl ? case 'video':
<div onClick={stopRecording} return <Fragment>{getUploader({ accept: 'video/*' })}</Fragment>
className={"audio-player-center-recording"}/> : case 'image':
<div onClick={startRecording} className={"audio-player-center-start"}/> return <Fragment>{getUploader({ accept: 'image/*' })}</Fragment>
} case 'file':
</div> return (
<div className={"audio-player-voice audio-player-margin"}/> <Fragment>
{ {getUploader({ accept: '*', afterParams: ['no_convert=1'] })}
status === 'recording' && ! mediaBlobUrl ? </Fragment>
<ReactStopwatch )
seconds={0} case 'voicemessage':
minutes={0} return (
hours={0} <Fragment>
render={({formatted}) => { {isMobile && (
return ( <div className='webwrap'>
<span <div>
className={"audio-player-timer audio-player-margin"}>{formatted}</span> Аудиозапись с мобильного устройства недоступна, <br />{' '}
) запишите стандартными функциями устройства и воспользуйтесь
}} кнопкой «Прикрепить файл»
/> : <span className={"audio-player-timer audio-player-margin"}/> </div>
} </div>
</div> )}
} {!isMobile && (
</Fragment> <div className='audio-player'>
) <div className='audio-player-start audio-player-margin'>
case 'screencust': {status === 'recording' && !mediaBlobUrl ? (
return ( <div
<> onClick={stopRecording}
<Fragment> className='audio-player-center-recording'
{ />
isMobile && ) : (
<> <div
<div className={"webwrap"}> onClick={startRecording}
<div>Запись экрана с мобильного устройства недоступна, <br/> запишите className='audio-player-center-start'
стандартными функциями устройства и воспользуйтесь кнопкой «Загрузить видео» />
</div> )}
</div> </div>
</> <div className='audio-player-voice audio-player-margin' />
{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' />
)}
</div>
)}
</Fragment>
)
case 'screencust':
return (
<>
<Fragment>
{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'>{formatted}</span>
)
}}
/>
) : (
<span className='webwrap-timer'>00:00:00</span>
)}
{!mediaBlobUrl && (
<div className='webwrap-start-border'>
<button
onClick={
status === 'recording'
? stopRecording
: startRecording
} }
{ className={
! isMobile && status === 'recording'
<> ? 'webwrap-record-center'
<div className={"webwrap"}> : 'webwrap-start-center'
<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>
</>
} }
</Fragment> />
</> </div>
) )}
case 'webcamera': </div>
return ( </div>
<> <div className='web-bottom-elements'>
<Fragment> {mediaBlobUrl && (
{ <div onClick={clearBlobUrl} className='web-button-wrap'>
isMobile && <div className='web-button-rerecord' />
<> <span className='web-button-rerecord-text'>
<div className={"webwrap"}> Перезаписать
<div>Видеозапись с мобильного устройства недоступна, <br/> запишите стандартными </span>
функциями устройства и воспользуйтесь кнопкой «Загрузить видео» </div>
</div> )}
</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>
</>
)}
</Fragment>
</>
)
case 'webcamera':
return (
<>
<Fragment>
{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'>{formatted}</span>
)
}}
/>
) : (
<span className='webwrap-timer'>00:00:00</span>
)}
{!mediaBlobUrl && (
<div className='webwrap-start-border'>
<button
onClick={
status === 'recording'
? stopRecording
: startRecording
} }
{ className={
! isMobile && status === 'recording'
<> ? 'webwrap-record-center'
<div className={"webwrap"}> : 'webwrap-start-center'
<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>
</>
} }
</Fragment> />
</> </div>
) )}
default: </div>
return <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>
</>
)}
</Fragment>
</>
)
default:
return <div>Пусто</div>
} }
}
const isDisabledAction = () => { const isDisabledAction = () => {
let isDisabled = false; let isDisabled = false
switch (innerModalType) { switch (innerModalType) {
case 'video': case 'video':
case 'image': case 'image':
if (uploadOptions.url === null || uploadedPaths.length === 0) { if (uploadOptions.url === null || uploadedPaths.length === 0) {
isDisabled = true; isDisabled = true
} }
break; break
case 'screencust': case 'screencust':
if (status === 'recording' || isUploading || ! mediaBlobUrl) { if (status === 'recording' || isUploading || !mediaBlobUrl) {
isDisabled = true; isDisabled = true
} }
break; break
case 'voicemessage': case 'voicemessage':
if (status === 'recording' || isUploading || ! mediaBlobUrl) { if (status === 'recording' || isUploading || !mediaBlobUrl) {
isDisabled = true; isDisabled = true
} }
break; break
case 'webcamera': case 'webcamera':
if (status === 'recording' || isUploading || ! mediaBlobUrl) { if (status === 'recording' || isUploading || !mediaBlobUrl) {
isDisabled = true; isDisabled = true
}
break;
case 'iframe':
try {
let url = new URL(embedContent);
switch (url.hostname) {
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;
}
} catch (error) {
isDisabled = true;
}
break;
case 'iframe_custom':
let regex = new RegExp('(?:<iframe[^>]*)(?:(?:\\/>)|(?:>.*?<\\/iframe>))');
isDisabled = !regex.test(embedContent);
break;
} }
break
case 'iframe':
try {
const url = new URL(embedContent)
return isDisabled; switch (url.hostname) {
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
}
} catch (error) {
isDisabled = true
}
break
case 'iframe_custom':
const regex = new RegExp(
'(?:<iframe[^>]*)(?:(?:\\/>)|(?:>.*?<\\/iframe>))'
)
isDisabled = !regex.test(embedContent)
break
} }
if ( ! editor) { return isDisabled
return null }
}
const buttons = innerModalType === 'remove_iframe' ? if (!editor) {
[ return null
{ }
title: 'Отмена',
className: ' atma-editor-cancel',
onClick: () => {
stopRecording();
unMuteAudio();
clearBlobUrl();
setUploaderUid(`uid${new Date()}`);
setUploadedPaths([]);
setModalIsOpen(false);
}
},
{
title: 'Удалить',
className: ' atma-editor-complete',
onClick: () => {
currentRemoveIframe?.remove();
stopRecording(); const buttons =
unMuteAudio(); innerModalType === 'remove_iframe'
clearBlobUrl(); ? [
setUploaderUid(`uid${new Date()}`); {
setUploadedPaths([]); title: 'Отмена',
setModalIsOpen(false); className: ' atma-editor-cancel',
} onClick: () => {
}, stopRecording()
] : [ unMuteAudio()
{ clearBlobUrl()
setUploaderUid(`uid${new Date()}`)
setUploadedPaths([])
setModalIsOpen(false)
}
},
{
title: 'Удалить',
className: ' atma-editor-complete',
onClick: () => {
stopRecording()
unMuteAudio()
clearBlobUrl()
setUploaderUid(`uid${new Date()}`)
setUploadedPaths([])
setModalIsOpen(false)
}
}
]
: [
{
title: 'Отмена', title: 'Отмена',
className: ' atma-editor-cancel', className: ' atma-editor-cancel',
onClick: () => { onClick: () => {
stopRecording(); stopRecording()
unMuteAudio(); unMuteAudio()
clearBlobUrl(); clearBlobUrl()
setUploaderUid(`uid${new Date()}`); setUploaderUid(`uid${new Date()}`)
setUploadedPaths([]); setUploadedPaths([])
setModalIsOpen(false); setModalIsOpen(false)
} }
}, },
{ {
title: (mediaBlobUrl && uploadedPaths.length === 0) ? (isUploading ? 'Сохранение...' : 'Вставить') : 'Вставить', title:
mediaBlobUrl && uploadedPaths.length === 0
? isUploading
? 'Сохранение...'
: 'Вставить'
: 'Вставить',
className: ' atma-editor-complete', className: ' atma-editor-complete',
onClick: async () => { onClick: async () => {
if (status === 'recording' || isUploading) {
return false
} else {
if (
document.querySelectorAll('.atma-editor-uploader-progress')
.length > 0
) {
if (
// eslint-disable-next-line no-undef
!confirm(
'Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?'
)
) {
return false
}
}
if ((status === 'recording' || isUploading)) { try {
return false; switch (innerModalType) {
} else { case 'image':
if (document.querySelectorAll('.atma-editor-uploader-progress').length > 0) { uploadedPaths.map((file, i) => {
if ( ! confirm('Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?')) { editor.chain().focus().setImage({ src: file.path })
return false; })
break
case 'video':
uploadedPaths.map((file, i) => {
editor
.chain()
.focus()
.setVideo({
src: file.path,
poster: file.path + '.jpg'
})
.run()
})
break
case 'voicemessage':
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()
}
})
} }
} }
break
try { case 'screencust':
switch (innerModalType) { if (mediaBlobUrl && uploadedPaths.length === 0) {
case 'image': if (!isUploading) {
uploadedPaths.map((file, i) => { await saveScreenCust(mediaBlobUrl).then((data) => {
editor.chain().focus().setImage({src: file.path}).run(); if (data?.file_path) {
}); editor
break .chain()
case 'video': .focus()
uploadedPaths.map((file, i) => { .setVideo({ src: data.file_path })
editor.chain().focus().setVideo({ .run()
src: file.path, }
poster: file.path + '.jpg' })
}).run(); }
}); }
break break
case 'voicemessage': case 'webcamera':
if (mediaBlobUrl && uploadedPaths.length === 0) { if (mediaBlobUrl && uploadedPaths.length === 0) {
if ( ! isUploading) { if (!isUploading) {
await saveScreenCust(mediaBlobUrl).then(data => { await saveScreenCust(mediaBlobUrl).then((data) => {
if (data?.file_path) { if (data?.file_path) {
editor.chain().focus().addVoiceMessage({src: data.file_path}).run(); editor
} .chain()
}); .focus()
} .setVideo({ src: data.file_path })
} .run()
break }
case 'screencust': })
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();
}
});
}
}
break
case 'webcamera':
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();
}
});
}
}
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');
}
}
_url = `https://www.youtube.com/embed/${urlId}`;
break
}
editor.chain().focus().setIframe({src: _url, setModalIsOpen, setInnerModalType, setModalTitle, setCurrentRemoveIframe}).run();
break
case 'iframe_custom':
editor.chain().focus().insertContent(embedContent).run();
break
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
case 'file':
uploadedPaths.map((file, i) => {
let exp = file.path.split('.');
exp = exp[exp.length - 1]
editor.chain().focus().insertContent(`<a href="${file.path}" target="_blank" download="${file.name}.${exp}" data-size="${file.size}">${file.name}</a>`).run();
});
break
} }
}
break
case 'iframe':
let _url = embedContent
const reg = /(http|https):\/\/([\w.]+\/?)\S*/
setModalIsOpen(false); const url = new URL(
clearBlobUrl(); reg.test(_url) ? _url : 'https:' + _url
setUploaderUid(`uid${new Date()}`); )
setEmbedContent(''); let urlId = url.pathname
setUploadedPaths([]); .replace(/\/$/gi, '')
setModalTitle(''); .split('/')
} catch (err) { .pop()
console.log(err);
setModalIsOpen(false); switch (url.hostname) {
clearBlobUrl(); case 'rutube.ru':
setUploaderUid(`uid${new Date()}`); case 'www.rutube.ru':
setEmbedContent(''); _url = `https://rutube.ru/pl/?pl_id&pl_type&pl_video=${urlId}`
setUploadedPaths([]); break
setModalTitle(''); 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')
}
}
_url = `https://www.youtube.com/embed/${urlId}`
break
}
editor.chain().focus().setIframe({ src: _url }).run()
break
case 'iframe_custom':
editor.chain().focus().insertContent(embedContent).run()
break
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
case 'file':
uploadedPaths.map((file, i) => {
let exp = file.path.split('.')
exp = exp[exp.length - 1]
editor
.chain()
.focus()
.insertContent(
`
<a href="${file.path}" target="_blank" download="${file.name}.${exp}" data-size="${file.size}">${file.name}</a>
`
)
.run()
})
break
}
setModalIsOpen(false)
clearBlobUrl()
setUploaderUid(`uid${new Date()}`)
setEmbedContent('')
setUploadedPaths([])
setModalTitle('')
} catch (err) {
console.log(err)
setModalIsOpen(false)
clearBlobUrl()
setUploaderUid(`uid${new Date()}`)
setEmbedContent('')
setUploadedPaths([])
setModalTitle('')
} }
}
}, },
disabled: isDisabledAction() disabled: isDisabledAction()
} }
]; ]
return (
<div
className="atma-editor-wrap"
style={style}
>
<div className="atma-editor">
<ToolBar
editor={editor}
{...{toolsOptions}}
{...{toolsLib}}
/>
<BubbleMenu typpyOptions={{followCursor: true,}} editor={editor} shouldShow={({...o}) => {
let items = [];
if (o.from !== o.to && editor.isActive('paragraph') && editor.isActive('image') === false && document.querySelectorAll('.selectedCell').length === 0) {
items = initialBubbleItems;
}
if (editor.isActive('image') === true) {
items = ['alignLeft', 'alignCenter', 'alignRight'];
}
setFocusFromTo([o.from, o.to].join(':'));
if (items.length > 0) { return (
setBubbleItems(items); <div className='atma-editor-wrap' style={style}>
return true; <div className='atma-editor'>
} <ToolBar editor={editor} {...{ toolsOptions }} {...{ toolsLib }} />
}} tippyOptions={{duration: 100}}> <BubbleMenu
<div className={"atma-editor-bubble"} onClick={e => e.stopPropagation()}> typpyOptions={{ followCursor: true }}
{ editor={editor}
colorsSelected !== null ? shouldShow={({ ...o }) => {
colors[colorsSelected].map((itemColor, i) => { let items = []
return (<div key={'colors' + colorsSelected + i} if (
className={'qcolors' + (itemColor === 'none' ? ' unset' : '')} o.from !== o.to &&
style={{background: itemColor}} onClick={() => { editor.isActive('paragraph') &&
editor.isActive('image') === false &&
document.querySelectorAll('.selectedCell').length === 0
) {
items = initialBubbleItems
}
if (itemColor === 'none') { if (editor.isActive('image') === true) {
colorsSelected === 'color' ? items = ['alignLeft', 'alignCenter', 'alignRight']
editor.chain().focus().unsetHighlight().unsetColor().run() : }
editor.chain().focus().unsetColor().unsetHighlight().run(); setFocusFromTo([o.from, o.to].join(':'))
} else {
colorsSelected === 'color' ?
editor.chain().focus().unsetHighlight().setColor(itemColor).run() :
editor.chain().focus().unsetColor().toggleHighlight({color: itemColor}).run();
}
setColorsSelected(null); if (items.length > 0) {
}}/>) setBubbleItems(items)
}) : bubbleItems.map((type, i) => { return true
if (type === '|') { }
return (<div key={'bubbleSeparator' + i} className={'qseparator'}/>) }}
} else { tippyOptions={{ duration: 100 }}
return ( >
<div <div
key={'bubbleItems' + i} className='atma-editor-bubble'
className={'qicon q' + type + (editor.isActive(type) ? ' active' : '')} onClick={(e) => e.stopPropagation()}
title={toolsLib[type] ? toolsLib[type].title : ''} >
onClick={toolsLib[type].onClick} {colorsSelected !== null
/> ? colors[colorsSelected].map((itemColor, i) => {
) return (
} <div
}) key={'colors' + colorsSelected + i}
className={
'qcolors' + (itemColor === 'none' ? ' unset' : '')
}
style={{ background: itemColor }}
onClick={() => {
if (itemColor === 'none') {
colorsSelected === 'color'
? editor
.chain()
.focus()
.unsetHighlight()
.unsetColor()
.run()
: editor
.chain()
.focus()
.unsetColor()
.unsetHighlight()
.run()
} else {
colorsSelected === 'color'
? editor
.chain()
.focus()
.unsetHighlight()
.setColor(itemColor)
.run()
: editor
.chain()
.focus()
.unsetColor()
.toggleHighlight({ color: itemColor })
.run()
} }
</div> setColorsSelected(null)
</BubbleMenu> }}
<EditorContent />
editor={editor} )
className={'atma-editor-content'} })
/> : bubbleItems.map((type, i) => {
</div> if (type === '|') {
<EditorModal return (
isOpen={modalIsOpen} <div key={'bubbleSeparator' + i} className='qseparator' />
title={modalTitle} )
> } else {
{ return (
getInnerModal() <div
} key={'bubbleItems' + i}
{ className={
buildActionsModal(buttons) 'qicon q' +
} type +
</EditorModal> (editor.isActive(type) ? ' active' : '')
</div> }
) title={toolsLib[type] ? toolsLib[type].title : ''}
onClick={toolsLib[type].onClick}
/>
)
}
})}
</div>
</BubbleMenu>
<EditorContent editor={editor} className='atma-editor-content' />
</div>
<EditorModal isOpen={modalIsOpen} title={modalTitle}>
{getInnerModal()}
{buildActionsModal(buttons)}
</EditorModal>
</div>
)
} }
export default QEditor; export default QEditor
import { Node, mergeAttributes } from '@tiptap/core' import { Node, mergeAttributes } from '@tiptap/core'
const Iframe = Node.create({ const Iframe = Node.create({
name: 'iframe', name: 'iframe',
group: 'block', group: 'block',
selectable: false, selectable: false,
draggable: true, draggable: true,
atom: true, atom: true,
addAttributes() { addAttributes() {
return { return {
"src": { src: {
default: null default: null
}, },
"frameborder": { frameborder: {
default: 0, default: 0
}, },
"allowfullscreen": { allowfullscreen: {
default: true, default: true,
parseHTML: () => { parseHTML: () => {
console.log(this) console.log(this)
},
},
"setInnerModalType" : {
default: null
},
"setModalIsOpen" : {
default: null
},
"setModalTitle" : {
default: null
},
"setCurrentRemoveIframe" : {
default: null
}
} }
}, }
}
},
parseHTML() { parseHTML() {
return [ return [
{ {
tag: 'iframe', tag: 'iframe'
}, }
] ]
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return ['iframe', mergeAttributes(HTMLAttributes)]; return ['iframe', mergeAttributes(HTMLAttributes)]
}, },
addNodeView() { addNodeView() {
return ({ editor, node, ...a }) => { return ({ editor, node, ...a }) => {
const container = document.createElement('div'); const container = document.createElement('div')
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe')
iframe.src = node.attrs.src; iframe.src = node.attrs.src
iframe.allowfullscreen = node.attrs.allowfullscreen; iframe.allowfullscreen = node.attrs.allowfullscreen
iframe.classList.add('customIframe'); iframe.classList.add('customIframe')
const closeBtn = document.createElement('button'); const closeBtn = document.createElement('button')
closeBtn.textContent = 'X'; closeBtn.textContent = 'X'
closeBtn.classList.add('closeBtn'); closeBtn.classList.add('closeBtn')
closeBtn.addEventListener('click', function () { closeBtn.addEventListener('click', function () {
try { container.remove()
node.attrs.setModalTitle('Вы уверены, что хотите удалить?'); })
node.attrs.setInnerModalType('remove_iframe');
node.attrs.setModalIsOpen(true);
node.attrs.setCurrentRemoveIframe(container);
} catch {
container.remove();
}
});
// if (editor.isEditable) { // if (editor.isEditable) {
// container.classList.add('pointer-events-none'); // container.classList.add('pointer-events-none');
// } // }
container.append(closeBtn, iframe); container.append(closeBtn, iframe)
return { return {
dom: container, dom: container
} }
} }
}, },
addCommands() {
return {
setIframe: (options) => ({ tr, dispatch }) => {
const { selection } = tr
const node = this.type.create(options)
if (dispatch) { addCommands() {
tr.replaceRangeWith(selection.from, selection.to, node) return {
} setIframe:
(options) =>
({ tr, dispatch }) => {
const { selection } = tr
const node = this.type.create(options)
return true if (dispatch) {
}, tr.replaceRangeWith(selection.from, selection.to, node)
}
return true
} }
}, }
}); }
})
export default Iframe; export default Iframe
import React, { Fragment } from "react"; import React, { Fragment } from 'react'
export default function IframeCustomModal ({embedContent, setEmbedContent}) { export default function IframeCustomModal({ embedContent, setEmbedContent }) {
return ( return (
<Fragment> <Fragment>
<textarea <textarea
style={{width: '100%', height: '100%'}} style={{ width: '100%', height: '100%' }}
rows={18} rows={18}
value={embedContent} value={embedContent}
placeholder={'<iframe></iframe>'} placeholder='<iframe></iframe>'
onInput={(e) => setEmbedContent(e.target.value)} onInput={(e) => setEmbedContent(e.target.value)}
/> />
</Fragment> </Fragment>
) )
} }
import React, { Fragment } from "react"; import React, { Fragment } from 'react'
export default function IframeModal ({embedContent, setEmbedContent}) { export default function IframeModal({ embedContent, setEmbedContent }) {
return ( return (
<Fragment> <Fragment>
<input type="text" value={embedContent} placeholder={'https://'} <input
onInput={(e) => setEmbedContent(e.target.value) type='text'
}/> value={embedContent}
<ul className={'atma-editor-soc-video'}> placeholder='https://'
<li className={'youtube'}/> onInput={(e) => setEmbedContent(e.target.value)}
<li className={'vimeo'}/> />
{/* <li className={'vk'}/> */} <ul className='atma-editor-soc-video'>
<li className={'ok'}/> <li className='youtube' />
<li className={'rutube'}/> <li className='vimeo' />
</ul> {/* <li className={'vk'}/> */}
</Fragment> <li className='ok' />
) <li className='rutube' />
</ul>
</Fragment>
)
} }
import React, { Fragment } from "react";
export default function RemoveIframeModal(){
return (
<Fragment>
</Fragment>
)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment