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
78d6243d
Commit
78d6243d
authored
Apr 17, 2026
by
Яков
Browse files
add button-url
parent
47ffb026
Changes
4
Hide whitespace changes
Inline
Side-by-side
src/QEditor.jsx
View file @
78d6243d
...
@@ -12,7 +12,7 @@ import TableCell from '@tiptap/extension-table-cell'
...
@@ -12,7 +12,7 @@ import TableCell from '@tiptap/extension-table-cell'
import
TableRow
from
'
@tiptap/extension-table-row
'
import
TableRow
from
'
@tiptap/extension-table-row
'
import
TableHeader
from
'
@tiptap/extension-table-header
'
import
TableHeader
from
'
@tiptap/extension-table-header
'
import
Focus
from
'
@tiptap/extension-focus
'
import
Focus
from
'
@tiptap/extension-focus
'
import
{
Input
,
Modal
,
Form
,
Button
,
message
}
from
"
antd
"
;
import
{
Input
,
Modal
,
Form
,
Button
,
message
,
Select
as
AntdSelect
}
from
"
antd
"
;
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
'
...
@@ -38,6 +38,7 @@ import TableExtension from './extensions/TableExtension'
...
@@ -38,6 +38,7 @@ import TableExtension from './extensions/TableExtension'
import
ToggleBlock
from
'
./extensions/ToggleBlock
'
import
ToggleBlock
from
'
./extensions/ToggleBlock
'
import
InteractiveImage
from
'
./extensions/InteractiveImage
'
import
InteractiveImage
from
'
./extensions/InteractiveImage
'
import
FontSize
from
'
./extensions/FontSize
'
import
FontSize
from
'
./extensions/FontSize
'
import
ButtonLinkExtension
from
'
./extensions/ButtonLink
'
// 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';
...
@@ -52,6 +53,7 @@ import { isMobile } from 'react-device-detect'
...
@@ -52,6 +53,7 @@ import { isMobile } from 'react-device-detect'
import
{
ExportPdf
}
from
'
./extensions/ExportPdf
'
import
{
ExportPdf
}
from
'
./extensions/ExportPdf
'
import
{
mergeAttributes
}
from
"
@tiptap/core
"
;
import
{
mergeAttributes
}
from
"
@tiptap/core
"
;
import
Upload
from
"
rc-upload
"
;
import
Upload
from
"
rc-upload
"
;
import
{
NodeSelection
}
from
'
prosemirror-state
'
// const CustomImage = Image.extend({
// const CustomImage = Image.extend({
// options: {inline: true},
// options: {inline: true},
...
@@ -92,6 +94,34 @@ const QEditor = ({
...
@@ -92,6 +94,34 @@ const QEditor = ({
})
=>
{
})
=>
{
global
.
uploadUrl
=
uploadOptions
.
url
global
.
uploadUrl
=
uploadOptions
.
url
const
defaultButtonLinkData
=
{
text
:
'
Перейти
'
,
href
:
''
,
fontSize
:
'
16px
'
,
textColor
:
'
#ffffff
'
,
backgroundColor
:
'
#1790FF
'
}
const
getSelectedButtonLinkData
=
()
=>
{
if
(
!
editor
||
!
(
editor
.
state
.
selection
instanceof
NodeSelection
))
{
return
null
}
const
selectedNode
=
editor
.
state
.
selection
.
node
if
(
!
selectedNode
||
selectedNode
.
type
.
name
!==
'
buttonLink
'
)
{
return
null
}
return
{
text
:
selectedNode
.
attrs
.
text
||
defaultButtonLinkData
.
text
,
href
:
selectedNode
.
attrs
.
href
||
''
,
fontSize
:
selectedNode
.
attrs
.
fontSize
||
defaultButtonLinkData
.
fontSize
,
textColor
:
selectedNode
.
attrs
.
textColor
||
defaultButtonLinkData
.
textColor
,
backgroundColor
:
selectedNode
.
attrs
.
backgroundColor
||
defaultButtonLinkData
.
backgroundColor
}
}
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
())
...
@@ -106,6 +136,7 @@ const QEditor = ({
...
@@ -106,6 +136,7 @@ const QEditor = ({
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
[
buttonLinkData
,
setButtonLinkData
]
=
useState
(
defaultButtonLinkData
)
let
formRef
=
useRef
(
null
);
let
formRef
=
useRef
(
null
);
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-unused-vars
...
@@ -167,6 +198,11 @@ const QEditor = ({
...
@@ -167,6 +198,11 @@ const QEditor = ({
}
}
const
modalOpener
=
(
type
,
title
)
=>
{
const
modalOpener
=
(
type
,
title
)
=>
{
if
(
type
===
'
buttonLink
'
)
{
const
selectedButtonLinkData
=
getSelectedButtonLinkData
()
setButtonLinkData
(
selectedButtonLinkData
||
defaultButtonLinkData
)
}
setModalTitle
(
title
)
setModalTitle
(
title
)
setInnerModalType
(
type
)
setInnerModalType
(
type
)
setModalIsOpen
(
true
)
setModalIsOpen
(
true
)
...
@@ -203,12 +239,42 @@ const QEditor = ({
...
@@ -203,12 +239,42 @@ const QEditor = ({
'
#ffe672
'
'
#ffe672
'
]
]
}
}
const
fontSizes
=
[
'
default
'
,
'
12px
'
,
'
14px
'
,
'
16px
'
,
'
18px
'
,
'
20px
'
,
'
24px
'
,
'
28px
'
,
'
32px
'
]
const
fontSizes
=
[
'
12px
'
,
'
14px
'
,
'
16px
'
,
'
18px
'
,
'
20px
'
,
'
24px
'
,
'
28px
'
,
'
32px
'
]
const
validateLinkHref
=
(
href
)
=>
{
if
(
!
href
||
typeof
href
!==
'
string
'
)
{
return
false
}
try
{
const
normalizedHref
=
/^
[
a-zA-Z
][
a-zA-Z
\d
+
\-
.
]
*:/
.
test
(
href
)
?
href
:
`https://
${
href
}
`
const
parsedUrl
=
new
URL
(
normalizedHref
)
return
[
'
http:
'
,
'
https:
'
].
includes
(
parsedUrl
.
protocol
)
}
catch
(
error
)
{
return
false
}
}
const
normalizeLinkHref
=
(
href
)
=>
{
if
(
!
href
||
typeof
href
!==
'
string
'
)
{
return
''
}
return
/^
[
a-zA-Z
][
a-zA-Z
\d
+
\-
.
]
*:/
.
test
(
href
)
?
href
:
`https://
${
href
}
`
}
const
toolsLib
=
{
const
toolsLib
=
{
link
:
{
link
:
{
title
:
'
Вставить ссылку
'
,
title
:
'
Вставить ссылку
'
,
onClick
:
()
=>
{
onClick
:
()
=>
{
const
selection
=
{
from
:
editor
.
state
.
selection
.
from
,
to
:
editor
.
state
.
selection
.
to
,
empty
:
editor
.
state
.
selection
.
empty
}
const
previousUrl
=
editor
.
getAttributes
(
'
link
'
).
href
const
previousUrl
=
editor
.
getAttributes
(
'
link
'
).
href
const
url
=
window
.
prompt
(
'
Введите URL
'
,
previousUrl
)
const
url
=
window
.
prompt
(
'
Введите URL
'
,
previousUrl
)
...
@@ -219,14 +285,41 @@ const QEditor = ({
...
@@ -219,14 +285,41 @@ const QEditor = ({
// empty
// empty
if
(
url
===
''
)
{
if
(
url
===
''
)
{
editor
.
chain
().
focus
().
extendMarkRange
(
'
link
'
).
unsetLink
().
run
()
editor
.
chain
().
focus
().
setTextSelection
({
from
:
selection
.
from
,
to
:
selection
.
to
}).
extendMarkRange
(
'
link
'
).
unsetLink
().
run
()
return
return
}
}
// update link
const
normalizedUrl
=
normalizeLinkHref
(
url
)
editor
.
chain
().
focus
().
extendMarkRange
(
'
link
'
).
setLink
({
href
:
url
,
target
:
'
_blank
'
}).
run
()
if
(
!
validateLinkHref
(
normalizedUrl
))
{
window
.
alert
(
'
Некорректный URL
'
)
return
}
if
(
selection
.
empty
)
{
editor
.
chain
().
focus
().
setTextSelection
(
selection
.
from
).
insertContent
({
type
:
'
text
'
,
text
:
url
,
marks
:
[
{
type
:
'
link
'
,
attrs
:
{
href
:
normalizedUrl
,
target
:
'
_blank
'
}
}
]
}).
run
()
return
}
editor
.
chain
().
focus
().
setTextSelection
({
from
:
selection
.
from
,
to
:
selection
.
to
}).
extendMarkRange
(
'
link
'
).
setLink
({
href
:
normalizedUrl
,
target
:
'
_blank
'
}).
run
()
}
}
},
},
buttonLink
:
{
title
:
'
Кнопка-ссылка
'
,
onClick
:
()
=>
modalOpener
(
'
buttonLink
'
,
'
Добавить кнопку
'
)
},
file
:
{
file
:
{
title
:
'
Прикрепить файл
'
,
title
:
'
Прикрепить файл
'
,
onClick
:
()
=>
modalOpener
(
'
file
'
,
'
Прикрепить файл
'
)
onClick
:
()
=>
modalOpener
(
'
file
'
,
'
Прикрепить файл
'
)
...
@@ -539,7 +632,7 @@ const QEditor = ({
...
@@ -539,7 +632,7 @@ const QEditor = ({
linkOnPaste
:
true
,
linkOnPaste
:
true
,
defaultProtocol
:
'
https
'
,
defaultProtocol
:
'
https
'
,
protocols
:
[
'
http
'
,
'
https
'
],
protocols
:
[
'
http
'
,
'
https
'
],
validate
:
(
href
)
=>
console
.
log
(
h
ref
)
,
validate
:
validateLinkH
ref
,
}),
}),
Video
,
Video
,
Iframe
,
Iframe
,
...
@@ -565,6 +658,7 @@ const QEditor = ({
...
@@ -565,6 +658,7 @@ const QEditor = ({
}),
}),
TextStyle
,
TextStyle
,
FontSize
,
FontSize
,
ButtonLinkExtension
,
Color
.
configure
({
Color
.
configure
({
types
:
[
'
textStyle
'
]
types
:
[
'
textStyle
'
]
}),
}),
...
@@ -769,6 +863,100 @@ const QEditor = ({
...
@@ -769,6 +863,100 @@ const QEditor = ({
{
getUploader
({
accept
:
'
*
'
,
afterParams
:
[
'
no_convert=1
'
]})
}
{
getUploader
({
accept
:
'
*
'
,
afterParams
:
[
'
no_convert=1
'
]})
}
</
Fragment
>
</
Fragment
>
)
)
case
'
buttonLink
'
:
return
(
<
div
className
=
'atma-editor-button-link-form'
>
<
label
className
=
'atma-editor-field'
>
<
span
>
Текст кнопки
</
span
>
<
Input
value
=
{
buttonLinkData
.
text
}
placeholder
=
'Перейти'
onChange
=
{
(
event
)
=>
{
setButtonLinkData
({
...
buttonLinkData
,
text
:
event
.
target
.
value
})
}
}
/>
</
label
>
<
label
className
=
'atma-editor-field'
>
<
span
>
Ссылка
</
span
>
<
Input
value
=
{
buttonLinkData
.
href
}
placeholder
=
'https://example.com'
onChange
=
{
(
event
)
=>
{
setButtonLinkData
({
...
buttonLinkData
,
href
:
event
.
target
.
value
})
}
}
/>
</
label
>
<
div
className
=
'atma-editor-button-link-grid'
>
<
label
className
=
'atma-editor-field atma-editor-field-select'
>
<
span
>
Размер шрифта
</
span
>
<
AntdSelect
value
=
{
buttonLinkData
.
fontSize
}
options
=
{
fontSizes
.
map
((
fontSize
)
=>
({
value
:
fontSize
,
label
:
fontSize
}))
}
getPopupContainer
=
{
(
triggerNode
)
=>
triggerNode
.
parentNode
}
dropdownStyle
=
{
{
zIndex
:
100003
}
}
onChange
=
{
(
value
)
=>
{
setButtonLinkData
({
...
buttonLinkData
,
fontSize
:
value
})
}
}
/>
</
label
>
<
label
className
=
'atma-editor-field atma-editor-field-color'
>
<
span
>
Цвет текста
</
span
>
<
input
type
=
'color'
value
=
{
buttonLinkData
.
textColor
}
className
=
'atma-editor-color-input'
onChange
=
{
(
event
)
=>
{
setButtonLinkData
({
...
buttonLinkData
,
textColor
:
event
.
target
.
value
})
}
}
/>
</
label
>
<
label
className
=
'atma-editor-field atma-editor-field-color'
>
<
span
>
Цвет кнопки
</
span
>
<
input
type
=
'color'
value
=
{
buttonLinkData
.
backgroundColor
}
className
=
'atma-editor-color-input'
onChange
=
{
(
event
)
=>
{
setButtonLinkData
({
...
buttonLinkData
,
backgroundColor
:
event
.
target
.
value
})
}
}
/>
</
label
>
</
div
>
<
div
className
=
'atma-editor-button-link-preview-wrap'
>
<
span
>
Предпросмотр
</
span
>
<
a
href
=
'/'
onClick
=
{
(
event
)
=>
event
.
preventDefault
()
}
className
=
'atma-editor-button-link-preview'
style
=
{
{
color
:
buttonLinkData
.
textColor
,
backgroundColor
:
buttonLinkData
.
backgroundColor
,
fontSize
:
buttonLinkData
.
fontSize
||
'
16px
'
}
}
>
{
buttonLinkData
.
text
||
'
Кнопка
'
}
</
a
>
</
div
>
</
div
>
)
case
'
voicemessage
'
:
case
'
voicemessage
'
:
return
(
return
(
<
Fragment
>
<
Fragment
>
...
@@ -1063,6 +1251,9 @@ const QEditor = ({
...
@@ -1063,6 +1251,9 @@ const QEditor = ({
)
)
isDisabled
=
!
regex
.
test
(
embedContent
)
isDisabled
=
!
regex
.
test
(
embedContent
)
break
break
case
'
buttonLink
'
:
isDisabled
=
!
buttonLinkData
.
text
.
trim
()
||
!
validateLinkHref
(
buttonLinkData
.
href
)
break
}
}
return
isDisabled
return
isDisabled
...
@@ -1084,6 +1275,7 @@ const QEditor = ({
...
@@ -1084,6 +1275,7 @@ const QEditor = ({
clearBlobUrl
()
clearBlobUrl
()
setUploaderUid
(
`uid
${
new
Date
()}
`
)
setUploaderUid
(
`uid
${
new
Date
()}
`
)
setUploadedPaths
([])
setUploadedPaths
([])
setButtonLinkData
(
defaultButtonLinkData
)
setModalIsOpen
(
false
)
setModalIsOpen
(
false
)
}
}
},
},
...
@@ -1096,6 +1288,7 @@ const QEditor = ({
...
@@ -1096,6 +1288,7 @@ const QEditor = ({
clearBlobUrl
()
clearBlobUrl
()
setUploaderUid
(
`uid
${
new
Date
()}
`
)
setUploaderUid
(
`uid
${
new
Date
()}
`
)
setUploadedPaths
([])
setUploadedPaths
([])
setButtonLinkData
(
defaultButtonLinkData
)
setModalIsOpen
(
false
)
setModalIsOpen
(
false
)
}
}
}
}
...
@@ -1317,6 +1510,26 @@ const QEditor = ({
...
@@ -1317,6 +1510,26 @@ const QEditor = ({
).
run
()
).
run
()
})
})
break
break
case
'
buttonLink
'
:
{
const
buttonLinkAttrs
=
{
text
:
buttonLinkData
.
text
.
trim
(),
href
:
normalizeLinkHref
(
buttonLinkData
.
href
),
fontSize
:
buttonLinkData
.
fontSize
.
trim
()
||
'
16px
'
,
textColor
:
buttonLinkData
.
textColor
,
backgroundColor
:
buttonLinkData
.
backgroundColor
}
if
(
editor
.
state
.
selection
instanceof
NodeSelection
&&
editor
.
state
.
selection
.
node
&&
editor
.
state
.
selection
.
node
.
type
.
name
===
'
buttonLink
'
)
{
editor
.
chain
().
focus
().
updateAttributes
(
'
buttonLink
'
,
buttonLinkAttrs
).
run
()
}
else
{
editor
.
chain
().
focus
().
setButtonLink
(
buttonLinkAttrs
).
insertContent
(
'
'
).
run
()
}
}
break
}
}
setModalIsOpen
(
false
)
setModalIsOpen
(
false
)
clearBlobUrl
()
clearBlobUrl
()
...
@@ -1324,6 +1537,7 @@ const QEditor = ({
...
@@ -1324,6 +1537,7 @@ const QEditor = ({
setEmbedContent
(
''
)
setEmbedContent
(
''
)
setUploadedPaths
([])
setUploadedPaths
([])
setModalTitle
(
''
)
setModalTitle
(
''
)
setButtonLinkData
(
defaultButtonLinkData
)
}
catch
(
err
)
{
}
catch
(
err
)
{
console
.
log
(
err
)
console
.
log
(
err
)
setModalIsOpen
(
false
)
setModalIsOpen
(
false
)
...
@@ -1332,6 +1546,7 @@ const QEditor = ({
...
@@ -1332,6 +1546,7 @@ const QEditor = ({
setEmbedContent
(
''
)
setEmbedContent
(
''
)
setUploadedPaths
([])
setUploadedPaths
([])
setModalTitle
(
''
)
setModalTitle
(
''
)
setButtonLinkData
(
defaultButtonLinkData
)
}
}
}
}
},
},
...
@@ -1348,6 +1563,14 @@ const QEditor = ({
...
@@ -1348,6 +1563,14 @@ const QEditor = ({
typpyOptions
=
{
{
followCursor
:
true
}
}
typpyOptions
=
{
{
followCursor
:
true
}
}
editor
=
{
editor
}
editor
=
{
editor
}
shouldShow
=
{
({...
o
})
=>
{
shouldShow
=
{
({...
o
})
=>
{
if
(
o
.
state
.
selection
instanceof
NodeSelection
)
{
const
selectedNode
=
o
.
state
.
selection
.
node
if
(
selectedNode
&&
selectedNode
.
type
&&
selectedNode
.
type
.
name
===
'
buttonLink
'
)
{
return
false
}
}
let
items
=
[]
let
items
=
[]
if
(
if
(
o
.
from
!==
o
.
to
&&
o
.
from
!==
o
.
to
&&
...
...
src/components/ToolBar.js
View file @
78d6243d
...
@@ -44,6 +44,7 @@ const toolsInit = {
...
@@ -44,6 +44,7 @@ const toolsInit = {
items
:
[
items
:
[
'
emoji
'
,
'
emoji
'
,
'
link
'
,
'
link
'
,
'
buttonLink
'
,
'
file
'
,
'
file
'
,
'
image
'
,
'
image
'
,
'
interactiveImage
'
,
'
interactiveImage
'
,
...
...
src/extensions/ButtonLink.js
0 → 100644
View file @
78d6243d
import
{
Node
}
from
'
@tiptap/core
'
import
{
Plugin
}
from
'
prosemirror-state
'
import
{
NodeSelection
}
from
'
prosemirror-state
'
const
ButtonLink
=
Node
.
create
({
name
:
'
buttonLink
'
,
group
:
'
inline
'
,
inline
:
true
,
atom
:
true
,
selectable
:
true
,
addAttributes
()
{
return
{
text
:
{
default
:
'
Кнопка
'
},
href
:
{
default
:
''
},
target
:
{
default
:
'
_blank
'
},
textColor
:
{
default
:
'
#ffffff
'
},
backgroundColor
:
{
default
:
'
#1790FF
'
},
fontSize
:
{
default
:
'
16px
'
}
}
},
parseHTML
()
{
return
[
{
tag
:
'
a[data-button-link="true"]
'
,
getAttrs
:
(
element
)
=>
({
text
:
element
.
textContent
||
'
Кнопка
'
,
href
:
element
.
getAttribute
(
'
href
'
)
||
''
,
target
:
element
.
getAttribute
(
'
target
'
)
||
'
_blank
'
,
textColor
:
element
.
getAttribute
(
'
data-text-color
'
)
||
'
#ffffff
'
,
backgroundColor
:
element
.
getAttribute
(
'
data-background-color
'
)
||
'
#1790FF
'
,
fontSize
:
element
.
getAttribute
(
'
data-font-size
'
)
||
'
16px
'
})
}
]
},
renderHTML
({
HTMLAttributes
})
{
const
{
text
,
href
,
target
,
textColor
,
backgroundColor
,
fontSize
}
=
HTMLAttributes
return
[
'
a
'
,
{
href
,
target
,
rel
:
'
noopener noreferrer nofollow
'
,
'
data-button-link
'
:
'
true
'
,
'
data-text-color
'
:
textColor
,
'
data-background-color
'
:
backgroundColor
,
'
data-font-size
'
:
fontSize
,
style
:
[
'
display: inline-block
'
,
'
padding: 10px 18px
'
,
'
border-radius: 10px
'
,
'
text-decoration: none
'
,
`font-size:
${
fontSize
}
`
,
`color:
${
textColor
}
`
,
`background-color:
${
backgroundColor
}
`
].
join
(
'
;
'
)
},
text
]
},
addCommands
()
{
return
{
setButtonLink
:
(
attrs
)
=>
({
commands
})
=>
{
return
commands
.
insertContent
({
type
:
this
.
name
,
attrs
})
}
}
},
addProseMirrorPlugins
()
{
return
[
new
Plugin
({
props
:
{
handleDOMEvents
:
{
click
:
(
view
,
event
)
=>
{
const
target
=
event
.
target
instanceof
HTMLElement
?
event
.
target
.
closest
(
'
a[data-button-link="true"]
'
)
:
null
if
(
!
target
||
!
view
.
dom
.
contains
(
target
))
{
return
false
}
event
.
preventDefault
()
return
true
},
touchstart
:
(
view
,
event
)
=>
{
const
target
=
event
.
target
instanceof
HTMLElement
?
event
.
target
.
closest
(
'
a[data-button-link="true"]
'
)
:
null
if
(
!
target
||
!
view
.
dom
.
contains
(
target
))
{
return
false
}
event
.
preventDefault
()
return
true
},
mousedown
:
(
view
,
event
)
=>
{
const
target
=
event
.
target
instanceof
HTMLElement
?
event
.
target
.
closest
(
'
a[data-button-link="true"]
'
)
:
null
if
(
!
target
||
!
view
.
dom
.
contains
(
target
))
{
return
false
}
const
position
=
view
.
posAtDOM
(
target
,
0
)
const
node
=
view
.
state
.
doc
.
nodeAt
(
position
)
if
(
!
node
||
node
.
type
.
name
!==
this
.
name
)
{
return
false
}
event
.
preventDefault
()
view
.
dispatch
(
view
.
state
.
tr
.
setSelection
(
NodeSelection
.
create
(
view
.
state
.
doc
,
position
)
)
)
view
.
focus
()
return
true
}
},
handleClickOn
:
(
view
,
pos
,
node
,
nodePos
,
event
,
direct
)
=>
{
if
(
!
direct
||
node
.
type
.
name
!==
this
.
name
)
{
return
false
}
event
.
preventDefault
()
const
transaction
=
view
.
state
.
tr
.
setSelection
(
NodeSelection
.
create
(
view
.
state
.
doc
,
nodePos
)
)
view
.
dispatch
(
transaction
)
view
.
focus
()
return
true
}
}
})
]
}
})
export
default
ButtonLink
src/index.scss
View file @
78d6243d
...
@@ -529,6 +529,15 @@ body{
...
@@ -529,6 +529,15 @@ body{
}
}
}
}
a[data-button-link="true"]{
cursor: pointer;
}
.ProseMirror-selectednode[data-button-link="true"]{
outline: 2px solid #1790FF;
outline-offset: 2px;
}
}
}
&-bubble{
&-bubble{
...
@@ -637,6 +646,68 @@ body{
...
@@ -637,6 +646,68 @@ body{
}
}
}
}
&-field{
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
span{
font-size: 13px;
color: #4f4f4f;
}
&-color{
align-items: flex-start;
}
}
&-color-input{
width: 72px;
min-width: 72px;
height: 36px;
border: 1px solid #d9d9d9;
border-radius: 8px;
background: #fff;
padding: 3px;
}
&-button-link{
&-form{
min-width: 420px;
}
&-grid{
display: grid;
grid-template-columns: minmax(0, 1.3fr) repeat(2, auto);
gap: 16px;
align-items: start;
}
&-preview-wrap{
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 8px;
span{
font-size: 13px;
color: #4f4f4f;
}
}
&-preview{
display: inline-flex;
align-items: center;
justify-content: center;
align-self: flex-start;
min-height: 42px;
border-radius: 10px;
padding: 10px 18px;
text-decoration: none;
}
}
&-uploader{
&-uploader{
&-drop{
&-drop{
display: flex;
display: flex;
...
@@ -913,6 +984,9 @@ body{
...
@@ -913,6 +984,9 @@ body{
.qicon{
.qicon{
&.qbuttonLink{
background-image: url("data:image/svg+xml,%3Csvg width='
18
' height='
18
' viewBox='
0
0
18
18
' fill='
none
' xmlns='
http
:
//
www
.w3.org
/
2000
/
svg
'%3E%3Crect x='
2
.25
' y='
4
' width='
13
.5
' height='
10
' rx='
2
.6
' stroke='
%231D1D1F
' stroke-width='
1
.4
'/%3E%3Cpath d='
M5
.2
9H8
.8
' stroke='
%231D1D1F
' stroke-width='
1
.4
' stroke-linecap='
round
'/%3E%3Cpath d='
M10
.3
7
.4H11.7C12.42
7
.4
13
7
.98
13
8
.7C13
9
.42
12
.42
10
11
.7
10H10
.3
' stroke='
%231D1D1F
' stroke-width='
1
.2
' stroke-linecap='
round
'/%3E%3Cpath d='
M9
.8
10
.9L11.1
9
.6
' stroke='
%231D1D1F
' stroke-width='
1
.2
' stroke-linecap='
round
'/%3E%3C/svg%3E");
}
&.qfontSize{
&.qfontSize{
background-image: url("data:image/svg+xml,%3Csvg width='
18
' height='
18
' viewBox='
0
0
18
18
' fill='
none
' xmlns='
http
:
//
www
.w3.org
/
2000
/
svg
'%3E%3Cpath d='
M4
.2
13
.8L6.84
5
.4H7.98L10.62
13
.8H9.51L8.88
11
.76H5.91L5.28
13
.8H4.2ZM6.15
10
.86H8.64L7.41
6
.84L6.15
10
.86ZM11.58
13
.8V8.43H12.6V9.12C13.02
8
.58
13
.59
8
.31
14
.34
8
.31C14.73
8
.31
15
.09
8
.4
15
.39
8
.58C15.69
8
.76
15
.93
9
.03
16
.08
9
.39C16.23
9
.75
16
.32
10
.17
16
.32
10
.65C16.32
11
.16
16
.23
11
.61
16
.05
12C15
.87
12
.39
15
.6
12
.69
15
.24
12
.9C14.88
13
.11
14
.49
13
.2
14
.07
13
.2C13.47
13
.2
12
.99
12
.99
12
.66
12
.6V13.8H11.58ZM12.6
10
.74C12.6
11
.37
12
.72
11
.82
12
.96
12
.12C13.2
12
.42
13
.5
12
.57
13
.89
12
.57C14.28
12
.57
14
.61
12
.42
14
.88
12
.09C15.15
11
.76
15
.27
11
.28
15
.27
10
.62C15.27
10
.02
15
.15
9
.57
14
.91
9
.27C14.67
8
.97
14
.37
8
.82
14
.01
8
.82C13.65
8
.82
13
.32
8
.97
13
.05
9
.3C12.75
9
.6
12
.6
10
.08
12
.6
10
.74Z
' fill='
%231D1D1F
'/%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg width='
18
' height='
18
' viewBox='
0
0
18
18
' fill='
none
' xmlns='
http
:
//
www
.w3.org
/
2000
/
svg
'%3E%3Cpath d='
M4
.2
13
.8L6.84
5
.4H7.98L10.62
13
.8H9.51L8.88
11
.76H5.91L5.28
13
.8H4.2ZM6.15
10
.86H8.64L7.41
6
.84L6.15
10
.86ZM11.58
13
.8V8.43H12.6V9.12C13.02
8
.58
13
.59
8
.31
14
.34
8
.31C14.73
8
.31
15
.09
8
.4
15
.39
8
.58C15.69
8
.76
15
.93
9
.03
16
.08
9
.39C16.23
9
.75
16
.32
10
.17
16
.32
10
.65C16.32
11
.16
16
.23
11
.61
16
.05
12C15
.87
12
.39
15
.6
12
.69
15
.24
12
.9C14.88
13
.11
14
.49
13
.2
14
.07
13
.2C13.47
13
.2
12
.99
12
.99
12
.66
12
.6V13.8H11.58ZM12.6
10
.74C12.6
11
.37
12
.72
11
.82
12
.96
12
.12C13.2
12
.42
13
.5
12
.57
13
.89
12
.57C14.28
12
.57
14
.61
12
.42
14
.88
12
.09C15.15
11
.76
15
.27
11
.28
15
.27
10
.62C15.27
10
.02
15
.15
9
.57
14
.91
9
.27C14.67
8
.97
14
.37
8
.82
14
.01
8
.82C13.65
8
.82
13
.32
8
.97
13
.05
9
.3C12.75
9
.6
12
.6
10
.08
12
.6
10
.74Z
' fill='
%231D1D1F
'/%3E%3C/svg%3E");
}
}
...
@@ -1409,8 +1483,21 @@ body{
...
@@ -1409,8 +1483,21 @@ body{
color: #8c8c8c;
color: #8c8c8c;
font-size: 13px;
font-size: 13px;
}
}
.atma-editor-field-select {
width: 110px
}
@media (max-width: 768px) {
@media (max-width: 768px) {
.atma-editor-button-link-form{
min-width: 0;
}
.atma-editor-button-link-grid{
grid-template-columns: 1fr 1fr;
}
.atma-editor-field-color{
align-items: flex-start;
}
.atma-editor-toolbar{
.atma-editor-toolbar{
width: 100%;
width: 100%;
max-width: 100%;
max-width: 100%;
...
...
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