|
1 | 1 | import React, { useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback } from 'react'; |
2 | 2 | import { type Origin, getAgentName } from '@plannotator/shared/agents'; |
3 | | -import { parseMarkdownToBlocks, exportAnnotations, exportLinkedDocAnnotations, exportEditorAnnotations, extractFrontmatter, wrapFeedbackForAgent, Frontmatter } from '@plannotator/ui/utils/parser'; |
| 3 | +import { parseMarkdownToBlocks, exportAnnotations, exportLinkedDocAnnotations, exportEditorAnnotations, extractFrontmatter, wrapFeedbackForAgent, Frontmatter, type LinkedDocAnnotationEntry } from '@plannotator/ui/utils/parser'; |
4 | 4 | import { Viewer, ViewerHandle } from '@plannotator/ui/components/Viewer'; |
5 | 5 | import { AnnotationPanel } from '@plannotator/ui/components/AnnotationPanel'; |
6 | 6 | import { ExportModal } from '@plannotator/ui/components/ExportModal'; |
@@ -150,6 +150,7 @@ const App: React.FC = () => { |
150 | 150 | const [gate, setGate] = useState(false); |
151 | 151 | const [annotateSource, setAnnotateSource] = useState<'file' | 'message' | 'folder' | null>(null); |
152 | 152 | const [sourceInfo, setSourceInfo] = useState<string | undefined>(); |
| 153 | + const [sourceConverted, setSourceConverted] = useState(false); |
153 | 154 | const [sourceFilePath, setSourceFilePath] = useState<string | undefined>(); |
154 | 155 | const [imageBaseDir, setImageBaseDir] = useState<string | undefined>(undefined); |
155 | 156 | const [isLoading, setIsLoading] = useState(true); |
@@ -304,7 +305,7 @@ const App: React.FC = () => { |
304 | 305 | const linkedDocHook = useLinkedDoc({ |
305 | 306 | markdown, annotations, selectedAnnotationId, globalAttachments, |
306 | 307 | setMarkdown, setAnnotations, setSelectedAnnotationId, setGlobalAttachments, |
307 | | - viewerRef, sidebar: linkedDocSidebar, sourceFilePath, |
| 308 | + viewerRef, sidebar: linkedDocSidebar, sourceFilePath, sourceConverted, |
308 | 309 | }); |
309 | 310 |
|
310 | 311 | // Archive browser |
@@ -652,7 +653,7 @@ const App: React.FC = () => { |
652 | 653 | if (!res.ok) throw new Error('Not in API mode'); |
653 | 654 | return res.json(); |
654 | 655 | }) |
655 | | - .then((data: { plan: string; origin?: Origin; mode?: 'annotate' | 'annotate-last' | 'annotate-folder' | 'archive'; filePath?: string; sourceInfo?: string; gate?: boolean; sharingEnabled?: boolean; shareBaseUrl?: string; pasteApiUrl?: string; repoInfo?: { display: string; branch?: string; host?: string }; previousPlan?: string | null; versionInfo?: { version: number; totalVersions: number; project: string }; archivePlans?: ArchivedPlan[]; projectRoot?: string; isWSL?: boolean; serverConfig?: { displayName?: string; gitUser?: string } }) => { |
| 656 | + .then((data: { plan: string; origin?: Origin; mode?: 'annotate' | 'annotate-last' | 'annotate-folder' | 'archive'; filePath?: string; sourceInfo?: string; sourceConverted?: boolean; gate?: boolean; sharingEnabled?: boolean; shareBaseUrl?: string; pasteApiUrl?: string; repoInfo?: { display: string; branch?: string; host?: string }; previousPlan?: string | null; versionInfo?: { version: number; totalVersions: number; project: string }; archivePlans?: ArchivedPlan[]; projectRoot?: string; isWSL?: boolean; serverConfig?: { displayName?: string; gitUser?: string } }) => { |
656 | 657 | // Initialize config store with server-provided values (config file > cookie > default) |
657 | 658 | configStore.init(data.serverConfig); |
658 | 659 | // gitUser drives the "Use git name" button in Settings; stays undefined (button hidden) when unavailable |
@@ -682,6 +683,7 @@ const App: React.FC = () => { |
682 | 683 | setAnnotateSource(data.mode === 'annotate-last' ? 'message' : data.mode === 'annotate-folder' ? 'folder' : 'file'); |
683 | 684 | } |
684 | 685 | setSourceInfo(data.sourceInfo ?? undefined); |
| 686 | + setSourceConverted(!!data.sourceConverted); |
685 | 687 | if (data.filePath) { |
686 | 688 | setImageBaseDir(data.mode === 'annotate-folder' ? data.filePath : data.filePath.replace(/\/[^/]+$/, '')); |
687 | 689 | if (data.mode === 'annotate') { |
@@ -1184,20 +1186,41 @@ const App: React.FC = () => { |
1184 | 1186 | return 'User reviewed the document and has no feedback.'; |
1185 | 1187 | } |
1186 | 1188 |
|
| 1189 | + // Derive the conversion flag for the currently-displayed document: |
| 1190 | + // when viewing a linked doc, use that doc's isConverted; otherwise use the root flag. |
| 1191 | + const activeConverted = linkedDocHook.isActive |
| 1192 | + ? (docAnnotations.get(linkedDocHook.filepath ?? '')?.isConverted ?? false) |
| 1193 | + : sourceConverted; |
| 1194 | + |
1187 | 1195 | let output = hasPlanAnnotations |
1188 | | - ? exportAnnotations(blocks, allAnnotations, globalAttachments, annotateSource === 'message' ? 'Message Feedback' : annotateSource === 'folder' ? 'Folder Feedback' : annotateSource === 'file' ? 'File Feedback' : 'Plan Feedback', annotateSource ?? 'plan') |
| 1196 | + ? exportAnnotations( |
| 1197 | + blocks, |
| 1198 | + allAnnotations, |
| 1199 | + globalAttachments, |
| 1200 | + annotateSource === 'message' ? 'Message Feedback' : annotateSource === 'folder' ? 'Folder Feedback' : annotateSource === 'file' ? 'File Feedback' : 'Plan Feedback', |
| 1201 | + annotateSource ?? 'plan', |
| 1202 | + { sourceConverted: activeConverted }, |
| 1203 | + ) |
1189 | 1204 | : ''; |
1190 | 1205 |
|
1191 | 1206 | if (hasDocAnnotations) { |
1192 | | - output += exportLinkedDocAnnotations(docAnnotations); |
| 1207 | + // Parse blocks for each linked doc's cached markdown so the exporter |
| 1208 | + // can attach source line numbers per annotation. |
| 1209 | + const enriched: Map<string, LinkedDocAnnotationEntry> = new Map(docAnnotations); |
| 1210 | + for (const [filepath, entry] of enriched) { |
| 1211 | + if (entry.markdown) { |
| 1212 | + enriched.set(filepath, { ...entry, blocks: parseMarkdownToBlocks(entry.markdown) }); |
| 1213 | + } |
| 1214 | + } |
| 1215 | + output += exportLinkedDocAnnotations(enriched); |
1193 | 1216 | } |
1194 | 1217 |
|
1195 | 1218 | if (hasEditorAnnotations) { |
1196 | 1219 | output += exportEditorAnnotations(editorAnnotations); |
1197 | 1220 | } |
1198 | 1221 |
|
1199 | 1222 | return output; |
1200 | | - }, [blocks, allAnnotations, globalAttachments, linkedDocHook.getDocAnnotations, editorAnnotations]); |
| 1223 | + }, [blocks, allAnnotations, globalAttachments, linkedDocHook.getDocAnnotations, editorAnnotations, sourceConverted, annotateSource, linkedDocHook.isActive, linkedDocHook.filepath]); |
1201 | 1224 |
|
1202 | 1225 | // Bot callback config — read once from URL search params (?cb=&ct=) |
1203 | 1226 | const callbackConfig = React.useMemo(() => getCallbackConfig(), []); |
|
0 commit comments