Skip to content

Commit e2dfd2e

Browse files
committed
Add context menu to preview, hide sidebar on welcome screen, implement recent files functionality
1 parent e668f51 commit e2dfd2e

8 files changed

Lines changed: 140 additions & 28 deletions

File tree

build/appicon.png

454 KB
Loading

build/windows/icon.ico

19 KB
Binary file not shown.

frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json.md5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
d3669e1b0e6ce7aca68f5a907d8116c0
1+
587ec3706bdf3222c8a715324ecd2646

frontend/src/App.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function App() {
2323
const [settingsOpen, setSettingsOpen] = useState(false);
2424
const [searchOpen, setSearchOpen] = useState(false);
2525
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
26-
const [recentFiles] = useState<RecentFile[]>([]);
26+
const [recentFiles, setRecentFiles] = useState<RecentFile[]>([]);
2727
const [isFullscreen, setIsFullscreen] = useState(false);
2828
const [zoom, setZoom] = useState(100);
2929
const [isDragging, setIsDragging] = useState(false);
@@ -109,6 +109,21 @@ export default function App() {
109109
loadInitialFile();
110110
}, [addTab]);
111111

112+
// Load recent files on mount
113+
useEffect(() => {
114+
const loadRecentFiles = async () => {
115+
const files = await wails.getRecentFiles();
116+
setRecentFiles(files);
117+
};
118+
loadRecentFiles();
119+
}, []);
120+
121+
// Update recent files when a file is opened
122+
const updateRecentFiles = useCallback(async () => {
123+
const files = await wails.getRecentFiles();
124+
setRecentFiles(files);
125+
}, []);
126+
112127
// Handle image paste
113128
useEffect(() => {
114129
const handlePaste = async (e: ClipboardEvent) => {
@@ -218,8 +233,17 @@ export default function App() {
218233
const result = await openFile();
219234
if (result) {
220235
addTab(result.name, result.path, result.content);
236+
updateRecentFiles();
237+
}
238+
}, [openFile, addTab, updateRecentFiles]);
239+
240+
const handleOpenRecentFile = useCallback(async (path: string) => {
241+
const result = await wails.readFileByPath(path);
242+
if (result) {
243+
addTab(result.name, result.path, result.content);
244+
updateRecentFiles();
221245
}
222-
}, [openFile, addTab]);
246+
}, [addTab, updateRecentFiles]);
223247

