feat: add clickable file links to FilePreview markdown viewer

Extend the remarkFileLinks plugin support to FilePreview.tsx, enabling
wiki-style [[links]] and path references in markdown documents to become
clickable and open in the file preview panel. This allows seamless
navigation between documents in the file viewer.

Changes:
- Add fileTree, cwd, and onFileClick props to FilePreview component
- Integrate remarkFileLinks remark plugin for markdown file rendering
- Update MainPanel to pass file linking props to FilePreview
- Document file linking feature in CLAUDE.md Key Files table

Claude ID: ba360ad6-3ae8-4a87-a73d-ab97e466ce07
Maestro ID: b9bc0d08-5be2-4fdf-93cd-5618a8d53b35
This commit is contained in:
Pedram Amini
2025-12-15 14:29:32 -06:00
parent 30aa8d9b14
commit fe2c25fb52
3 changed files with 43 additions and 17 deletions

View File

@@ -95,6 +95,7 @@ src/
| Add playbook feature | `src/cli/services/playbooks.ts` |
| Modify wizard flow | `src/renderer/components/Wizard/` (see Onboarding Wizard section) |
| Add tour step | `src/renderer/components/Wizard/tour/tourSteps.ts` |
| Modify file linking | `src/renderer/utils/remarkFileLinks.ts` (remark plugin for `[[wiki]]` and path links) |
## Core Patterns

View File

@@ -11,6 +11,8 @@ import { MODAL_PRIORITIES } from '../constants/modalPriorities';
import { MermaidRenderer } from './MermaidRenderer';
import { getEncoding } from 'js-tiktoken';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { remarkFileLinks } from '../utils/remarkFileLinks';
import type { FileNode } from '../hooks/useFileExplorer';
interface FileStats {
size: number;
@@ -26,6 +28,12 @@ interface FilePreviewProps {
setMarkdownEditMode: (value: boolean) => void;
onSave?: (path: string, content: string) => Promise<void>;
shortcuts: Record<string, any>;
/** File tree for linking file references */
fileTree?: FileNode[];
/** Current working directory for proximity-based matching */
cwd?: string;
/** Callback when a file link is clicked */
onFileClick?: (path: string) => void;
}
// Get language from filename extension
@@ -317,7 +325,7 @@ function remarkHighlight() {
};
}
export function FilePreview({ file, onClose, theme, markdownEditMode, setMarkdownEditMode, onSave, shortcuts }: FilePreviewProps) {
export function FilePreview({ file, onClose, theme, markdownEditMode, setMarkdownEditMode, onSave, shortcuts, fileTree, cwd, onFileClick }: FilePreviewProps) {
const [searchQuery, setSearchQuery] = useState('');
const [searchOpen, setSearchOpen] = useState(false);
const [showCopyNotification, setShowCopyNotification] = useState(false);
@@ -1102,24 +1110,38 @@ export function FilePreview({ file, onClose, theme, markdownEditMode, setMarkdow
.prose img { display: block; max-width: 100%; height: auto; }
`}</style>
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkHighlight]}
remarkPlugins={[
remarkGfm,
remarkHighlight,
...(fileTree && fileTree.length > 0 && cwd !== undefined
? [[remarkFileLinks, { fileTree, cwd }] as any]
: [])
]}
rehypePlugins={[rehypeRaw]}
components={{
a: ({ node, href, children, ...props }) => (
<a
href={href}
{...props}
onClick={(e) => {
e.preventDefault();
if (href) {
window.maestro.shell.openExternal(href);
}
}}
style={{ color: theme.colors.accent, textDecoration: 'underline', cursor: 'pointer' }}
>
{children}
</a>
),
a: ({ node, href, children, ...props }) => {
// Handle maestro-file:// protocol for internal file links
const isMaestroFile = href?.startsWith('maestro-file://');
const filePath = isMaestroFile ? href.replace('maestro-file://', '') : null;
return (
<a
href={href}
{...props}
onClick={(e) => {
e.preventDefault();
if (isMaestroFile && filePath && onFileClick) {
onFileClick(filePath);
} else if (href) {
window.maestro.shell.openExternal(href);
}
}}
style={{ color: theme.colors.accent, textDecoration: 'underline', cursor: 'pointer' }}
>
{children}
</a>
);
},
code: ({ node, inline, className, children, ...props }) => {
const match = (className || '').match(/language-(\w+)/);
const language = match ? match[1] : 'text';

View File

@@ -871,6 +871,9 @@ export const MainPanel = forwardRef<MainPanelHandle, MainPanelProps>(function Ma
setPreviewFile({ ...previewFile, content });
}}
shortcuts={shortcuts}
fileTree={props.fileTree}
cwd={activeSession?.cwd}
onFileClick={props.onFileClick}
/>
</div>
) : (