Skip to content

Commit ab9f08e

Browse files
committed
fix(mobile): improve tab management and UI polish
1 parent 3450a94 commit ab9f08e

File tree

9 files changed

+108
-41
lines changed

9 files changed

+108
-41
lines changed

.husky/pre-commit

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
#!/usr/bin/env sh
22
. "$(dirname "$0")/_/husky.sh"
33

4+
# Load nvm
5+
export NVM_DIR="$HOME/.nvm"
6+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
7+
48
echo "🔍 Running pre-commit checks..."
59

610
# Type checking
711
echo "📝 Type checking..."
8-
npm run type-check
12+
pnpm run type-check
913
if [ $? -ne 0 ]; then
1014
echo "❌ Type check failed"
1115
exit 1
1216
fi
1317

1418
# Linting
1519
echo "🔍 Linting..."
16-
npm run lint
20+
pnpm run lint
1721
if [ $? -ne 0 ]; then
1822
echo "❌ Lint failed"
1923
exit 1

.husky/pre-push

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
#!/usr/bin/env sh
22
. "$(dirname "$0")/_/husky.sh"
33

4+
# Load nvm
5+
export NVM_DIR="$HOME/.nvm"
6+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
7+
48
echo "🔍 Running pre-push checks..."
59

610
# Type checking
711
echo "📝 Type checking..."
8-
npm run type-check
12+
pnpm run type-check
913
if [ $? -ne 0 ]; then
1014
echo "❌ Type check failed"
1115
exit 1
1216
fi
1317

1418
# Linting
1519
echo "🔍 Linting..."
16-
npm run lint
20+
pnpm run lint
1721
if [ $? -ne 0 ]; then
1822
echo "❌ Lint failed"
1923
exit 1

src/components/diagrams/DiagramEditor.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ export default function DiagramEditor({
210210
startOnLoad: false,
211211
theme: isDark ? 'dark' : 'default',
212212
securityLevel: 'loose',
213+
suppressErrorRendering: true,
214+
logLevel: 'fatal',
213215
});
214216
}, [isDark]);
215217

@@ -227,6 +229,8 @@ export default function DiagramEditor({
227229
startOnLoad: false,
228230
theme: isDark ? 'dark' : 'default',
229231
securityLevel: 'loose',
232+
suppressErrorRendering: true,
233+
logLevel: 'fatal',
230234
});
231235

232236
// Generate unique ID for this diagram
@@ -240,7 +244,7 @@ export default function DiagramEditor({
240244
setError(null);
241245
} catch (err) {
242246
setError(err instanceof Error ? err.message : 'Failed to render diagram');
243-
console.error('Mermaid render error:', err);
247+
// Silently catch errors - don't log to console
244248
}
245249
};
246250

@@ -559,6 +563,8 @@ export default function DiagramEditor({
559563
startOnLoad: false,
560564
theme: 'default', // Force light theme for print
561565
securityLevel: 'loose',
566+
suppressErrorRendering: true,
567+
logLevel: 'fatal',
562568
});
563569

