diff --git a/packages/frontend/src/components/content/Header.vue b/packages/frontend/src/components/content/Header.vue index 4c160d3..a548180 100644 --- a/packages/frontend/src/components/content/Header.vue +++ b/packages/frontend/src/components/content/Header.vue @@ -1,17 +1,40 @@ diff --git a/packages/frontend/src/index.ts b/packages/frontend/src/index.ts index 1ef127c..89a4c11 100644 --- a/packages/frontend/src/index.ts +++ b/packages/frontend/src/index.ts @@ -85,6 +85,9 @@ export const init = (sdk: FrontendSDK) => { onEnter: () => { emitter.emit("refreshEditors"); checkLegacyNotes(sdk); + setTimeout(() => { + emitter.emit("restoreFocus"); + }, 200); }, }); diff --git a/packages/frontend/src/utils/eventBus.ts b/packages/frontend/src/utils/eventBus.ts index 6c120bd..74d3dcd 100644 --- a/packages/frontend/src/utils/eventBus.ts +++ b/packages/frontend/src/utils/eventBus.ts @@ -3,6 +3,7 @@ import mitt from "mitt"; type Events = { refreshEditors: void; refreshTree: number; + restoreFocus: void; showMigrationDialog: { path: string; content: string }[]; confirmMigration: { path: string; content: string }[]; }; diff --git a/packages/frontend/src/utils/jsonToMarkdown.ts b/packages/frontend/src/utils/jsonToMarkdown.ts new file mode 100644 index 0000000..28efdbe --- /dev/null +++ b/packages/frontend/src/utils/jsonToMarkdown.ts @@ -0,0 +1,128 @@ +import { Editor } from "@tiptap/core"; +import { StarterKit } from "@tiptap/starter-kit"; +import { type NoteContent, type NoteContentItem } from "shared"; +import { Markdown } from "tiptap-markdown"; + +import { type FrontendSDK } from "@/types"; + +async function fetchReplaySessionContent( + sdk: FrontendSDK, + sessionId: string, +): Promise { + try { + const sessionResponse = await sdk.graphql.replaySessionEntries({ + id: sessionId, + }); + const activeEntryId = sessionResponse?.replaySession?.activeEntry?.id; + + if (!activeEntryId) { + return undefined; + } + + const entryResponse = await sdk.graphql.replayEntry({ + id: activeEntryId, + }); + + return entryResponse?.replayEntry?.raw || undefined; + } catch (error) { + console.error("Error fetching replay session content:", error); + return undefined; + } +} + +function collectMentionIds(content: NoteContentItem[]): string[] { + const ids: string[] = []; + + for (const node of content) { + if (node.type === "mention" && node.attrs?.id) { + ids.push(node.attrs.id as string); + } + if (node.content) { + ids.push(...collectMentionIds(node.content)); + } + } + + return ids; +} + +function replaceMentionsWithCodeBlocks( + content: NoteContentItem[], + httpContentMap: Map, +): NoteContentItem[] { + const result: NoteContentItem[] = []; + + for (const node of content) { + if (node.type === "mention" && node.attrs?.id) { + const sessionId = node.attrs.id as string; + const httpContent = httpContentMap.get(sessionId); + + if (httpContent) { + result.push({ + type: "codeBlock", + attrs: { language: "http" }, + content: [ + { + type: "text", + text: httpContent, + }, + ], + }); + } else { + result.push({ + type: "paragraph", + content: [ + { + type: "text", + text: `[Replay Session: ${sessionId} (unavailable)]`, + }, + ], + }); + } + } else if (node.content) { + result.push({ + ...node, + content: replaceMentionsWithCodeBlocks(node.content, httpContentMap), + }); + } else { + result.push(node); + } + } + + return result; +} + +export async function convertTipTapToMarkdown( + content: NoteContent, + sdk: FrontendSDK, +): Promise { + const mentionIds = collectMentionIds(content.content || []); + + const httpContentMap = new Map(); + if (mentionIds.length > 0) { + const fetchPromises = mentionIds.map(async (id) => { + const httpContent = await fetchReplaySessionContent(sdk, id); + if (httpContent) { + httpContentMap.set(id, httpContent); + } + }); + await Promise.all(fetchPromises); + } + + const processedContent: NoteContent = { + ...content, + content: replaceMentionsWithCodeBlocks( + content.content || [], + httpContentMap, + ), + }; + + const editor = new Editor({ + extensions: [StarterKit, Markdown], + content: processedContent, + }); + + const markdown = editor.storage.markdown.getMarkdown(); + editor.destroy(); + + return markdown; +}