Skip to content

Commit 9cee89c

Browse files
authored
fix: resolve the issue of not being able to drag after adding a figure caption (#7984)
#### What type of PR is this? /kind bug /area editor /milestone 2.22.x #### What this PR does / why we need it: 解决由于调整图片时多个位置同时修改 figure 而导致的报错问题。 #### Which issue(s) this PR fixes: Fixes #7980 #### Does this PR introduce a user-facing change? ```release-note 解决在 figure 中添加 caption 后拖动图片报错的问题 ```
1 parent 15a1c6e commit 9cee89c

File tree

3 files changed

+109
-21
lines changed

3 files changed

+109
-21
lines changed

ui/packages/editor/src/extensions/figure/index.ts

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Node,
55
Plugin,
66
PluginKey,
7+
TextSelection,
78
type CommandProps,
89
} from "@/tiptap";
910
import type { ExtensionOptions } from "@/types";
@@ -16,7 +17,7 @@ declare module "@/tiptap" {
1617
figure: {
1718
setFigure: (attrs?: Record<string, unknown>) => ReturnType;
1819
unsetFigure: () => ReturnType;
19-
updateFigureContainerWidth: (width: string) => ReturnType;
20+
updateFigureContainerWidth: (width?: string) => ReturnType;
2021
};
2122
}
2223
}
@@ -96,6 +97,51 @@ export const ExtensionFigure = Node.create<ExtensionFigureOptions>({
9697

9798
addKeyboardShortcuts() {
9899
return {
100+
Enter: ({ editor }) => {
101+
const { state } = editor;
102+
const { selection } = state;
103+
const { $from } = selection;
104+
105+
let inFigure = false;
106+
let figureDepth = -1;
107+
for (let depth = $from.depth; depth > 0; depth--) {
108+
const node = $from.node(depth);
109+
if (node.type.name === this.name) {
110+
inFigure = true;
111+
figureDepth = depth;
112+
break;
113+
}
114+
}
115+
116+
if (!inFigure) {
117+
return false;
118+
}
119+
120+
for (let depth = $from.depth; depth > 0; depth--) {
121+
const node = $from.node(depth);
122+
if (node.type.name === ExtensionFigureCaption.name) {
123+
return false;
124+
}
125+
}
126+
127+
const figureNode = $from.node(figureDepth);
128+
const figurePos = $from.before(figureDepth);
129+
const afterFigurePos = figurePos + figureNode.nodeSize;
130+
131+
editor
132+
.chain()
133+
.command(({ tr }) => {
134+
const paragraph = tr.doc.type.schema.nodes.paragraph.create();
135+
tr.insert(afterFigurePos, paragraph);
136+
tr.setSelection(
137+
TextSelection.near(tr.doc.resolve(afterFigurePos + 1))
138+
);
139+
return true;
140+
})
141+
.run();
142+
143+
return true;
144+
},
99145
Backspace: ({ editor }) => {
100146
const { state } = editor;
101147
const { selection, doc } = state;
@@ -205,17 +251,26 @@ export const ExtensionFigure = Node.create<ExtensionFigureOptions>({
205251
return commands.lift(this.name);
206252
},
207253
updateFigureContainerWidth:
208-
(width: string) =>
209-
({ state }: CommandProps) => {
210-
// Here we need to use this.editor instead of the dispatch and tr obtained from CommandProps
211-
// Because the dispatch and tr in CommandProps are outdated
212-
const { dispatch } = this.editor.view;
213-
const { tr } = this.editor.state;
254+
(width?: string) =>
255+
({ state, dispatch }: CommandProps) => {
214256
const { selection } = state;
215257
const { $from } = selection;
216258

259+
let figureDepth = -1;
260+
for (let d = $from.depth; d > 0; d--) {
261+
if ($from.node(d).type.name === this.name) {
262+
figureDepth = d;
263+
break;
264+
}
265+
}
266+
267+
if (figureDepth === -1) {
268+
return false;
269+
}
270+
271+
const figureNode = $from.node(figureDepth);
217272
const figureCaptionNodes = findChildren(
218-
$from.node(),
273+
figureNode,
219274
(node) => node.type.name === ExtensionFigureCaption.name
220275
);
221276

@@ -224,7 +279,10 @@ export const ExtensionFigure = Node.create<ExtensionFigureOptions>({
224279
}
225280

226281
const figureCaptionNode = figureCaptionNodes[0];
227-
tr.setNodeMarkup(figureCaptionNode.pos + 1, undefined, {
282+
const figurePos = $from.start(figureDepth);
283+
const captionPos = figurePos + figureCaptionNode.pos;
284+
285+
const tr = state.tr.setNodeMarkup(captionPos, undefined, {
228286
width: width,
229287
});
230288
dispatch?.(tr);

ui/packages/editor/src/extensions/image/BubbleItemImageSize.vue

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
BlockActionSeparator,
66
} from "@/components";
77
import { i18n } from "@/locales";
8-
import type { Editor } from "@/tiptap";
8+
import { findChildren, type Editor } from "@/tiptap";
99
import { computed, type Component } from "vue";
1010
import MdiBackupRestore from "~icons/mdi/backup-restore";
1111
import MdiImageSizeSelectActual from "~icons/mdi/image-size-select-actual";
@@ -35,9 +35,31 @@ const size = computed({
3535
});
3636
3737
function handleSetSize(size: { width?: string; height?: string }) {
38+
let figureWidth = size.width;
39+
if (!size.width) {
40+
const { state } = props.editor;
41+
const { selection } = state;
42+
const imageNodePosList = findChildren(
43+
selection.$from.node(),
44+
(node) => node.type.name === ExtensionImage.name
45+
);
46+
if (imageNodePosList.length === 0) {
47+
return;
48+
}
49+
const imageNodePos = imageNodePosList[0];
50+
if (imageNodePos && imageNodePos.node.type.name === ExtensionImage.name) {
51+
const pos = selection.$from.pos + imageNodePos.pos;
52+
const nodeDOM = props.editor.view.nodeDOM(pos) as HTMLElement;
53+
const imageNodeDOM = nodeDOM.querySelector("img");
54+
if (imageNodeDOM && imageNodeDOM.naturalWidth) {
55+
figureWidth = `${imageNodeDOM.naturalWidth}px`;
56+
}
57+
}
58+
}
3859
props.editor
3960
.chain()
4061
.updateAttributes(ExtensionImage.name, size)
62+
.updateFigureContainerWidth(figureWidth)
4163
.setNodeSelection(props.editor.state.selection.from)
4264
.focus()
4365
.run();

ui/packages/editor/src/extensions/image/ImageView.vue

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,28 @@ const aspectRatio = ref<number>(0);
108108
const resizeRef = ref<HTMLDivElement>();
109109
const inputRef = ref<HTMLInputElement>();
110110
111-
function onImageLoaded() {
111+
function onImageLoaded(event: Event) {
112112
if (!resizeRef.value) return;
113113
114114
aspectRatio.value =
115115
resizeRef.value.clientWidth / resizeRef.value.clientHeight;
116+
const target = event.target;
117+
118+
if (props.node?.attrs.width) {
119+
return;
120+
}
121+
if (target instanceof HTMLImageElement) {
122+
props.editor
123+
.chain()
124+
.updateAttributes(ExtensionImage.name, {
125+
width: `${target.naturalWidth}px`,
126+
height: `${target.naturalHeight}px`,
127+
})
128+
.updateFigureContainerWidth(`${target.naturalWidth}px`)
129+
.setNodeSelection(props.getPos() || 0)
130+
.focus()
131+
.run();
132+
}
116133
}
117134
118135
let cleanupResize: (() => void) | null = null;
@@ -182,6 +199,7 @@ function setupResizeListener() {
182199
props.editor
183200
.chain()
184201
.updateAttributes(ExtensionImage.name, { width, height })
202+
.updateFigureContainerWidth(width)
185203
.setNodeSelection(props.getPos() || 0)
186204
.focus()
187205
.run();
@@ -249,16 +267,6 @@ watch([src, resizeHandleRef], () => {
249267
}
250268
});
251269
252-
watch(
253-
() => props.node?.attrs.width,
254-
(newWidth) => {
255-
if (newWidth) {
256-
props.editor.commands.updateFigureContainerWidth(newWidth);
257-
}
258-
},
259-
{ immediate: false }
260-
);
261-
262270
const { isExternalAsset, transferring, handleTransfer } =
263271
useExternalAssetsTransfer(src, handleSetExternalLink);
264272

0 commit comments

Comments
 (0)