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
bbaa6d76
Commit
bbaa6d76
authored
Jul 04, 2025
by
Яков
Browse files
fix
parent
d232d707
Changes
2
Hide whitespace changes
Inline
Side-by-side
package.json
View file @
bbaa6d76
{
{
"name"
:
"react-ag-qeditor"
,
"name"
:
"react-ag-qeditor"
,
"version"
:
"1.0.9
7
"
,
"version"
:
"1.0.9
8
"
,
"description"
:
"WYSIWYG html editor"
,
"description"
:
"WYSIWYG html editor"
,
"author"
:
"atma"
,
"author"
:
"atma"
,
"license"
:
"
MIT
"
,
"license"
:
"
MIT
"
,
...
...
src/extensions/Image.jsx
View file @
bbaa6d76
...
@@ -13,21 +13,78 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -13,21 +13,78 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
const
isInitialized
=
useRef
(
false
);
const
isInitialized
=
useRef
(
false
);
const
[
isResizing
,
setIsResizing
]
=
useState
(
false
);
const
[
isResizing
,
setIsResizing
]
=
useState
(
false
);
// Получаем ширину редактора
для масштабирования изображений
// Получаем
текущую
ширину редактора
и доступное пространство
const
getEditor
Width
=
()
=>
{
const
getEditor
Dimensions
=
()
=>
{
const
editorElement
=
editor
?.
options
?.
element
?.
closest
(
'
.atma-editor-content
'
);
const
editorElement
=
editor
?.
options
?.
element
?.
closest
(
'
.atma-editor-content
'
);
return
editorElement
?
editorElement
.
clientWidth
:
null
;
if
(
!
editorElement
)
return
{
width
:
Infinity
,
availableSpace
:
Infinity
};
const
editorWidth
=
editorElement
.
clientWidth
;
const
imgElement
=
imgRef
.
current
;
let
availableSpace
=
editorWidth
;
if
(
imgElement
)
{
const
imgRect
=
imgElement
.
getBoundingClientRect
();
const
editorRect
=
editorElement
.
getBoundingClientRect
();
if
(
node
.
attrs
.
align
===
'
center
'
)
{
const
leftSpace
=
imgRect
.
left
-
editorRect
.
left
;
const
rightSpace
=
editorRect
.
right
-
imgRect
.
right
;
availableSpace
=
Math
.
min
(
editorWidth
,
(
leftSpace
+
rightSpace
+
imgRect
.
width
));
console
.
log
(
leftSpace
,
rightSpace
,
availableSpace
);
}
else
if
(
node
.
attrs
.
align
===
'
right
'
)
{
availableSpace
=
imgRect
.
left
-
editorRect
.
left
+
node
.
attrs
.
width
;
}
else
if
(
node
.
attrs
.
align
===
'
left
'
||
node
.
attrs
.
align
===
'
text
'
)
{
availableSpace
=
editorRect
.
right
-
imgRect
.
left
;
}
}
return
{
width
:
editorWidth
,
availableSpace
};
};
// Безопасное обновление атрибутов с учетом выравнивания и границ
const
safeUpdateAttributes
=
(
newAttrs
)
=>
{
const
{
width
:
editorWidth
,
availableSpace
}
=
getEditorDimensions
();
let
{
width
,
height
,
align
}
=
{
...
node
.
attrs
,
...
newAttrs
};
const
newAlign
=
newAttrs
.
align
||
align
;
// При изменении выравнивания проверяем доступное пространство
if
(
newAlign
&&
newAlign
!==
align
)
{
const
maxWidth
=
availableSpace
;
if
(
width
>
maxWidth
)
{
const
ratio
=
maxWidth
/
width
;
width
=
maxWidth
;
height
=
Math
.
round
(
height
*
ratio
);
}
}
else
{
// Для обычного обновления размеров
const
maxWidth
=
availableSpace
;
if
(
width
>
maxWidth
)
{
const
ratio
=
maxWidth
/
width
;
width
=
maxWidth
;
height
=
Math
.
round
(
height
*
ratio
);
}
}
// Проверяем минимальный размер
if
(
width
<
MIN_WIDTH
)
{
const
ratio
=
MIN_WIDTH
/
width
;
width
=
MIN_WIDTH
;
height
=
Math
.
round
(
height
*
ratio
);
}
updateAttributes
({
width
,
height
,
...
newAttrs
});
};
};
//
Генерация уникального ID при созда
ни
и
//
Инициализация изображе
ни
я
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
node
.
attrs
[
'
data-node-id
'
])
{
if
(
!
node
.
attrs
[
'
data-node-id
'
])
{
u
pdateAttributes
({
safeU
pdateAttributes
({
'
data-node-id
'
:
`img-
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)}
`
'
data-node-id
'
:
`img-
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)}
`
});
});
}
}
},
[
node
.
attrs
[
'
data-node-id
'
]
,
updateAttributes
]);
},
[
node
.
attrs
[
'
data-node-id
'
]]);
// Обработка кликов вне изображения
useEffect
(()
=>
{
useEffect
(()
=>
{
const
handleClickOutside
=
(
event
)
=>
{
const
handleClickOutside
=
(
event
)
=>
{
if
(
wrapperRef
.
current
&&
!
wrapperRef
.
current
.
contains
(
event
.
target
)
&&
selected
)
{
if
(
wrapperRef
.
current
&&
!
wrapperRef
.
current
.
contains
(
event
.
target
)
&&
selected
)
{
...
@@ -38,40 +95,22 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -38,40 +95,22 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
return
()
=>
document
.
removeEventListener
(
'
mousedown
'
,
handleClickOutside
);
return
()
=>
document
.
removeEventListener
(
'
mousedown
'
,
handleClickOutside
);
},
[
selected
,
editor
,
getPos
]);
},
[
selected
,
editor
,
getPos
]);
// Загрузка и инициализация изображения
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
imgRef
.
current
||
isInitialized
.
current
)
return
;
if
(
!
imgRef
.
current
||
isInitialized
.
current
)
return
;
const
initImageSize
=
()
=>
{
const
initImageSize
=
()
=>
{
try
{
try
{
const
editorWidth
=
getEditor
Width
();
const
{
width
:
editorWidth
}
=
getEditor
Dimensions
();
const
naturalWidth
=
imgRef
.
current
.
naturalWidth
;
const
naturalWidth
=
imgRef
.
current
.
naturalWidth
;
const
naturalHeight
=
imgRef
.
current
.
naturalHeight
;
const
naturalHeight
=
imgRef
.
current
.
naturalHeight
;
let
width
=
node
.
attrs
.
width
||
naturalWidth
;
safeUpdateAttributes
({
let
height
=
node
.
attrs
.
height
||
naturalHeight
;
width
:
naturalWidth
,
height
:
naturalHeight
,
// Масштабируем изображение, если оно шире редактора
'
data-node-id
'
:
node
.
attrs
[
'
data-node-id
'
]
||
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)
if
(
editorWidth
&&
width
>
editorWidth
)
{
});
const
ratio
=
editorWidth
/
width
;
isInitialized
.
current
=
true
;
width
=
editorWidth
;
height
=
Math
.
round
(
height
*
ratio
);
}
// Проверяем минимальный размер
if
(
width
<
MIN_WIDTH
)
{
const
ratio
=
MIN_WIDTH
/
width
;
width
=
MIN_WIDTH
;
height
=
Math
.
round
(
height
*
ratio
);
}
if
(
width
>
0
&&
height
>
0
)
{
updateAttributes
({
width
:
Math
.
round
(
width
),
height
:
Math
.
round
(
height
),
'
data-node-id
'
:
node
.
attrs
[
'
data-node-id
'
]
||
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)
});
isInitialized
.
current
=
true
;
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
warn
(
'
Error initializing image size:
'
,
error
);
console
.
warn
(
'
Error initializing image size:
'
,
error
);
}
}
...
@@ -86,8 +125,9 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -86,8 +125,9 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
return
()
=>
{
return
()
=>
{
if
(
imgRef
.
current
)
imgRef
.
current
.
onload
=
null
;
if
(
imgRef
.
current
)
imgRef
.
current
.
onload
=
null
;
};
};
},
[
node
.
attrs
.
width
,
node
.
attrs
.
height
,
updateAttributes
,
node
.
attrs
[
'
data-node-id
'
]]);
},
[
node
.
attrs
.
width
,
node
.
attrs
.
height
,
node
.
attrs
[
'
data-node-id
'
]]);
// Обработка ресайза изображения
const
handleResizeStart
=
(
direction
)
=>
(
e
)
=>
{
const
handleResizeStart
=
(
direction
)
=>
(
e
)
=>
{
e
.
preventDefault
();
e
.
preventDefault
();
e
.
stopPropagation
();
e
.
stopPropagation
();
...
@@ -100,36 +140,49 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -100,36 +140,49 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
const
aspectRatio
=
startWidth
/
startHeight
;
const
aspectRatio
=
startWidth
/
startHeight
;
const
startX
=
e
.
clientX
;
const
startX
=
e
.
clientX
;
const
startY
=
e
.
clientY
;
const
startY
=
e
.
clientY
;
const
{
width
:
editorWidth
,
availableSpace
}
=
getEditorDimensions
();
const
onMouseMove
=
(
e
)
=>
{
const
onMouseMove
=
(
e
)
=>
{
const
deltaX
=
e
.
clientX
-
startX
;
const
deltaX
=
e
.
clientX
-
startX
;
const
deltaY
=
e
.
clientY
-
startY
;
const
deltaY
=
e
.
clientY
-
startY
;
let
newWidth
,
newHeight
;
let
newWidth
,
newHeight
;
const
maxWidth
=
node
.
attrs
.
align
===
'
center
'
?
editorWidth
:
availableSpace
;
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
.
min
(
Math
.
round
(
newHeight
*
aspectRatio
),
maxWidth
);
newHeight
=
Math
.
round
(
newWidth
/
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
.
min
(
Math
.
max
(
startWidth
+
deltaX
*
scale
,
MIN_WIDTH
),
maxWidth
);
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
.
min
(
Math
.
max
(
startWidth
+
deltaX
*
scale
,
MIN_WIDTH
),
maxWidth
);
newHeight
=
Math
.
round
(
newWidth
/
aspectRatio
);
newHeight
=
Math
.
round
(
newWidth
/
aspectRatio
);
}
else
{
}
else
{
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
.
min
(
Math
.
round
(
newHeight
*
aspectRatio
),
maxWidth
);
newHeight
=
Math
.
round
(
newWidth
/
aspectRatio
);
}
}
}
}
u
pdateAttributes
({
width
:
newWidth
,
height
:
newHeight
});
safeU
pdateAttributes
({
width
:
newWidth
,
height
:
newHeight
});
};
};
const
onMouseUp
=
()
=>
{
const
onMouseUp
=
()
=>
{
...
@@ -144,12 +197,15 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -144,12 +197,15 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
window
.
addEventListener
(
'
mouseup
'
,
onMouseUp
);
window
.
addEventListener
(
'
mouseup
'
,
onMouseUp
);
};
};
// Изменение выравнивания с автоматическим масштабированием
const
handleAlign
=
(
align
)
=>
{
const
handleAlign
=
(
align
)
=>
{
updateAttributes
({
align
});
safeUpdateAttributes
({
align
});
safeUpdateAttributes
({
align
});
setShowAlignMenu
(
false
);
setShowAlignMenu
(
false
);
editor
.
commands
.
focus
();
editor
.
commands
.
focus
();
};
};
// Стили для обертки изображения
const
getWrapperStyle
=
()
=>
{
const
getWrapperStyle
=
()
=>
{
const
baseStyle
=
{
const
baseStyle
=
{
display
:
'
inline-block
'
,
display
:
'
inline-block
'
,
...
@@ -160,7 +216,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -160,7 +216,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
margin
:
'
0.5rem 0
'
,
margin
:
'
0.5rem 0
'
,
};
};
// Для выравнивания по центру
if
(
node
.
attrs
.
align
===
'
center
'
)
{
if
(
node
.
attrs
.
align
===
'
center
'
)
{
return
{
return
{
...
baseStyle
,
...
baseStyle
,
...
@@ -173,7 +228,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -173,7 +228,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
};
};
}
}
// Для других вариантов выравнивания
return
{
return
{
...
baseStyle
,
...
baseStyle
,
...(
node
.
attrs
.
align
===
'
left
'
&&
{
...(
node
.
attrs
.
align
===
'
left
'
&&
{
...
@@ -196,16 +250,17 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -196,16 +250,17 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
};
};
};
};
// Стили для самого изображения
const
getImageStyle
=
()
=>
({
const
getImageStyle
=
()
=>
({
width
:
node
.
attrs
.
width
?
`
${
node
.
attrs
.
width
}
px`
:
'
auto
'
,
width
:
node
.
attrs
.
width
?
`
${
node
.
attrs
.
width
}
px`
:
'
auto
'
,
height
:
'
auto
'
,
// Автоматическая высота для сохранения пропорций
height
:
'
auto
'
,
maxWidth
:
'
100%
'
,
maxWidth
:
'
100%
'
,
display
:
'
block
'
,
display
:
'
block
'
,
cursor
:
'
default
'
,
cursor
:
'
default
'
,
userSelect
:
'
none
'
,
userSelect
:
'
none
'
,
margin
:
node
.
attrs
.
align
===
'
center
'
?
'
0 auto
'
:
'
0
'
,
margin
:
node
.
attrs
.
align
===
'
center
'
?
'
0 auto
'
:
'
0
'
,
verticalAlign
:
node
.
attrs
.
align
===
'
text
'
?
'
middle
'
:
'
top
'
,
verticalAlign
:
node
.
attrs
.
align
===
'
text
'
?
'
middle
'
:
'
top
'
,
objectFit
:
'
contain
'
// Сохраняем пропорции изображения
objectFit
:
'
contain
'
});
});
return
(
return
(
...
@@ -227,30 +282,13 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
...
@@ -227,30 +282,13 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos, select
style
=
{
getImageStyle
()
}
style
=
{
getImageStyle
()
}
onLoad
=
{
()
=>
{
onLoad
=
{
()
=>
{
if
(
imgRef
.
current
&&
!
isInitialized
.
current
)
{
if
(
imgRef
.
current
&&
!
isInitialized
.
current
)
{
const
editorWidth
=
getEditor
Width
();
const
{
width
:
editorWidth
}
=
getEditor
Dimensions
();
const
naturalWidth
=
imgRef
.
current
.
naturalWidth
;
const
naturalWidth
=
imgRef
.
current
.
naturalWidth
;
const
naturalHeight
=
imgRef
.
current
.
naturalHeight
;
const
naturalHeight
=
imgRef
.
current
.
naturalHeight
;
let
width
=
naturalWidth
;
safeUpdateAttributes
({
let
height
=
naturalHeight
;
width
:
naturalWidth
,
height
:
naturalHeight
,
// Масштабируем изображение, если оно шире редактора
if
(
editorWidth
&&
width
>
editorWidth
)
{
const
ratio
=
editorWidth
/
width
;
width
=
editorWidth
;
height
=
Math
.
round
(
height
*
ratio
);
}
// Проверяем минимальный размер
if
(
width
<
MIN_WIDTH
)
{
const
ratio
=
MIN_WIDTH
/
width
;
width
=
MIN_WIDTH
;
height
=
Math
.
round
(
height
*
ratio
);
}
updateAttributes
({
width
:
Math
.
round
(
width
),
height
:
Math
.
round
(
height
),
'
data-node-id
'
:
node
.
attrs
[
'
data-node-id
'
]
||
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)
'
data-node-id
'
:
node
.
attrs
[
'
data-node-id
'
]
||
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)
});
});
isInitialized
.
current
=
true
;
isInitialized
.
current
=
true
;
...
...
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