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
9ed36052
Commit
9ed36052
authored
Jun 27, 2025
by
Яков
Browse files
выравнивание картинки, обтекание текста, ресайз
parent
2b0a2301
Changes
2
Hide whitespace changes
Inline
Side-by-side
package.json
View file @
9ed36052
{
{
"name"
:
"react-ag-qeditor"
,
"name"
:
"react-ag-qeditor"
,
"version"
:
"1.0.8
5
"
,
"version"
:
"1.0.8
6
"
,
"description"
:
"WYSIWYG html editor"
,
"description"
:
"WYSIWYG html editor"
,
"author"
:
"atma"
,
"author"
:
"atma"
,
"license"
:
"
MIT
"
,
"license"
:
"
MIT
"
,
...
...
src/extensions/Image.jsx
View file @
9ed36052
...
@@ -20,6 +20,7 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -20,6 +20,7 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
aspectRatio
:
1
aspectRatio
:
1
});
});
// Добавляем прозрачный нулевой пробел после изображения
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
editor
||
!
getPos
)
return
;
if
(
!
editor
||
!
getPos
)
return
;
...
@@ -29,21 +30,16 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -29,21 +30,16 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
if
(
doc
.
nodeSize
>
pos
&&
doc
.
nodeAt
(
pos
)?.
textContent
!==
'
\
u200B
'
)
{
if
(
doc
.
nodeSize
>
pos
&&
doc
.
nodeAt
(
pos
)?.
textContent
!==
'
\
u200B
'
)
{
editor
.
commands
.
insertContentAt
(
pos
,
{
editor
.
commands
.
insertContentAt
(
pos
,
{
type
:
'
text
'
,
type
:
'
text
'
,
text
:
'
\
u200B
'
text
:
'
\
u200B
'
// Невидимый нулевой пробел
});
});
}
}
},
[
editor
,
getPos
]);
},
[
editor
,
getPos
]);
// Инициализация размеров
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
imgRef
.
current
&&
!
isInitialized
.
current
)
{
if
(
imgRef
.
current
&&
!
isInitialized
.
current
)
{
if
(
node
.
attrs
.
width
&&
node
.
attrs
.
height
)
{
const
width
=
node
.
attrs
.
width
||
imgRef
.
current
.
naturalWidth
;
isInitialized
.
current
=
true
;
const
height
=
node
.
attrs
.
height
||
imgRef
.
current
.
naturalHeight
;
return
;
}
const
width
=
imgRef
.
current
.
naturalWidth
;
const
height
=
imgRef
.
current
.
naturalHeight
;
updateAttributes
({
updateAttributes
({
width
:
Math
.
round
(
width
),
width
:
Math
.
round
(
width
),
height
:
Math
.
round
(
height
)
height
:
Math
.
round
(
height
)
...
@@ -56,8 +52,8 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -56,8 +52,8 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
e
.
preventDefault
();
e
.
preventDefault
();
e
.
stopPropagation
();
e
.
stopPropagation
();
const
currentWidth
=
node
.
attrs
.
width
;
const
currentWidth
=
node
.
attrs
.
width
||
imgRef
.
current
.
naturalWidth
;
const
currentHeight
=
node
.
attrs
.
height
;
const
currentHeight
=
node
.
attrs
.
height
||
imgRef
.
current
.
naturalHeight
;
resizeData
.
current
=
{
resizeData
.
current
=
{
startWidth
:
currentWidth
,
startWidth
:
currentWidth
,
...
@@ -77,16 +73,20 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -77,16 +73,20 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
let
newWidth
,
newHeight
;
let
newWidth
,
newHeight
;
if
(
node
.
attrs
.
align
===
'
center
'
)
{
if
(
node
.
attrs
.
align
===
'
center
'
)
{
// Особый случай для центрированного изображения
if
(
direction
.
includes
(
'
n
'
)
||
direction
.
includes
(
'
s
'
))
{
if
(
direction
.
includes
(
'
n
'
)
||
direction
.
includes
(
'
s
'
))
{
// Только вертикальный ресайз с сохранением пропорций
const
scale
=
direction
.
includes
(
'
s
'
)
?
1
:
-
1
;
const
scale
=
direction
.
includes
(
'
s
'
)
?
1
:
-
1
;
newHeight
=
Math
.
max
(
startHeight
+
deltaY
*
scale
,
MIN_WIDTH
);
newHeight
=
Math
.
max
(
startHeight
+
deltaY
*
scale
,
MIN_WIDTH
);
newWidth
=
Math
.
round
(
newHeight
*
aspectRatio
);
newWidth
=
Math
.
round
(
newHeight
*
aspectRatio
);
}
else
{
}
else
{
// Горизонтальный ресайз с сохранением пропорций
const
scale
=
direction
.
includes
(
'
e
'
)
?
1
:
-
1
;
const
scale
=
direction
.
includes
(
'
e
'
)
?
1
:
-
1
;
newWidth
=
Math
.
max
(
startWidth
+
deltaX
*
scale
,
MIN_WIDTH
);
newWidth
=
Math
.
max
(
startWidth
+
deltaX
*
scale
,
MIN_WIDTH
);
newHeight
=
Math
.
round
(
newWidth
/
aspectRatio
);
newHeight
=
Math
.
round
(
newWidth
/
aspectRatio
);
}
}
}
else
{
}
else
{
// Обычный ресайз для других выравниваний
if
(
direction
.
includes
(
'
e
'
)
||
direction
.
includes
(
'
w
'
))
{
if
(
direction
.
includes
(
'
e
'
)
||
direction
.
includes
(
'
w
'
))
{
const
scale
=
direction
.
includes
(
'
e
'
)
?
1
:
-
1
;
const
scale
=
direction
.
includes
(
'
e
'
)
?
1
:
-
1
;
newWidth
=
Math
.
max
(
startWidth
+
deltaX
*
scale
,
MIN_WIDTH
);
newWidth
=
Math
.
max
(
startWidth
+
deltaX
*
scale
,
MIN_WIDTH
);
...
@@ -123,6 +123,8 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -123,6 +123,8 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
const
getWrapperStyle
=
()
=>
{
const
getWrapperStyle
=
()
=>
{
const
baseStyle
=
{
const
baseStyle
=
{
display
:
'
inline-block
'
,
display
:
'
inline-block
'
,
lineHeight
:
0
,
margin
:
'
0.5rem 0
'
,
position
:
'
relative
'
,
position
:
'
relative
'
,
outline
:
editing
?
`1px dashed
${
BORDER_COLOR
}
`
:
'
none
'
,
outline
:
editing
?
`1px dashed
${
BORDER_COLOR
}
`
:
'
none
'
,
verticalAlign
:
'
top
'
,
verticalAlign
:
'
top
'
,
...
@@ -131,33 +133,39 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -131,33 +133,39 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
switch
(
node
.
attrs
.
align
)
{
switch
(
node
.
attrs
.
align
)
{
case
'
left
'
:
case
'
left
'
:
return
{
return
{
...
baseStyle
,
float
:
'
left
'
,
marginRight
:
'
1rem
'
};
...
baseStyle
,
float
:
'
left
'
,
margin
:
'
0 1rem 1rem 0
'
};
case
'
right
'
:
case
'
right
'
:
return
{
return
{
...
baseStyle
,
float
:
'
right
'
,
marginLeft
:
'
1rem
'
};
...
baseStyle
,
float
:
'
right
'
,
margin
:
'
0 0 1rem 1rem
'
};
case
'
center
'
:
case
'
center
'
:
return
{
return
{
...
baseStyle
,
...
baseStyle
,
display
:
'
block
'
,
display
:
'
block
'
,
margin
:
'
0
auto
'
,
margin
Left
:
'
auto
'
,
float
:
'
none
'
,
marginRight
:
'
auto
'
,
textAlign
:
'
center
'
textAlign
:
'
center
'
};
};
case
'
wrap-left
'
:
return
{
...
baseStyle
,
float
:
'
left
'
,
margin
:
'
0 1rem 1rem 0
'
,
shapeOutside
:
'
margin-box
'
};
case
'
wrap-right
'
:
return
{
...
baseStyle
,
float
:
'
right
'
,
margin
:
'
0 0 1rem 1rem
'
,
shapeOutside
:
'
margin-box
'
};
default
:
default
:
return
baseStyle
;
return
baseStyle
;
}
}
};
};
const
getImageStyle
=
()
=>
({
width
:
node
.
attrs
.
width
?
`
${
node
.
attrs
.
width
}
px`
:
'
auto
'
,
height
:
node
.
attrs
.
height
?
`
${
node
.
attrs
.
height
}
px`
:
'
auto
'
,
maxWidth
:
'
100%
'
,
display
:
'
block
'
,
cursor
:
'
default
'
,
userSelect
:
'
none
'
,
margin
:
node
.
attrs
.
align
===
'
center
'
?
'
0 auto
'
:
'
0
'
});
return
(
return
(
<
NodeViewWrapper
<
NodeViewWrapper
as
=
"
span
"
as
=
"
div
"
style
=
{
getWrapperStyle
()
}
style
=
{
getWrapperStyle
()
}
ref
=
{
wrapperRef
}
ref
=
{
wrapperRef
}
onClick
=
{
(
e
)
=>
{
onClick
=
{
(
e
)
=>
{
...
@@ -170,18 +178,9 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
...
@@ -170,18 +178,9 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
<
img
<
img
{
...
node
.
attrs
}
{
...
node
.
attrs
}
ref
=
{
imgRef
}
ref
=
{
imgRef
}
style
=
{
{
style
=
{
getImageStyle
()
}
width
:
`
${
node
.
attrs
.
width
}
px`
,
height
:
`
${
node
.
attrs
.
height
}
px`
,
maxWidth
:
'
100%
'
,
display
:
'
block
'
,
margin
:
'
0 auto
'
,
// Центрируем изображение внутри wrapper'а
cursor
:
'
default
'
,
userSelect
:
'
none
'
}
}
onLoad
=
{
()
=>
{
onLoad
=
{
()
=>
{
if
(
imgRef
.
current
&&
!
isInitialized
.
current
&&
if
(
imgRef
.
current
&&
!
isInitialized
.
current
)
{
(
!
node
.
attrs
.
width
||
!
node
.
attrs
.
height
))
{
const
width
=
imgRef
.
current
.
naturalWidth
;
const
width
=
imgRef
.
current
.
naturalWidth
;
const
height
=
imgRef
.
current
.
naturalHeight
;
const
height
=
imgRef
.
current
.
naturalHeight
;
updateAttributes
({
updateAttributes
({
...
@@ -313,26 +312,27 @@ const ResizableImageExtension = TipTapImage.extend({
...
@@ -313,26 +312,27 @@ const ResizableImageExtension = TipTapImage.extend({
renderHTML
({
HTMLAttributes
})
{
renderHTML
({
HTMLAttributes
})
{
const
align
=
HTMLAttributes
.
align
||
'
left
'
;
const
align
=
HTMLAttributes
.
align
||
'
left
'
;
const
isWrap
=
[
'
wrap-left
'
,
'
wrap-right
'
].
includes
(
align
);
const
floatDirection
=
isWrap
?
align
.
split
(
'
-
'
)[
1
]
:
[
'
left
'
,
'
right
'
].
includes
(
align
)
?
align
:
'
none
'
;
return
[
'
span
'
,
{
return
[
'
span
'
,
{
'
data-type
'
:
'
resizable-image
'
,
'
data-type
'
:
'
resizable-image
'
,
'
data-image-wrapper
'
:
true
,
'
data-image-wrapper
'
:
true
,
style
:
`
style
:
`
display:
${
align
===
'
center
'
?
'
block
'
:
'
inline-block
'
}
;
display:
${
align
===
'
center
'
?
'
block
'
:
'
inline-block
'
}
;
float:
${
[
'
left
'
,
'
right
'
].
includes
(
align
)
?
align
:
'
none
'
}
;
float:
${
floatDirection
}
;
margin:
${
align
===
'
left
'
?
'
0 1rem 1rem 0
'
:
margin:
${
align
===
'
left
'
?
'
0 1rem 1rem 0
'
:
align
===
'
right
'
?
'
0 0 1rem 1rem
'
:
align
===
'
right
'
?
'
0 0 1rem 1rem
'
:
align
===
'
center
'
?
'
0 auto
'
:
'
0
'
}
;
align
===
'
wrap-left
'
?
'
0 1rem 1rem 0
'
:
text-align:
${
align
===
'
center
'
?
'
center
'
:
'
left
'
}
;
align
===
'
wrap-right
'
?
'
0 0 1rem 1rem
'
:
align
===
'
center
'
?
'
0.5rem auto
'
:
'
0
'
}
;
shape-outside:
${
isWrap
?
'
margin-box
'
:
'
none
'
}
;
vertical-align: top;
vertical-align: top;
position: relative;
position: relative;
z-index: 1;
z-index: 1;
`
,
`
,
'
data-align
'
:
align
'
data-align
'
:
align
},
[
'
img
'
,
{
},
[
'
img
'
,
HTMLAttributes
]];
...
HTMLAttributes
,
style
:
`width:
${
HTMLAttributes
.
width
}
px;height:
${
HTMLAttributes
.
height
}
px;max-width:100%;display:block;margin:0 auto;`
}]];
},
},
addNodeView
()
{
addNodeView
()
{
...
@@ -342,7 +342,7 @@ const ResizableImageExtension = TipTapImage.extend({
...
@@ -342,7 +342,7 @@ const ResizableImageExtension = TipTapImage.extend({
inline
:
true
,
inline
:
true
,
group
:
'
inline
'
,
group
:
'
inline
'
,
draggable
:
true
,
draggable
:
true
,
selectable
:
false
selectable
:
false
// Важно отключить выделение изображения
});
});
export
default
ResizableImageExtension
;
export
default
ResizableImageExtension
;
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