Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
lib
react-ag-qeditor
Commits
07c74710
Commit
07c74710
authored
May 20, 2026
by
Яков
Browse files
верстка
parent
a53a9695
Changes
4
Show whitespace changes
Inline
Side-by-side
package.json
View file @
07c74710
{
{
"name"
:
"react-ag-qeditor"
,
"name"
:
"react-ag-qeditor"
,
"version"
:
"1.1.4
5
"
,
"version"
:
"1.1.4
6
"
,
"description"
:
"WYSIWYG html editor"
,
"description"
:
"WYSIWYG html editor"
,
"author"
:
"atma"
,
"author"
:
"atma"
,
"license"
:
"
MIT
"
,
"license"
:
"
MIT
"
,
...
...
src/QEditor.jsx
View file @
07c74710
...
@@ -181,13 +181,15 @@ const QEditor = ({
...
@@ -181,13 +181,15 @@ const QEditor = ({
}
}
},
[
focusFromTo
])
},
[
focusFromTo
])
const
SUGGEST_TYPES
=
[
'
image
'
,
'
interactiveImage
'
,
'
video
'
,
'
audio
'
,
'
file
'
]
const
SUGGEST_TYPES
=
[
'
image
'
,
'
interactiveImage
'
,
'
video
'
,
'
audio
'
,
'
file
'
,
'
iframe_pdf
'
,
'
iframe_pptx
'
]
const
SUGGEST_TYPE_MAP
=
{
const
SUGGEST_TYPE_MAP
=
{
image
:
'
image
'
,
image
:
'
image
'
,
interactiveImage
:
'
image
'
,
interactiveImage
:
'
image
'
,
video
:
'
video
'
,
video
:
'
video
'
,
audio
:
'
audio
'
,
audio
:
'
audio
'
,
file
:
'
file
'
file
:
'
file
'
,
iframe_pdf
:
'
pdf
'
,
iframe_pptx
:
'
pptx
'
}
}
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -861,8 +863,23 @@ const QEditor = ({
...
@@ -861,8 +863,23 @@ const QEditor = ({
}
}
const
isVideo
=
innerModalType
===
'
video
'
const
isVideo
=
innerModalType
===
'
video
'
const
isAudio
=
innerModalType
===
'
audio
'
const
isAudio
=
innerModalType
===
'
audio
'
const
isFile
=
innerModalType
===
'
file
'
const
isFile
Type
=
innerModalType
===
'
file
'
||
innerModalType
===
'
iframe_pdf
'
||
innerModalType
===
'
iframe_pptx
'
const
totalPages
=
Math
.
ceil
(
suggestTotal
/
SUGGEST_LIMIT
)
const
totalPages
=
Math
.
ceil
(
suggestTotal
/
SUGGEST_LIMIT
)
const
toggleSelect
=
(
file
,
uid
)
=>
{
const
isSelected
=
uploadedPaths
.
some
(
p
=>
p
.
uid
===
uid
)
if
(
isSelected
)
{
setUploadedPaths
(
uploadedPaths
.
filter
(
p
=>
p
.
uid
!==
uid
))
}
else
{
setUploadedPaths
([...
uploadedPaths
,
{
path
:
file
.
path
,
uid
,
name
:
file
.
name
,
size
:
file
.
size
}])
}
}
return
(
return
(
<
div
className
=
'atma-editor-suggest'
>
<
div
className
=
'atma-editor-suggest'
>
<
div
className
=
'atma-editor-suggest-title'
>
Ранее загруженные файлы
</
div
>
<
div
className
=
'atma-editor-suggest-title'
>
Ранее загруженные файлы
</
div
>
...
@@ -870,33 +887,42 @@ const QEditor = ({
...
@@ -870,33 +887,42 @@ const QEditor = ({
{
suggestedFiles
.
map
((
file
,
i
)
=>
{
{
suggestedFiles
.
map
((
file
,
i
)
=>
{
const
uid
=
'
suggest_
'
+
file
.
path
const
uid
=
'
suggest_
'
+
file
.
path
const
isSelected
=
uploadedPaths
.
some
(
p
=>
p
.
uid
===
uid
)
const
isSelected
=
uploadedPaths
.
some
(
p
=>
p
.
uid
===
uid
)
const
thumbnail
=
isVideo
?
(
file
.
poster
||
file
.
path
+
'
.jpg
'
)
if
(
isVideo
)
{
:
(
!
isAudio
&&
!
isFile
?
file
.
path
:
null
)
return
(
<
div
key
=
{
'
suggest
'
+
i
}
className
=
{
'
atma-editor-suggest-item is-video
'
+
(
isSelected
?
'
selected
'
:
''
)
}
>
<
video
src
=
{
file
.
path
}
controls
className
=
'atma-editor-suggest-video'
title
=
{
file
.
name
}
/>
<
button
type
=
'button'
className
=
'atma-editor-suggest-select-btn'
title
=
{
isSelected
?
'
Снять выбор
'
:
'
Выбрать
'
}
onClick
=
{
()
=>
toggleSelect
(
file
,
uid
)
}
/>
</
div
>
)
}
const
thumbnail
=
!
isAudio
&&
!
isFileType
?
file
.
path
:
null
return
(
return
(
<
div
<
div
key
=
{
'
suggest
'
+
i
}
key
=
{
'
suggest
'
+
i
}
className
=
{
className
=
{
'
atma-editor-suggest-item
'
+
'
atma-editor-suggest-item
'
+
(
isSelected
?
'
selected
'
:
''
)
+
(
isSelected
?
'
selected
'
:
''
)
+
(
isFile
?
'
is-file
'
:
''
)
+
(
isFileType
?
'
is-file
'
:
''
)
+
(
isAudio
?
'
is-audio
'
:
''
)
+
(
isAudio
?
'
is-audio
'
:
''
)
(
isVideo
?
'
is-video
'
:
''
)
}
}
style
=
{
thumbnail
?
{
backgroundImage
:
`url(
${
thumbnail
}
)`
}
:
{}
}
style
=
{
thumbnail
?
{
backgroundImage
:
`url(
${
thumbnail
}
)`
}
:
{}
}
title
=
{
file
.
name
}
title
=
{
file
.
name
}
onClick
=
{
()
=>
{
onClick
=
{
()
=>
toggleSelect
(
file
,
uid
)
}
if
(
isSelected
)
{
setUploadedPaths
(
uploadedPaths
.
filter
(
p
=>
p
.
uid
!==
uid
))
}
else
{
setUploadedPaths
([...
uploadedPaths
,
{
path
:
file
.
path
,
uid
,
name
:
file
.
name
,
size
:
file
.
size
}])
}
}
}
>
>
<
span
className
=
'atma-editor-suggest-item-name'
>
{
file
.
name
}
</
span
>
<
span
className
=
'atma-editor-suggest-item-name'
>
{
file
.
name
}
</
span
>
</
div
>
</
div
>
...
@@ -945,6 +971,7 @@ const QEditor = ({
...
@@ -945,6 +971,7 @@ const QEditor = ({
case
'
iframe_pptx
'
:
case
'
iframe_pptx
'
:
return
(
return
(
<
Fragment
>
<
Fragment
>
{
getSuggestionsSection
()
}
{
getUploader
({
{
getUploader
({
accept
:
accept
:
'
application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.slideshow, application/vnd.openxmlformats-officedocument.presentationml.presentation
'
,
'
application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.slideshow, application/vnd.openxmlformats-officedocument.presentationml.presentation
'
,
...
@@ -962,6 +989,7 @@ const QEditor = ({
...
@@ -962,6 +989,7 @@ const QEditor = ({
case
'
iframe_pdf
'
:
case
'
iframe_pdf
'
:
return
(
return
(
<
Fragment
>
<
Fragment
>
{
getSuggestionsSection
()
}
{
getUploader
({
{
getUploader
({
accept
:
'
application/pdf
'
,
accept
:
'
application/pdf
'
,
afterParams
:
[
'
no_convert=1
'
]
afterParams
:
[
'
no_convert=1
'
]
...
@@ -1342,6 +1370,8 @@ const QEditor = ({
...
@@ -1342,6 +1370,8 @@ const QEditor = ({
switch
(
innerModalType
)
{
switch
(
innerModalType
)
{
case
'
video
'
:
case
'
video
'
:
case
'
image
'
:
case
'
image
'
:
case
'
iframe_pdf
'
:
case
'
iframe_pptx
'
:
if
(
uploadOptions
.
url
===
null
||
uploadedPaths
.
length
===
0
)
{
if
(
uploadOptions
.
url
===
null
||
uploadedPaths
.
length
===
0
)
{
isDisabled
=
true
isDisabled
=
true
}
}
...
...
src/components/ToolBar.js
View file @
07c74710
...
@@ -3,100 +3,64 @@ import React, { Fragment, useState } from 'react';
...
@@ -3,100 +3,64 @@ import React, { Fragment, useState } from 'react';
const
toolsInit
=
{
const
toolsInit
=
{
'
all
'
:
[
'
all
'
:
[
{
{
type
:
'
g
'
,
items
:
[
'
undo
'
,
'
redo
'
]
},
type
:
'
g
'
,
{
type
:
'
g
'
,
items
:
[
'
|
'
]
},
items
:
[
'
undo
'
,
// Aa▼ — стиль текста
'
redo
'
{
type
:
'
s
'
,
toggle
:
true
,
items
:
[
'
paragraph
'
,
'
h2
'
,
'
h3
'
,
'
h4
'
]
},
]
},
// Lists▼ — списки
{
{
type
:
'
s
'
,
toggle
:
false
,
items
:
[
'
bulletList
'
,
'
orderedList
'
,
'
insertToggleBlock
'
]
},
type
:
'
s
'
,
toggle
:
true
,
items
:
[
'
paragraph
'
,
'
h2
'
,
'
h3
'
,
'
h4
'
]
},
{
type
:
'
g
'
,
items
:
[
'
bulletList
'
,
'
orderedList
'
,
'
blockquote
'
,
'
codeBlock
'
,
'
hr
'
,
]
},
{
type
:
'
s
'
,
toggle
:
true
,
items
:
[
'
alignLeft
'
,
'
alignCenter
'
,
'
alignRight
'
,
]
},
{
type
:
'
g
'
,
items
:
[
'
emoji
'
,
'
link
'
,
'
buttonLink
'
,
'
file
'
,
'
image
'
,
'
interactiveImage
'
,
'
video
'
,
'
iframe
'
,
'
iframe_pptx
'
,
'
iframe_pdf
'
,
'
pdf
'
,
'
audio
'
,
'
iframe_custom
'
,
'
insertToggleBlock
'
]
},
{
type
:
'
g
'
,
items
:
[
'
clearMarks
'
,
'
hardBreak
'
,
]
},
// ≡▼ — выравнивание
{
type
:
'
s
'
,
toggle
:
true
,
items
:
[
'
alignLeft
'
,
'
alignCenter
'
,
'
alignRight
'
]
},
// strike
{
type
:
'
g
'
,
items
:
[
'
strike
'
]
},
// Table▼
{
{
type
:
'
s
'
,
type
:
'
s
'
,
toggle
:
false
,
toggle
:
false
,
items
:
[
items
:
[
'
insertTable
'
,
'
insertTable
'
,
'
|
'
,
'
|
'
,
'
addRowBefore
'
,
'
addRowAfter
'
,
'
deleteRow
'
,
'
|
'
,
'
addRowBefore
'
,
'
addColumnBefore
'
,
'
addColumnAfter
'
,
'
deleteColumn
'
,
'
|
'
,
'
addRowAfter
'
,
'
toggleHeaderCell
'
,
'
|
'
,
'
deleteRow
'
,
'
mergeOrSplit
'
,
'
deleteTable
'
,
'
|
'
,
'
|
'
,
'
addColumnBefore
'
,
'
addColumnAfter
'
,
'
deleteColumn
'
,
'
|
'
,
'
toggleHeaderCell
'
,
'
|
'
,
'
mergeOrSplit
'
,
'
deleteTable
'
,
'
|
'
,
'
toggleTableBorders
'
'
toggleTableBorders
'
]
]
},
},
{
type
:
'
g
'
,
{
type
:
'
g
'
,
items
:
[
'
emoji
'
]
},
items
:
[
{
type
:
'
g
'
,
items
:
[
'
clearMarks
'
]
},
'
voicemessage
'
,
{
type
:
'
g
'
,
items
:
[
'
codeBlock
'
]
},
'
webcamera
'
,
'
screencust
'
,
// ─── разделитель ───
'
export_pdf
'
,
{
type
:
'
g
'
,
items
:
[
'
|
'
]
},
]
},
// Image▼ — изображение / интерактивное
{
type
:
'
s
'
,
toggle
:
false
,
items
:
[
'
image
'
,
'
interactiveImage
'
]
},
// Video▼ — загрузить / по ссылке
{
type
:
'
s
'
,
toggle
:
false
,
items
:
[
'
video
'
,
'
iframe
'
]
},
{
type
:
'
g
'
,
items
:
[
'
audio
'
]
},
{
type
:
'
g
'
,
items
:
[
'
voicemessage
'
]
},
{
type
:
'
g
'
,
items
:
[
'
webcamera
'
]
},
{
type
:
'
g
'
,
items
:
[
'
screencust
'
]
},
// ─── разделитель ───
{
type
:
'
g
'
,
items
:
[
'
|
'
]
},
// Docs▼ — pptx / pdf / pdf->text
{
type
:
'
s
'
,
toggle
:
false
,
items
:
[
'
iframe_pptx
'
,
'
iframe_pdf
'
,
'
pdf
'
]
},
// Link▼ — ссылка / кнопка-ссылка
{
type
:
'
s
'
,
toggle
:
false
,
items
:
[
'
link
'
,
'
buttonLink
'
]
},
{
type
:
'
g
'
,
items
:
[
'
file
'
]
},
{
type
:
'
g
'
,
items
:
[
'
iframe_custom
'
]
},
],
],
'
text
'
:
[
'
text
'
:
[
{
{
...
@@ -304,10 +268,24 @@ const ToolBar = ({ editor, toolsLib = [], toolsOptions }) => {
...
@@ -304,10 +268,24 @@ const ToolBar = ({ editor, toolsLib = [], toolsOptions }) => {
}
}
const
isThickSep
=
(
section
)
=>
section
.
type
===
'
g
'
&&
section
.
items
.
length
===
1
&&
section
.
items
[
0
]
===
'
|
'
const
getItems
=
()
=>
{
const
getItems
=
()
=>
{
let
toolItems
=
[];
let
toolItems
=
[];
toolbarItems
.
map
((
section
,
i
)
=>
{
toolbarItems
.
map
((
section
,
i
)
=>
{
const
prevSection
=
toolbarItems
[
i
-
1
]
const
needThinSep
=
i
>
0
&&
!
isThickSep
(
section
)
&&
prevSection
&&
!
isThickSep
(
prevSection
)
if
(
needThinSep
)
{
toolItems
.
push
(
<
div
key
=
{
`thin-sep-
${
i
}
`
}
className
=
"
atma-editor-toolbar-thin-sep
"
/>
)
}
let
gItems
=
[];
let
gItems
=
[];
if
(
section
.
type
===
'
g
'
){
if
(
section
.
type
===
'
g
'
){
section
.
items
.
map
((
gKey
,
idx
)
=>
{
section
.
items
.
map
((
gKey
,
idx
)
=>
{
...
...
src/index.scss
View file @
07c74710
...
@@ -257,22 +257,35 @@ body{
...
@@ -257,22 +257,35 @@ body{
box-shadow: 0 -3px 0 3px #ffffff;
box-shadow: 0 -3px 0 3px #ffffff;
padding: 6px;
padding: 6px;
z-index: 10;
z-index: 10;
grid-gap: 7.5px;
&-g{
&-g{
display: flex;
display: flex;
flex-direction: row;
flex-direction: row;
align-items: center;
&:after{
.qseparator{
content: '';
width: 1px;
display: inline-block;
height: 18px;
width: 20px;
background-color: #CCD1D9;
margin: 0 6px;
flex-shrink: 0;
}
}
}
}
&-thin-sep{
width: 1px;
height: 18px;
background-color: rgba(0, 0, 0, 0.06);
flex-shrink: 0;
align-self: center;
}
&-s{
&-s{
$this: &;
$this: &;
position: relative;
position: relative;
padding-right: 5px;
padding-right: 5px;
margin-right:
9px
;
margin-right:
0
;
.qseparator{
.qseparator{
display: block;
display: block;
...
@@ -597,8 +610,8 @@ body{
...
@@ -597,8 +610,8 @@ body{
background: #fff;
background: #fff;
transform: translate(0, -50%);
transform: translate(0, -50%);
border-radius: 8px;
border-radius: 8px;
max-width: 90%;
//
max-width: 90%;
m
in
-width:
560
px;
m
ax
-width:
646
px;
padding: 48px;
padding: 48px;
max-height: 900px;
max-height: 900px;
overflow-y: scroll;
overflow-y: scroll;
...
@@ -711,7 +724,7 @@ body{
...
@@ -711,7 +724,7 @@ body{
&-uploader{
&-uploader{
&-drop{
&-drop{
display: flex;
display: flex;
width:
49
0px;
width:
55
0px;
height: 154px;
height: 154px;
background-color: #F5F7FA;
background-color: #F5F7FA;
border-radius: 16px;
border-radius: 16px;
...
@@ -902,7 +915,23 @@ body{
...
@@ -902,7 +915,23 @@ body{
}
}
&.is-video{
&.is-video{
background-color: #1a1a1a;
width: 240px;
aspect-ratio: 16 / 9;
background-color: #000;
background-image: none !important;
cursor: default;
&:hover{
box-shadow: 0 0 0 2px transparent;
}
&.selected{
box-shadow: 0 0 0 2px #1790FF;
&::after{
display: none;
}
}
}
}
&-name{
&-name{
...
@@ -921,6 +950,41 @@ body{
...
@@ -921,6 +950,41 @@ body{
}
}
}
}
&-video{
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
&-select-btn{
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid rgba(255,255,255,0.8);
background-color: rgba(0,0,0,0.35);
cursor: pointer;
padding: 0;
transition: background-color 0.15s, border-color 0.15s;
z-index: 1;
&:hover{
background-color: rgba(0,0,0,0.55);
border-color: #fff;
}
.selected &{
background-color: #1790FF;
border-color: #1790FF;
background-image: url('
data
:image
/
svg
+
xml
;
charset
=
utf8
,
%3Csvg%20width%3D%228%22%20height%3D%226%22%20xmlns%3D%22http%3A%2F%2Fwww
.w3.org
%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M1%203l2%202%204-4%22%20stroke%3D%22%23fff%22%20stroke-width%3D%221
.5
%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E
');
background-repeat: no-repeat;
background-position: center;
}
}
&-pagination{
&-pagination{
display: flex;
display: flex;
align-items: center;
align-items: center;
...
@@ -1362,10 +1426,6 @@ body{
...
@@ -1362,10 +1426,6 @@ body{
}
}
}
}
.qseparator{
width: 16px;
}
.closeBtn {
.closeBtn {
position: relative;
position: relative;
display: flex;
display: flex;
...
...
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