564570
// Generate unique ID for print diagram
@@ -584,9 +590,11 @@ export default function DiagramEditor({
584590
startOnLoad: false,
585591
theme: 'dark',
586592
securityLevel: 'loose',
593+
suppressErrorRendering: true,
594+
logLevel: 'fatal',
587595
});
588-
} catch (error) {
589-
console.error('Failed to render light mode for print:', error);
596+
} catch {
597+
// Silently catch errors
590598
window.print(); // Fallback to regular print
591599
}
592600
} else {

src/components/layout/DesktopLayout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface DesktopLayoutProps {
1919
activeTabId: string | null;
2020
onTabClick: (tabId: string) => void;
2121
onTabClose: (tabId: string) => void;
22+
onCloseAll?: () => void;
2223
}
2324

2425
export function DesktopLayout({
@@ -31,6 +32,7 @@ export function DesktopLayout({
3132
activeTabId,
3233
onTabClick,
3334
onTabClose,
35+
onCloseAll,
3436
}: DesktopLayoutProps) {
3537
// Keyboard shortcuts for tab management
3638
useEffect(() => {
@@ -78,6 +80,7 @@ export function DesktopLayout({
7880
activeTabId={activeTabId}
7981
onTabClick={onTabClick}
8082
onTabClose={onTabClose}
83+
onCloseAll={onCloseAll}
8184
/>
8285
<main className="flex-1 overflow-hidden">
8386
<Index {...editorProps} />

src/components/layout/MainLayout.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,33 @@ export default function MainLayout() {
235235
}
236236
}, [openTabs, activeTabId, notes, setSelectedNote]);
237237

238+
const handleCloseAllTabs = useCallback(() => {
239+
// Close all tabs and clear selection
240+
setOpenTabs([]);
241+
setActiveTabId(null);
242+
setSelectedNote(null);
243+
}, [setSelectedNote]);
244+
245+
const handleDeleteNote = useCallback(async (noteId: string) => {
246+
// Find and close the tab for this note
247+
const tabToClose = openTabs.find(t => t.noteId === noteId);
248+
if (tabToClose) {
249+
handleTabClose(tabToClose.id);
250+
}
251+
// Delete the note
252+
await deleteNote(noteId);
253+
}, [openTabs, handleTabClose, deleteNote]);
254+
255+
const handleArchiveNote = useCallback(async (noteId: string) => {
256+
// Find and close the tab for this note
257+
const tabToClose = openTabs.find(t => t.noteId === noteId);
258+
if (tabToClose) {
259+
handleTabClose(tabToClose.id);
260+
}
261+
// Archive the note
262+
await archiveNote(noteId);
263+
}, [openTabs, handleTabClose, archiveNote]);
264+
238265
const handleDirtyChange = useCallback((isDirty: boolean) => {
239266
if (!selectedNote) return;
240267

@@ -356,15 +383,16 @@ export default function MainLayout() {
356383
onCreateCode: handleCreateCode,
357384
onToggleFolderPanel: handleToggleFolderPanel,
358385
onEmptyTrash: handleEmptyTrash,
386+
onRefresh: refetch,
359387
creatingNote,
360388
};
361389

362390
const editorProps: EditorProps = {
363391
note: selectedNote,
364392
folders,
365393
onUpdateNote: updateNote,
366-
onDeleteNote: deleteNote,
367-
onArchiveNote: archiveNote,
394+
onDeleteNote: handleDeleteNote,
395+
onArchiveNote: handleArchiveNote,
368396
onToggleStar: toggleStar,
369397
starringStar,
370398
onHideNote: hideNote,
@@ -437,6 +465,7 @@ export default function MainLayout() {
437465
activeTabId={activeTabId}
438466
onTabClick={handleTabClick}
439467
onTabClose={handleTabClose}
468+
onCloseAll={handleCloseAllTabs}
440469
/>
441470
);
442471
}

src/components/layout/TabBar.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DropdownMenu,
55
DropdownMenuContent,
66
DropdownMenuItem,
7+
DropdownMenuSeparator,
78
DropdownMenuTrigger,
89
} from '@/components/ui/dropdown-menu';
910
import { useRef, useState, useEffect } from 'react';
@@ -21,6 +22,7 @@ interface TabBarProps {
2122
activeTabId: string | null;
2223
onTabClick: (tabId: string) => void;
2324
onTabClose: (tabId: string) => void;
25+
onCloseAll?: () => void;
2426
}
2527

2628
const TabIcon = ({ type }: { type: Tab['type'] }) => {
@@ -34,7 +36,7 @@ const TabIcon = ({ type }: { type: Tab['type'] }) => {
3436
}
3537
};
3638

37-
export function TabBar({ tabs, activeTabId, onTabClick, onTabClose }: TabBarProps) {
39+
export function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onCloseAll }: TabBarProps) {
3840
const scrollContainerRef = useRef<HTMLDivElement>(null);
3941
const tabRefs = useRef<Map<string, HTMLDivElement>>(new Map());
4042
const [showOverflow, setShowOverflow] = useState(false);
@@ -116,9 +118,9 @@ export function TabBar({ tabs, activeTabId, onTabClick, onTabClose }: TabBarProp
116118
{tab.title || 'Untitled'}
117119
</span>
118120
<Button
119-
variant="secondary"
121+
variant="outline"
120122
size="sm"
121-
className="h-5 w-5 p-0 ml-1 opacity-0 group-hover:opacity-100 transition-opacity"
123+
className="h-5 w-5 p-0 ml-1 opacity-0 group-hover:opacity-100 transition-opacity shadow-none"
122124
onClick={(e) => {
123125
e.stopPropagation();
124126
onTabClose(tab.id);
@@ -160,9 +162,9 @@ export function TabBar({ tabs, activeTabId, onTabClick, onTabClose }: TabBarProp
160162
</span>
161163
</div>
162164
<Button
163-
variant="secondary"
165+
variant="outline"
164166
size="sm"
165-
className="h-5 w-5 p-0 ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
167+
className="h-5 w-5 p-0 ml-2 opacity-0 group-hover:opacity-100 transition-opacity shadow-none"
166168
onClick={(e) => {
167169
e.stopPropagation();
168170
onTabClose(tab.id);
@@ -172,6 +174,18 @@ export function TabBar({ tabs, activeTabId, onTabClick, onTabClose }: TabBarProp
172174
</Button>
173175
</DropdownMenuItem>
174176
))}
177+
{onCloseAll && tabs.length > 0 && (
178+
<>
179+
<DropdownMenuSeparator />
180+
<DropdownMenuItem
181+
onClick={onCloseAll}
182+
className="text-muted-foreground"
183+
>
184+
<X className="h-4 w-4 mr-2" />
185+
Close All Tabs
186+
</DropdownMenuItem>
187+
</>
188+
)}
175189
</DropdownMenuContent>
176190
</DropdownMenu>
177191
</div>

src/components/notes/NotesPanel/index.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ChevronDown,
99
Network,
1010
Code2,
11+
RefreshCw,
1112
} from 'lucide-react';
1213
import NotesList from '@/components/notes/NotesPanel/NotesList.tsx';
1314
import { Button } from '@/components/ui/button.tsx';
@@ -57,6 +58,7 @@ interface FilesPanelProps {
5758
onCreateCode?: (templateData?: { language: string; code: string }) => void;
5859
onToggleFolderPanel: () => void;
5960
onEmptyTrash?: () => Promise<void>;
61+
onRefresh?: () => Promise<void>;
6062
creatingNote?: boolean;
6163
isMobile?: boolean;
6264
onClose?: () => void;
@@ -77,6 +79,7 @@ export default function FilesPanel({
7779
onCreateCode,
7880
onToggleFolderPanel,
7981
onEmptyTrash,
82+
onRefresh,
8083
creatingNote = false,
8184
isMobile = false,
8285
onClose,
@@ -94,6 +97,8 @@ export default function FilesPanel({
9497
showCodeOnly: false,
9598
});
9699

100+
const [isRefreshing, setIsRefreshing] = useState(false);
101+
97102
const sortNotes = (notes: Note[], config: SortConfig): Note[] => {
98103
return [...notes].sort((a, b) => {
99104
let aValue: string | number | Date;
@@ -203,6 +208,17 @@ export default function FilesPanel({
203208
}
204209
};
205210

211+
const handleRefresh = async () => {
212+
if (!onRefresh || isRefreshing) return;
213+
214+
setIsRefreshing(true);
215+
try {
216+
await onRefresh();
217+
} finally {
218+
setIsRefreshing(false);
219+
}
220+
};
221+
206222
const isTrashView = currentView === 'trash';
207223

208224
const getEmptyMessage = () => {
@@ -269,6 +285,18 @@ export default function FilesPanel({
269285

270286
<div className="flex shrink-0 items-center gap-2">
271287
<ButtonGroup>
288+
{onRefresh && (
289+
<Button
290+
variant="outline"
291+
size="sm"
292+
onClick={handleRefresh}
293+
disabled={isRefreshing}
294+
className={`shadow-none flex items-center justify-center ${isMobile ? 'h-9 w-9 touch-manipulation p-0' : ''}`}
295+
title="Refresh notes from server"
296+
>
297+
<RefreshCw className={`${isMobile ? 'h-3 w-3' : 'h-4 w-4'} ${isRefreshing ? 'animate-spin' : ''}`} />
298+
</Button>
299+
)}
272300
<DropdownMenu>
273301
<DropdownMenuTrigger asChild>
274302
<Button

src/hooks/useNotes.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,6 @@ export function useNotes() {
310310
});
311311

312312
setNotes(notesWithFolders);
313-
314-
setSelectedNote((prev) => {
315-
if (prev === null && notesWithFolders.length > 0) {
316-
return notesWithFolders[0];
317-
}
318-
return prev;
319-
});
320313
} catch (error) {
321314
secureLogger.error('Data loading failed', error);
322315
if (error instanceof Error && error.message.includes('decrypt')) {
@@ -427,20 +420,6 @@ export function useNotes() {
427420
// }
428421
// }, [selectedNote?.id, webSocket.isAuthenticated, webSocket]);
429422

430-
// Auto-select a note when selectedNote becomes null and there are available notes
431-
useEffect(() => {
432-
if (!selectedNote && filteredNotes.length > 0) {
433-
// Don't auto-select notes that are in trash unless explicitly viewing trash
434-
const firstNonTrashedNote = filteredNotes.find(note => !note.deleted);
435-
if (firstNonTrashedNote) {
436-
setSelectedNote(firstNonTrashedNote);
437-
} else if (currentView === 'trash') {
438-
// Only select trash notes if we're explicitly viewing trash
439-
setSelectedNote(filteredNotes[0]);
440-
}
441-
}
442-
}, [selectedNote, filteredNotes, currentView]);
443-
444423
// Refetch notes when switching folders to check for new notes from other devices/users
445424
useEffect(() => {
446425
// Skip the initial mount to avoid duplicate fetches
@@ -522,17 +501,15 @@ export function useNotes() {
522501
await restNotesOperations.deleteNote(noteId);
523502

524503
if (selectedNote?.id === noteId) {
525-
const remainingNotes = filteredNotes.filter((note) => note.id !== noteId);
526-
setSelectedNote(remainingNotes.length > 0 ? remainingNotes[0] : null);
504+
setSelectedNote(null);
527505
}
528506
};
529507

530508
const archiveNote = async (noteId: string) => {
531509
await restNotesOperations.archiveNote(noteId);
532510

533511
if (selectedNote?.id === noteId) {
534-
const remainingNotes = filteredNotes.filter((note) => note.id !== noteId);
535-
setSelectedNote(remainingNotes.length > 0 ? remainingNotes[0] : null);
512+
setSelectedNote(null);
536513
}
537514
};
538515

@@ -610,8 +587,7 @@ export function useNotes() {
610587
restNotesOperations.permanentlyDeleteNote(noteId);
611588

612589
if (selectedNote?.id === noteId) {
613-
const remainingNotes = filteredNotes.filter((note) => note.id !== noteId);
614-
setSelectedNote(remainingNotes.length > 0 ? remainingNotes[0] : null);
590+
setSelectedNote(null);
615591
}
616592
};
617593

src/types/layout.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface FilesPanelProps {
4545
onCreateCode?: (templateData?: { language: string; code: string }) => void;
4646
onToggleFolderPanel: () => void;
4747
onEmptyTrash: () => Promise<void>;
48+
onRefresh?: () => Promise<void>;
4849
creatingNote?: boolean;
4950
isMobile?: boolean;
5051
onClose?: () => void;

0 commit comments

Comments
 (0)