224248
const handleOpenFolder = useCallback(async () => {
225249
const tree = await wails.openFolder();
@@ -539,6 +563,7 @@ export default function App() {
539563
folderTree={folderTree}
540564
onFileClick={handleFileTreeClick}
541565
currentFilePath={activeTab?.filePath || filePath}
566+
onOpenRecentFile={handleOpenRecentFile}
542567
/>
543568

544569
<main className="flex-1 overflow-hidden relative">

frontend/src/components/Sidebar/Sidebar.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,21 @@ export function Sidebar({
2626
currentFilePath,
2727
onOpenRecentFile
2828
}: SidebarProps) {
29-
const [activeTab, setActiveTab] = useState<TabType>('toc');
29+
const [activeTab, setActiveTab] = useState<TabType>('files');
3030

3131
// Auto-switch to files tab when folder is opened
3232
if (folderTree.length > 0 && activeTab !== 'files') {
3333
setActiveTab('files');
3434
}
3535

36+
// Check if we have any content to show
37+
const hasContent = headings.length > 0 || folderTree.length > 0 || recentFiles.length > 0;
38+
39+
// If no content, don't render sidebar
40+
if (!hasContent) {
41+
return null;
42+
}
43+
3644
return (
3745
<div
3846
className={`
@@ -42,15 +50,17 @@ export function Sidebar({
4250
>
4351
<div className="w-56 h-full flex flex-col sidebar">
4452
<div className="flex border-b sidebar-border">
45-
<button
46-
onClick={() => setActiveTab('toc')}
47-
className={`sidebar-tab ${activeTab === 'toc' ? 'sidebar-tab-active' : 'sidebar-tab-inactive'}`}
48-
>
49-
<span className="flex items-center justify-center gap-1.5">
50-
<List className="w-3.5 h-3.5" />
51-
TOC
52-
</span>
53-
</button>
53+
{headings.length > 0 && (
54+
<button
55+
onClick={() => setActiveTab('toc')}
56+
className={`sidebar-tab ${activeTab === 'toc' ? 'sidebar-tab-active' : 'sidebar-tab-inactive'}`}
57+
>
58+
<span className="flex items-center justify-center gap-1.5">
59+
<List className="w-3.5 h-3.5" />
60+
TOC
61+
</span>
62+
</button>
63+
)}
5464
<button
5565
onClick={() => setActiveTab('files')}
5666
className={`sidebar-tab ${activeTab === 'files' ? 'sidebar-tab-active' : 'sidebar-tab-inactive'}`}

frontend/src/components/Viewer/MarkdownViewer.tsx

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useSettings } from '../../hooks/useSettings';
99
import { Link } from 'lucide-react';
1010
import type { HeadingItem } from '../../types';
1111
import 'katex/dist/katex.min.css';
12+
import { useState, useCallback, useEffect } from 'react';
1213

1314
interface MarkdownViewerProps {
1415
content: string;
@@ -20,6 +21,65 @@ interface HeadingProps {
2021
children: React.ReactNode;
2122
}
2223

24+
interface ContextMenuProps {
25+
x: number;
26+
y: number;
27+
onClose: () => void;
28+
}
29+
30+
function ContextMenu({ x, y, onClose }: ContextMenuProps) {
31+
useEffect(() => {
32+
const handleClick = () => onClose();
33+
const handleScroll = () => onClose();
34+
35+
document.addEventListener('click', handleClick);
36+
document.addEventListener('scroll', handleScroll, true);
37+
38+
return () => {
39+
document.removeEventListener('click', handleClick);
40+
document.removeEventListener('scroll', handleScroll, true);
41+
};
42+
}, [onClose]);
43+
44+
const handleCopy = () => {
45+
document.execCommand('copy');
46+
onClose();
47+
};
48+
49+
const handleSelectAll = () => {
50+
const selection = window.getSelection();
51+
const range = document.createRange();
52+
const content = document.querySelector('.markdown-body');
53+
if (content && selection) {
54+
range.selectNodeContents(content);
55+
selection.removeAllRanges();
56+
selection.addRange(range);
57+
}
58+
onClose();
59+
};
60+
61+
return (
62+
<div
63+
className="fixed z-50 bg-zinc-900 border border-zinc-700 rounded-lg shadow-xl py-1 min-w-[160px]"
64+
style={{ left: x, top: y }}
65+
onClick={(e) => e.stopPropagation()}
66+
>
67+
<button
68+
onClick={handleCopy}
69+
className="w-full px-4 py-2 text-left text-sm text-zinc-300 hover:bg-zinc-800 transition-colors"
70+
>
71+
Copy
72+
</button>
73+
<button
74+
onClick={handleSelectAll}
75+
className="w-full px-4 py-2 text-left text-sm text-zinc-300 hover:bg-zinc-800 transition-colors"
76+
>
77+
Select All
78+
</button>
79+
</div>
80+
);
81+
}
82+
2383
function HeadingWithAnchor({ id, children, Tag }: HeadingProps & { Tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }) {
2484
return (
2585
<Tag id={id} className="group relative">
@@ -39,17 +99,25 @@ function HeadingWithAnchor({ id, children, Tag }: HeadingProps & { Tag: 'h1' | '
3999

40100
export function MarkdownViewer({ content, headings }: MarkdownViewerProps) {
41101
const { settings } = useSettings();
102+
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
42103
let headingIndex = 0;
43104

105+
const handleContextMenu = useCallback((e: React.MouseEvent) => {
106+
e.preventDefault();
107+
setContextMenu({ x: e.clientX, y: e.clientY });
108+
}, []);
109+
44110
return (
45-
<div
46-
className="markdown-body px-8 py-6 max-w-3xl mx-auto"
47-
style={{
48-
['--md-font-size' as string]: `${settings.fontSize}px`,
49-
['--md-line-height' as string]: settings.lineHeight,
50-
fontFamily: settings.fontFamily,
51-
}}
52-
>
111+
<>
112+
<div
113+
className="markdown-body px-8 py-6 max-w-3xl mx-auto"
114+
onContextMenu={handleContextMenu}
115+
style={{
116+
['--md-font-size' as string]: `${settings.fontSize}px`,
117+
['--md-line-height' as string]: settings.lineHeight,
118+
fontFamily: settings.fontFamily,
119+
}}
120+
>
53121
<ReactMarkdown
54122
remarkPlugins={[remarkGfm, remarkMath]}
55123
rehypePlugins={[rehypeRaw, rehypeKatex]}
@@ -135,6 +203,14 @@ export function MarkdownViewer({ content, headings }: MarkdownViewerProps) {
135203
>
136204
{content}
137205
</ReactMarkdown>
138-
</div>
206+
</div>
207+
{contextMenu && (
208+
<ContextMenu
209+
x={contextMenu.x}
210+
y={contextMenu.y}
211+
onClose={() => setContextMenu(null)}
212+
/>
213+
)}
214+
</>
139215
);
140216
}

frontend/src/utils/wailsBindings.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ declare global {
99
ExportToPDF: (path: string) => Promise<void>;
1010
ExportContentToPDF: (content: string) => Promise<void>;
1111
ExportToHTML: (content: string) => Promise<void>;
12-
GetRecentFiles: () => Promise<Array<{ path: string; name: string; lastOpened: string }>>;
12+
GetRecentFiles: () => Promise<Array<{ path: string; name: string; accessedAt: string }>>;
1313
OpenFileDialog: () => Promise<string>;
1414
SaveFileDialog: (defaultName: string) => Promise<string>;
1515
ReadFileByPath: (path: string) => Promise<{ content: string; path: string; name: string }>;
@@ -128,9 +128,10 @@ export const wails = {
128128
try {
129129
if (window.go?.main?.App?.GetRecentFiles) {
130130
const files = await window.go.main.App.GetRecentFiles();
131-
return files.map(f => ({
132-
...f,
133-
lastOpened: new Date(f.lastOpened),
131+
return files.map((f: any) => ({
132+
path: f.path,
133+
name: f.name,
134+
lastOpened: new Date(f.accessedAt || f.lastOpened),
134135
}));
135136
}
136137
return [];

0 commit comments

Comments
 (0)