Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
R
react-ag-qeditor
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lib
react-ag-qeditor
Commits
5101cee0
Commit
5101cee0
authored
Oct 09, 2023
by
DenSakh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: refactoring
parent
5499f2c8
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
1211 additions
and
1090 deletions
+1211
-1090
QEditor.jsx
src/QEditor.jsx
+1112
-967
Iframe.js
src/extensions/Iframe.js
+67
-85
IframeCustomModal.js
src/modals/IframeCustomModal.js
+13
-13
IframeModal.js
src/modals/IframeModal.js
+19
-16
RemoveIframeModal.js
src/modals/RemoveIframeModal.js
+0
-9
No files found.
src/QEditor.jsx
View file @
5101cee0
/* 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
src/extensions/Iframe.js
View file @
5101cee0
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
src/modals/IframeCustomModal.js
View file @
5101cee0
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
>
)
)
}
}
src/modals/IframeModal.js
View file @
5101cee0
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
>
)
}
}
src/modals/RemoveIframeModal.js
deleted
100644 → 0
View file @
5499f2c8
import
React
,
{
Fragment
}
from
"react"
;
export
default
function
RemoveIframeModal
(){
return
(
<
Fragment
>
<
/Fragment
>
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment