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
2041da4b
Commit
2041da4b
authored
Jul 14, 2025
by
Яков
Browse files
update
parent
f31dfa97
Changes
6
Show whitespace changes
Inline
Side-by-side
example/src/setupProxy.js
View file @
2041da4b
...
@@ -7,7 +7,7 @@ module.exports = function(app) {
...
@@ -7,7 +7,7 @@ module.exports = function(app) {
target
:
'
https://cdn.atmaguru.online
'
,
target
:
'
https://cdn.atmaguru.online
'
,
changeOrigin
:
true
,
changeOrigin
:
true
,
pathRewrite
:
{
pathRewrite
:
{
'
^/upload
'
:
'
https://cdn.atmaguru.online/upload/?sid=demo&md5=
HNxOMxidAMprpPLfAwdTA
g&expires=175
1035910
'
,
'
^/upload
'
:
'
https://cdn.atmaguru.online/upload/?sid=demo&md5=
G04JoFyFRn5TOviY9R0Na
g&expires=175
2528156
'
,
},
},
onProxyReq
:
(
proxyReq
)
=>
{
onProxyReq
:
(
proxyReq
)
=>
{
// Добавляем необходимые заголовки
// Добавляем необходимые заголовки
...
...
package.json
View file @
2041da4b
{
{
"name"
:
"react-ag-qeditor"
,
"name"
:
"react-ag-qeditor"
,
"version"
:
"1.1.
8
"
,
"version"
:
"1.1.
9
"
,
"description"
:
"WYSIWYG html editor"
,
"description"
:
"WYSIWYG html editor"
,
"author"
:
"atma"
,
"author"
:
"atma"
,
"license"
:
"
MIT
"
,
"license"
:
"
MIT
"
,
...
...
src/QEditor.jsx
View file @
2041da4b
...
@@ -35,6 +35,7 @@ import ReactStopwatch from 'react-stopwatch'
...
@@ -35,6 +35,7 @@ import ReactStopwatch from 'react-stopwatch'
import
Audio
from
'
./extensions/Audio
'
import
Audio
from
'
./extensions/Audio
'
import
TableExtension
from
'
./extensions/TableExtension
'
import
TableExtension
from
'
./extensions/TableExtension
'
import
ToggleBlock
from
'
./extensions/ToggleBlock
'
import
ToggleBlock
from
'
./extensions/ToggleBlock
'
import
InteractiveImage
from
'
./extensions/InteractiveImage
'
// import Image from '@tiptap/extension-image'
// import Image from '@tiptap/extension-image'
// import ImageResize from 'tiptap-extension-resize-image';
// import ImageResize from 'tiptap-extension-resize-image';
...
@@ -257,6 +258,12 @@ const QEditor = ({
...
@@ -257,6 +258,12 @@ const QEditor = ({
title
:
'
Загрузить изображение
'
,
title
:
'
Загрузить изображение
'
,
onClick
:
()
=>
modalOpener
(
'
image
'
,
'
Загрузить изображение
'
)
onClick
:
()
=>
modalOpener
(
'
image
'
,
'
Загрузить изображение
'
)
},
},
interactiveImage
:
{
title
:
'
Интерактивное изображение
'
,
onClick
:
()
=>
{
modalOpener
(
'
interactiveImage
'
,
'
Интерактивное изображение
'
)
},
},
h2
:
{
h2
:
{
title
:
'
Заголовок 2
'
,
title
:
'
Заголовок 2
'
,
onClick
:
()
=>
editor
.
chain
().
focus
().
toggleHeading
({
level
:
2
}).
run
()
onClick
:
()
=>
editor
.
chain
().
focus
().
toggleHeading
({
level
:
2
}).
run
()
...
@@ -551,6 +558,7 @@ const QEditor = ({
...
@@ -551,6 +558,7 @@ const QEditor = ({
Superscript
,
Superscript
,
Subscript
,
Subscript
,
ToggleBlock
,
ToggleBlock
,
InteractiveImage
,
DragAndDrop
.
configure
({
DragAndDrop
.
configure
({
uploadUrl
:
uploadOptions
.
url
,
uploadUrl
:
uploadOptions
.
url
,
allowedFileTypes
:
[
allowedFileTypes
:
[
...
@@ -616,6 +624,7 @@ const QEditor = ({
...
@@ -616,6 +624,7 @@ const QEditor = ({
if
(
typeof
o
.
multiple
!==
'
undefined
'
)
{
if
(
typeof
o
.
multiple
!==
'
undefined
'
)
{
multiple
=
o
.
multiple
multiple
=
o
.
multiple
}
}
// console.log(o);
return
(
return
(
<
Uploader
<
Uploader
...
@@ -714,6 +723,8 @@ const QEditor = ({
...
@@ -714,6 +723,8 @@ const QEditor = ({
return
<
Fragment
>
{
getUploader
({
accept
:
'
video/mp4,.mp4
'
})
}
</
Fragment
>
return
<
Fragment
>
{
getUploader
({
accept
:
'
video/mp4,.mp4
'
})
}
</
Fragment
>
case
'
image
'
:
case
'
image
'
:
return
<
Fragment
>
{
getUploader
({
accept
:
'
image/*
'
})
}
</
Fragment
>
return
<
Fragment
>
{
getUploader
({
accept
:
'
image/*
'
})
}
</
Fragment
>
case
'
interactiveImage
'
:
return
<
Fragment
>
{
getUploader
({
accept
:
'
image/*
'
,
multiple
:
false
})
}
</
Fragment
>
case
'
file
'
:
case
'
file
'
:
return
(
return
(
<
Fragment
>
<
Fragment
>
...
@@ -967,6 +978,11 @@ const QEditor = ({
...
@@ -967,6 +978,11 @@ const QEditor = ({
isDisabled
=
true
isDisabled
=
true
}
}
break
break
case
'
interactiveImage
'
:
if
(
uploadOptions
.
url
===
null
||
uploadedPaths
.
length
===
0
)
{
isDisabled
=
true
}
break
case
'
screencust
'
:
case
'
screencust
'
:
if
(
status
===
'
recording
'
||
isUploading
||
!
mediaBlobUrl
)
{
if
(
status
===
'
recording
'
||
isUploading
||
!
mediaBlobUrl
)
{
isDisabled
=
true
isDisabled
=
true
...
@@ -1111,6 +1127,33 @@ const QEditor = ({
...
@@ -1111,6 +1127,33 @@ const QEditor = ({
// uploadedPaths.map((file, i) => {
// uploadedPaths.map((file, i) => {
// editor.chain().focus().setImage({src: file.path}).run();
// editor.chain().focus().setImage({src: file.path}).run();
// })
// })
break
case
'
interactiveImage
'
:
uploadedPaths
.
map
(
async
(
file
)
=>
{
const
img
=
new
Image
()
img
.
src
=
file
.
path
img
.
onload
=
()
=>
{
const
maxWidth
=
editor
.
view
.
dom
.
clientWidth
-
32
// учёт padding
const
realWidth
=
Math
.
min
(
img
.
naturalWidth
,
maxWidth
)
const
realHeight
=
img
.
naturalHeight
*
(
realWidth
/
img
.
naturalWidth
)
editor
.
chain
()
.
focus
()
.
insertContent
({
type
:
'
interactiveImage
'
,
attrs
:
{
src
:
file
.
path
,
width
:
Math
.
round
(
realWidth
),
height
:
Math
.
round
(
realHeight
),
points
:
[]
},
})
.
run
()
}
})
break
break
case
'
video
'
:
case
'
video
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
uploadedPaths
.
map
((
file
,
i
)
=>
{
...
...
src/components/ToolBar.js
View file @
2041da4b
...
@@ -45,6 +45,7 @@ const toolsInit = {
...
@@ -45,6 +45,7 @@ const toolsInit = {
'
link
'
,
'
link
'
,
'
file
'
,
'
file
'
,
'
image
'
,
'
image
'
,
'
interactiveImage
'
,
'
video
'
,
'
video
'
,
'
iframe
'
,
'
iframe
'
,
'
iframe_pptx
'
,
'
iframe_pptx
'
,
...
...
src/components/Uploader.js
View file @
2041da4b
...
@@ -107,7 +107,7 @@ export default class Uploader extends React.Component {
...
@@ -107,7 +107,7 @@ export default class Uploader extends React.Component {
if
(
this
.
action
===
null
){
if
(
this
.
action
===
null
){
return
<
div
style
=
{{
textAlign
:
'
left
'
}}
>
{
this
.
errorMessage
}
<
/div
>
return
<
div
style
=
{{
textAlign
:
'
left
'
}}
>
{
this
.
errorMessage
}
<
/div
>
}
}
console
.
log
(
this
.
props
)
return
(
return
(
<
Fragment
>
<
Fragment
>
<
div
className
=
{
'
atma-editor-uploader-uitems
'
}
>
<
div
className
=
{
'
atma-editor-uploader-uitems
'
}
>
...
...
src/extensions/InteractiveImage.js
0 → 100644
View file @
2041da4b
import
{
Node
,
mergeAttributes
,
ReactNodeViewRenderer
}
from
'
@tiptap/react
'
import
React
,
{
useState
}
from
'
react
'
import
{
NodeViewWrapper
}
from
'
@tiptap/react
'
import
{
Button
,
Modal
,
Popconfirm
,
Input
}
from
'
antd
'
const
InteractiveImageView
=
({
node
,
updateAttributes
})
=>
{
const
[
modalVisible
,
setModalVisible
]
=
useState
(
false
)
const
[
points
,
setPoints
]
=
useState
(
node
.
attrs
.
points
||
[])
const
addPoint
=
(
e
)
=>
{
const
rect
=
e
.
target
.
getBoundingClientRect
()
const
x
=
e
.
clientX
-
rect
.
left
const
y
=
e
.
clientY
-
rect
.
top
const
newText
=
prompt
(
'
Введите текст точки:
'
)
if
(
newText
)
{
const
newPoints
=
[...
points
,
{
x
,
y
,
text
:
newText
}]
setPoints
(
newPoints
)
updateAttributes
({
points
:
newPoints
})
}
}
const
removePoint
=
(
index
)
=>
{
const
newPoints
=
points
.
filter
((
_
,
i
)
=>
i
!==
index
)
setPoints
(
newPoints
)
updateAttributes
({
points
:
newPoints
})
}
return
(
<
NodeViewWrapper
as
=
"
div
"
className
=
"
interactive-image-wrapper
"
contentEditable
=
{
false
}
>
<
div
style
=
{{
position
:
'
relative
'
,
display
:
'
inline-block
'
}}
>
<
img
src
=
{
node
.
attrs
.
src
}
alt
=
""
style
=
{{
maxWidth
:
'
100%
'
,
display
:
'
block
'
}}
/
>
<
Button
size
=
"
small
"
type
=
"
primary
"
onClick
=
{()
=>
setModalVisible
(
true
)}
style
=
{{
position
:
'
absolute
'
,
top
:
10
,
right
:
10
,
zIndex
:
10
}}
>
Редактировать
<
/Button
>
{
points
.
map
((
point
,
idx
)
=>
(
<
div
key
=
{
idx
}
title
=
{
point
.
text
}
style
=
{{
position
:
'
absolute
'
,
top
:
point
.
y
,
left
:
point
.
x
,
width
:
10
,
height
:
10
,
backgroundColor
:
'
red
'
,
borderRadius
:
'
50%
'
,
transform
:
'
translate(-50%, -50%)
'
,
zIndex
:
5
,
}}
/
>
))}
<
/div
>
<
Modal
open
=
{
modalVisible
}
onCancel
=
{()
=>
setModalVisible
(
false
)}
onOk
=
{()
=>
setModalVisible
(
false
)}
title
=
"
Редактировать точки
"
footer
=
{
null
}
>
<
div
style
=
{{
position
:
'
relative
'
}}
>
<
img
src
=
{
node
.
attrs
.
src
}
alt
=
""
style
=
{{
width
:
'
100%
'
,
maxWidth
:
'
100%
'
,
display
:
'
block
'
}}
onClick
=
{
addPoint
}
/
>
{
points
.
map
((
point
,
idx
)
=>
(
<
Popconfirm
key
=
{
idx
}
title
=
{
<
div
>
<
Input
.
TextArea
defaultValue
=
{
point
.
text
}
onChange
=
{(
e
)
=>
{
const
updated
=
[...
points
]
updated
[
idx
].
text
=
e
.
target
.
value
setPoints
(
updated
)
}}
/
>
<
/div
>
}
onConfirm
=
{()
=>
updateAttributes
({
points
})}
onCancel
=
{()
=>
removePoint
(
idx
)}
okText
=
"
Применить
"
cancelText
=
"
Удалить
"
>
<
div
style
=
{{
position
:
'
absolute
'
,
top
:
point
.
y
,
left
:
point
.
x
,
width
:
12
,
height
:
12
,
backgroundColor
:
'
blue
'
,
borderRadius
:
'
50%
'
,
transform
:
'
translate(-50%, -50%)
'
,
cursor
:
'
pointer
'
,
}}
/
>
<
/Popconfirm
>
))}
<
/div
>
<
/Modal
>
<
/NodeViewWrapper
>
)
}
export
const
InteractiveImage
=
Node
.
create
({
name
:
'
interactiveImage
'
,
group
:
'
block
'
,
draggable
:
true
,
selectable
:
true
,
atom
:
true
,
addAttributes
()
{
return
{
src
:
{
default
:
null
},
points
:
{
default
:
[],
// [{x: 120, y: 90, text: "Hello"}]
parseHTML
:
el
=>
JSON
.
parse
(
el
.
getAttribute
(
'
data-points
'
)
||
'
[]
'
),
renderHTML
:
attrs
=>
attrs
.
points
.
length
>
0
?
{
'
data-points
'
:
JSON
.
stringify
(
attrs
.
points
)
}
:
{},
},
}
},
parseHTML
()
{
return
[{
tag
:
'
interactive-image
'
}]
},
renderHTML
({
HTMLAttributes
})
{
return
[
'
interactive-image
'
,
mergeAttributes
(
HTMLAttributes
)]
},
addNodeView
()
{
return
ReactNodeViewRenderer
(
InteractiveImageView
)
},
})
export
default
InteractiveImage
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