Skip to content

Commit 0f54e69

Browse files
committed
fix: 表格第一次悬浮的位置不准确
1 parent 8154279 commit 0f54e69

File tree

3 files changed

+130
-86
lines changed

3 files changed

+130
-86
lines changed

app/components/writing-studio/bubble-menu/TableColumn.vue

Lines changed: 92 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,26 @@ type TableColumnActionItem = {
4646
const { t } = useI18n();
4747
const editorRef = computed(() => props.editor);
4848
const containerRef = computed(() => props.container);
49-
const bubbleMenuAppendTarget = computed(() => containerRef.value ?? undefined);
5049
const handleAnchorRef = useTemplateRef<HTMLElement>("handleAnchor");
5150
const isMenuOpen = ref(false);
5251
const isColorMenuOpen = ref(false);
5352
const searchQuery = ref("");
5453
const { activeCell, isColumnSelection } = useWritingStudioTableColumnMenuState(editorRef);
5554
const colorPresets = useWritingStudioTableColumnColors();
5655
56+
const TABLE_COLUMN_MENU_PLUGIN_KEY = "writing-studio-table-column-menu";
57+
const TABLE_COLUMN_HANDLE_WIDTH_REM = 2.5;
58+
const TABLE_COLUMN_HANDLE_HEIGHT_REM = 1.5;
59+
const TABLE_COLUMN_HANDLE_TOP_OFFSET_PX = 1;
60+
5761
const openColorMenu = () => {
5862
isColorMenuOpen.value = true;
63+
requestColumnMenuPositionUpdate();
5964
};
6065
6166
const closeColorMenu = () => {
6267
isColorMenuOpen.value = false;
68+
requestColumnMenuPositionUpdate();
6369
};
6470
6571
const closeColumnMenu = () => {
@@ -68,6 +74,18 @@ const closeColumnMenu = () => {
6874
searchQuery.value = "";
6975
};
7076
77+
const requestColumnMenuPositionUpdate = async () => {
78+
if (!props.editor || props.editor.isDestroyed) {
79+
return;
80+
}
81+
82+
await nextTick();
83+
84+
props.editor.view.dispatch(
85+
props.editor.state.tr.setMeta(TABLE_COLUMN_MENU_PLUGIN_KEY, "updatePosition"),
86+
);
87+
};
88+
7189
const resolveFilteredColorEntries = (kind: WritingStudioTableColorKind) => {
7290
const query = searchQuery.value.trim().toLowerCase();
7391
const entries = Object.entries(colorPresets[kind]) as Array<[WritingStudioTableCellColorValue, (typeof colorPresets)[typeof kind][WritingStudioTableCellColorValue]]>;
@@ -152,18 +170,32 @@ const filteredTableActions = computed(() => {
152170
});
153171
});
154172
155-
watch(activeCell, (cell) => {
156-
if (!cell) {
157-
closeColumnMenu();
173+
const canShowColumnMenu = computed(() => {
174+
const currentEditor = props.editor;
175+
176+
if (!currentEditor?.isEditable || !isMenuOpen.value) {
177+
return false;
158178
}
159-
});
160179
161-
watch(isColumnSelection, (value) => {
162-
if (!value && isMenuOpen.value) {
163-
closeColumnMenu();
180+
if (!activeCell.value || !isColumnSelection.value) {
181+
return false;
164182
}
183+
184+
return Boolean(resolveWritingStudioActiveTableCell(currentEditor));
165185
});
166186
187+
watch(
188+
canShowColumnMenu,
189+
(value) => {
190+
if (!value && isMenuOpen.value) {
191+
closeColumnMenu();
192+
}
193+
},
194+
{
195+
flush: "post",
196+
},
197+
);
198+
167199
watch(
168200
[editorRef, isMenuOpen],
169201
([editor, menuOpen]) => {
@@ -238,24 +270,26 @@ const getColumnMenuVirtualElement = () => {
238270
return null;
239271
}
240272
241-
const handleAnchor = handleAnchorRef.value;
273+
const currentCell = resolveWritingStudioActiveTableCell(props.editor);
274+
const rect = resolveWritingStudioTableColumnRect(props.editor, currentCell);
242275
243-
if (handleAnchor) {
244-
return {
245-
contextElement: handleAnchor,
246-
getBoundingClientRect: () => handleAnchor.getBoundingClientRect(),
247-
};
248-
}
249-
250-
const rect = activeOutlineRect.value;
251-
252-
if (!rect || !props.container || !import.meta.client) {
276+
if (!rect || !import.meta.client) {
253277
return null;
254278
}
255279
280+
const rootFontSize = Number.parseFloat(window.getComputedStyle(document.documentElement).fontSize || "16");
281+
const handleWidth = TABLE_COLUMN_HANDLE_WIDTH_REM * rootFontSize;
282+
const handleHeight = TABLE_COLUMN_HANDLE_HEIGHT_REM * rootFontSize;
283+
const domRect = new DOMRect(
284+
rect.left + rect.width / 2 - handleWidth / 2,
285+
rect.top + TABLE_COLUMN_HANDLE_TOP_OFFSET_PX - handleHeight / 2,
286+
handleWidth,
287+
handleHeight,
288+
);
289+
256290
return {
257-
contextElement: props.container,
258-
getBoundingClientRect: () => new DOMRect(rect.left + rect.width / 2, rect.top, 0, 0),
291+
getBoundingClientRect: () => domRect,
292+
getClientRects: () => [domRect],
259293
};
260294
};
261295
@@ -272,31 +306,19 @@ const openColumnMenu = () => {
272306
273307
isColorMenuOpen.value = false;
274308
isMenuOpen.value = true;
309+
requestColumnMenuPositionUpdate();
275310
};
276311
277312
const shouldShowColumnMenu = (menuProps: any) => {
278313
const currentEditor = menuProps?.editor as Editor | undefined;
279314
280-
if (!currentEditor?.isEditable) {
281-
return false;
282-
}
283-
284-
if (!isMenuOpen.value) {
285-
return false;
286-
}
287-
288-
if (!isWritingStudioColumnSelectionActive(currentEditor)) {
289-
closeColumnMenu();
290-
return false;
291-
}
292-
293-
const currentCell = resolveWritingStudioActiveTableCell(currentEditor);
294-
if (!currentCell) {
295-
closeColumnMenu();
296-
return false;
297-
}
298-
299-
return true;
315+
return Boolean(
316+
currentEditor?.isEditable
317+
&& isMenuOpen.value
318+
&& activeCell.value
319+
&& isWritingStudioColumnSelectionActive(currentEditor)
320+
&& resolveWritingStudioActiveTableCell(currentEditor),
321+
);
300322
};
301323
302324
const runColumnAction = (actionId: TableColumnActionId) => {
@@ -306,6 +328,7 @@ const runColumnAction = (actionId: TableColumnActionId) => {
306328
307329
if (actionId === "color") {
308330
openColorMenu();
331+
requestColumnMenuPositionUpdate();
309332
return;
310333
}
311334
@@ -372,11 +395,10 @@ const applyColumnColor = (kind: WritingStudioTableColorKind, value: WritingStudi
372395
<div class="ws-table-column-selection-outline" :class="{ 'ws-table-column-selection-outline--column': isMenuOpen || isColumnSelection }" />
373396
</div>
374397
<BubbleMenu
375-
v-if="editor && activeCell"
398+
v-if="editor"
376399
plugin-key="writing-studio-table-column-menu"
377400
class="z-2"
378401
:editor="editor"
379-
:append-to="bubbleMenuAppendTarget"
380402
:should-show="shouldShowColumnMenu"
381403
:get-referenced-virtual-element="getColumnMenuVirtualElement"
382404
:options="{ placement: 'bottom-start', strategy: 'fixed', offset: 14 }">
@@ -409,36 +431,38 @@ const applyColumnColor = (kind: WritingStudioTableColorKind, value: WritingStudi
409431
</button>
410432
</HoverCardTrigger>
411433

412-
<HoverCardContent side="right" align="start" :side-offset="12" class="ws-table-column-color-menu" @pointerenter="openColorMenu">
413-
<ScrollArea class="ws-table-column-color-scroll">
414-
<div v-if="filteredTextColorEntries.length > 0" class="ws-table-column-color-section">
415-
<div class="ws-table-column-color-title">
416-
{{ t("writingStudio.toolbar.table.columnMenu.colors.text.title") }}
434+
<HoverCardContent side="right" align="start" :side-offset="12" class="ws-table-column-color-menu">
435+
<div @pointerenter="openColorMenu">
436+
<ScrollArea class="ws-table-column-color-scroll">
437+
<div v-if="filteredTextColorEntries.length > 0" class="ws-table-column-color-section">
438+
<div class="ws-table-column-color-title">
439+
{{ t("writingStudio.toolbar.table.columnMenu.colors.text.title") }}
440+
</div>
441+
442+
<button v-for="[value, preset] in filteredTextColorEntries" :key="`text-${value}`" type="button" class="ws-table-column-color-item" @mousedown.prevent @click="applyColumnColor('text', value)">
443+
<span class="ws-table-column-color-swatch" :style="{ color: preset.textColor ?? 'oklch(var(--foreground))' }">
444+
<Type class="h-6 w-6" />
445+
</span>
446+
<span>{{ t(preset.labelKey) }}</span>
447+
</button>
417448
</div>
418449

419-
<button v-for="[value, preset] in filteredTextColorEntries" :key="`text-${value}`" type="button" class="ws-table-column-color-item" @mousedown.prevent @click="applyColumnColor('text', value)">
420-
<span class="ws-table-column-color-swatch" :style="{ color: preset.textColor ?? 'oklch(var(--foreground))' }">
421-
<Type class="h-6 w-6" />
422-
</span>
423-
<span>{{ t(preset.labelKey) }}</span>
424-
</button>
425-
</div>
450+
<Separator v-if="filteredTextColorEntries.length > 0 && filteredBackgroundColorEntries.length > 0" class="my-3" />
426451

427-
<Separator v-if="filteredTextColorEntries.length > 0 && filteredBackgroundColorEntries.length > 0" class="my-3" />
452+
<div v-if="filteredBackgroundColorEntries.length > 0" class="ws-table-column-color-section">
453+
<div class="ws-table-column-color-title">
454+
{{ t("writingStudio.toolbar.table.columnMenu.colors.background.title") }}
455+
</div>
428456

429-
<div v-if="filteredBackgroundColorEntries.length > 0" class="ws-table-column-color-section">
430-
<div class="ws-table-column-color-title">
431-
{{ t("writingStudio.toolbar.table.columnMenu.colors.background.title") }}
457+
<button v-for="[value, preset] in filteredBackgroundColorEntries" :key="`background-${value}`" type="button" class="ws-table-column-color-item" @mousedown.prevent @click="applyColumnColor('background', value)">
458+
<span class="ws-table-column-color-swatch" :style="{ color: preset.backgroundColor ?? 'oklch(var(--muted-foreground))' }">
459+
<Square class="h-6 w-6 fill-current" />
460+
</span>
461+
<span>{{ t(preset.labelKey) }}</span>
462+
</button>
432463
</div>
433-
434-
<button v-for="[value, preset] in filteredBackgroundColorEntries" :key="`background-${value}`" type="button" class="ws-table-column-color-item" @mousedown.prevent @click="applyColumnColor('background', value)">
435-
<span class="ws-table-column-color-swatch" :style="{ color: preset.backgroundColor ?? 'oklch(var(--muted-foreground))' }">
436-
<Square class="h-6 w-6 fill-current" />
437-
</span>
438-
<span>{{ t(preset.labelKey) }}</span>
439-
</button>
440-
</div>
441-
</ScrollArea>
464+
</ScrollArea>
465+
</div>
442466
</HoverCardContent>
443467
</HoverCard>
444468

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@tiptap/extension-audio": "^3.20.0",
2020
"@tiptap/extension-code-block": "^3.20.0",
2121
"@tiptap/extension-code-block-lowlight": "^3.20.0",
22+
"@tiptap/extension-collaboration": "^3.20.1",
2223
"@tiptap/extension-details": "^3.20.0",
2324
"@tiptap/extension-document": "^3.20.0",
2425
"@tiptap/extension-drag-handle": "^3.20.0",
@@ -28,6 +29,7 @@
2829
"@tiptap/extension-image": "^3.20.0",
2930
"@tiptap/extension-link": "^3.20.0",
3031
"@tiptap/extension-mention": "^3.20.0",
32+
"@tiptap/extension-node-range": "^3.20.1",
3133
"@tiptap/extension-placeholder": "^3.20.0",
3234
"@tiptap/extension-subscript": "^3.20.0",
3335
"@tiptap/extension-superscript": "^3.20.0",
@@ -46,6 +48,7 @@
4648
"@tiptap/pm": "^3.20.0",
4749
"@tiptap/starter-kit": "^3.20.0",
4850
"@tiptap/vue-3": "^3.20.0",
51+
"@tiptap/y-tiptap": "^3.0.2",
4952
"@unhead/vue": "^2.0.19",
5053
"axios": "^1.13.2",
5154
"crypto": "^1.0.1",
@@ -62,7 +65,9 @@
6265
"uuid": "^13.0.0",
6366
"vue": "^3.5.22",
6467
"vue-router": "^4.6.3",
65-
"vue-sonner": "^2.0.9"
68+
"vue-sonner": "^2.0.9",
69+
"y-protocols": "^1.0.7",
70+
"yjs": "^13.6.29"
6671
},
6772
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
6873
"devDependencies": {

0 commit comments

Comments
 (0)