mirror of
https://github.com/docmost/docmost.git
synced 2026-08-31 10:55:29 +08:00
fix(base): show skeleton while inline embed is being created, anchor at slash position
The Database slash command deleted the trigger text and then awaited
the create-base API before inserting the embed. During the wait the
editor sat empty (no visible state), and by the time the embed was
inserted the editor's selection had often drifted — so the new node
landed in the wrong place and the page appeared to "jump up" when the
response arrived.
Insert a placeholder baseEmbed node synchronously at the slash position
with pageId: null and a unique pendingKey. BaseEmbedView renders
BaseTableSkeleton while pendingKey is set — same skeleton BaseTable
shows on its own initial load, so the swap to the real table is a
visual no-op. Once the API responds, look up the placeholder by its
pendingKey and patch in the real pageId via setNodeMarkup. On API
failure, remove the placeholder and surface a toast. The pendingKey
attribute is non-serializing (parseHTML returns null, renderHTML
returns {}) so a placeholder can't survive a page reload as orphan.
This commit is contained in:
@@ -53,6 +53,30 @@ import {
|
||||
YoutubeIcon,
|
||||
} from "@/components/icons";
|
||||
import api from "@/lib/api-client";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import type { Editor } from "@tiptap/core";
|
||||
|
||||
// Resolve the position of a baseEmbed placeholder by its pendingKey.
|
||||
// Used by the Database slash command to patch in the real pageId once
|
||||
// the create-base API responds — positions may have shifted in the
|
||||
// interim from collab edits, undo/redo, or concurrent slash commands.
|
||||
function findBaseEmbedPlaceholderPos(
|
||||
editor: Editor,
|
||||
pendingKey: string,
|
||||
): number | null {
|
||||
let foundPos: number | null = null;
|
||||
editor.state.doc.descendants((node, pos) => {
|
||||
if (
|
||||
node.type.name === "baseEmbed" &&
|
||||
node.attrs.pendingKey === pendingKey
|
||||
) {
|
||||
foundPos = pos;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return foundPos;
|
||||
}
|
||||
|
||||
const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
basic: [
|
||||
@@ -488,13 +512,57 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
const parentPageId = editor.storage?.pageId as string | undefined;
|
||||
if (!parentPageId) return;
|
||||
|
||||
editor.chain().focus().deleteRange(range).run();
|
||||
// Insert a placeholder embed at the slash position synchronously
|
||||
// so (a) the position is established before any focus/selection
|
||||
// drift during the await, and (b) the user sees a skeleton in
|
||||
// the document instead of an empty gap. The API call then patches
|
||||
// the real pageId into this exact node, identified by pendingKey.
|
||||
const pendingKey =
|
||||
typeof crypto !== "undefined" && "randomUUID" in crypto
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${Math.random()}`;
|
||||
|
||||
const res = await api.post<{ id: string }>("/bases/inline-embed", {
|
||||
parentPageId,
|
||||
});
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.insertBaseEmbed({ pageId: null, pendingKey })
|
||||
.run();
|
||||
|
||||
editor.commands.insertBaseEmbed({ pageId: res.data.id });
|
||||
try {
|
||||
const res = await api.post<{ id: string }>("/bases/inline-embed", {
|
||||
parentPageId,
|
||||
});
|
||||
|
||||
const pos = findBaseEmbedPlaceholderPos(editor, pendingKey);
|
||||
if (pos === null) return;
|
||||
editor
|
||||
.chain()
|
||||
.command(({ tr }) => {
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
pageId: res.data.id,
|
||||
pendingKey: null,
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.run();
|
||||
} catch {
|
||||
const pos = findBaseEmbedPlaceholderPos(editor, pendingKey);
|
||||
if (pos !== null) {
|
||||
editor
|
||||
.chain()
|
||||
.command(({ tr }) => {
|
||||
const node = tr.doc.nodeAt(pos);
|
||||
if (node) tr.delete(pos, pos + node.nodeSize);
|
||||
return true;
|
||||
})
|
||||
.run();
|
||||
}
|
||||
notifications.show({
|
||||
message: "Failed to create database",
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user