Skip to content

Commit 0e28a95

Browse files
committed
fix(review): address PR review findings for keybinds, timers, and graph view
1 parent a59360a commit 0e28a95

8 files changed

Lines changed: 73 additions & 58 deletions

File tree

AUDIT_LOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ This log tracks all significant changes, updates, and versions in the PaperCache
88
**Details/Why:**
99
1. **Version Bump**: Bumped application version to 0.5.6 across `package.json`, `Cargo.toml`, `tauri.conf.json`, and added release notes file `New Features in v0.5.6.md`.
1010
2. **Keybinds Settings Panel**: Created `ShortcutInput.tsx` as a shared component and `KeybindsModal.tsx` as a dedicated settings panel for remapping shortcuts, accessible via Settings. Added new storage keys in `settingsKeys.ts` and updated `useGlobalHotkey.ts` to dynamically match keyboard events against customizable shortcut settings. Refined UI layout so keycaps are centered horizontally and the container/buttons match the main Settings window.
11-
3. **Keybind Updates**: Updated default shortcuts so `Cmd+R` opens Tasks/Reminders and `Cmd+T` opens the countdown Timers panel, aligning with user navigation habits.
12-
4. **Timer Auto-Deletion**: Updated `useTimerStore.ts` and `App.tsx` so that when a countdown timer completes, it schedules a targeted 5-second `setTimeout` to call `removeTimer(id)`, reducing UI clutter. Moved backend `timer-complete` event listener to `App.tsx` so completion notifications and auto-cleanup function globally even when the panel is closed.
13-
5. **Graph View Link Parsing**: Expanded regex detection in `GraphView.tsx` to link notes using standard markdown links (`[Title](Title.md)`) and wikilinks (`[[Title]]`) in addition to `/file` links, and added z-axis centering forces (`centerZ`, `folderZ`) for improved 3D layout stability.
11+
3. **Keybind Updates**: Updated default shortcuts so `Cmd+R` opens Tasks/Reminders and `Cmd+T` opens the countdown Timers panel, aligning with user navigation habits. Preserved cleared shortcuts via `getShortcut` helper distinguishing `null` from `''`. Dynamically generated the shortcuts reference note (`Cmd+/`) and added recording guards in `useGlobalHotkey.ts` and `ShortcutInput.tsx`. Persisted toggle shortcut changes in `Settings.tsx`.
12+
4. **Timer Auto-Deletion**: Updated `useTimerStore.ts` and `App.tsx` so that when a countdown timer completes, it schedules a targeted 5-second `setTimeout` to call `removeTimer(id)`, reducing UI clutter. Moved backend `timer-complete` event listener to `App.tsx` with robust late-resolution cleanup tracking so completion notifications and auto-cleanup function globally even when the panel is closed or unmounted.
13+
5. **Graph View Link Parsing**: Expanded regex detection in `GraphView.tsx` to link notes using standard markdown links (`[Title](Title.md)`) and wikilinks (`[[Title]]`, stripping aliases like `[[Title|Display]]`) in addition to `/file` links, and removed unused `cz` centroid force values to keep layout logic consistent.
1414

1515
**Files changed:** `package.json`, `src-tauri/Cargo.toml`, `src-tauri/tauri.conf.json`, `notes/New Features in v0.5.6.md`, `src/lib/settingsKeys.ts`, `src/components/ShortcutInput.tsx`, `src/components/KeybindsModal.tsx`, `src/store/useAppStore.ts`, `src/App.tsx`, `src/Settings.tsx`, `src/hooks/useGlobalHotkey.ts`, `src/store/useTimerStore.ts`, `src/components/TimersPage.tsx`, `src/GraphView.tsx`, `CHANGELOG.md`, `AUDIT_LOG.md`.
1616

src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ function App() {
7474
useTimerStore.getState().cleanExpiredTimers()
7575

7676
let unlistenTimer: (() => void) | undefined
77+
let isUnmounted = false
7778
listen<string>('timer-complete', (event) => {
7879
const id = event.payload
7980
useTimerStore.getState().completeTimer(id)
@@ -82,10 +83,12 @@ function App() {
8283
.getState()
8384
.addToast({ message: `⏱ Timer done: ${t?.label || ''}`, type: 'success' })
8485
}).then((fn) => {
85-
unlistenTimer = fn
86+
if (isUnmounted) fn()
87+
else unlistenTimer = fn
8688
})
8789

8890
return () => {
91+
isUnmounted = true
8992
disposeUpdateReady()
9093
unlistenTimer?.()
9194
}

src/GraphView.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@ interface GraphLink {
3232
target: string
3333
}
3434

35-
function buildFolderCentroids(
36-
folderNames: string[]
37-
): Map<string, { cx: number; cy: number; cz: number }> {
38-
const centroids = new Map<string, { cx: number; cy: number; cz: number }>()
35+
function buildFolderCentroids(folderNames: string[]): Map<string, { cx: number; cy: number }> {
36+
const centroids = new Map<string, { cx: number; cy: number }>()
3937
const n = folderNames.length
4038
if (n === 0) return centroids
4139
const radius = 60
@@ -44,7 +42,6 @@ function buildFolderCentroids(
4442
centroids.set(folder, {
4543
cx: radius * Math.cos(angle),
4644
cy: radius * Math.sin(angle),
47-
cz: (i % 2 === 0 ? 1 : -1) * (15 * (i % 3)),
4845
})
4946
})
5047
return centroids
@@ -167,7 +164,7 @@ export default function GraphView({
167164
// 3. Match [[<title>]]
168165
const reWiki = /\[\[([^\]]+)\]\]/g
169166
while ((match = reWiki.exec(note.content)) !== null) {
170-
let targetId = match[1].trim().replace(/\\/g, '/')
167+
let targetId = match[1].split('|')[0].trim().replace(/\\/g, '/')
171168
if (!targetId.endsWith('.md')) targetId += '.md'
172169
targets.add(targetId)
173170
}

src/Settings.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect } from 'react'
22
import { getVersion } from '@tauri-apps/api/app'
3-
import { SETTINGS_KEYS } from './lib/settingsKeys'
3+
import { SETTINGS_KEYS, getShortcut } from './lib/settingsKeys'
44
import { useAppStore } from './store/useAppStore'
55
import { useSettingsStore } from './store/useSettingsStore'
66
import { ShortcutInput } from './components/ShortcutInput'
@@ -41,10 +41,10 @@ export default function Settings({ onClose }: { onClose?: () => void }) {
4141
const defaultMod = isHyprland ? 'Alt' : 'CommandOrControl'
4242

4343
const [shortcutNewNote, setShortcutNewNote] = useState(
44-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_NEWNOTE) || `${defaultMod}+Shift+N`
44+
getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE, `${defaultMod}+Shift+N`)
4545
)
4646
const [shortcutToggle, setShortcutToggle] = useState(
47-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TOGGLE) || `${defaultMod}+Shift+C`
47+
getShortcut(SETTINGS_KEYS.SHORTCUT_TOGGLE, `${defaultMod}+Shift+C`)
4848
)
4949

5050
// Startup
@@ -132,18 +132,17 @@ export default function Settings({ onClose }: { onClose?: () => void }) {
132132
}
133133

134134
// Shortcuts
135-
const oldShortcut =
136-
localStorage.getItem('papercache-shortcut-newnote') || `${defaultMod}+Shift+N`
135+
const oldShortcut = getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE, `${defaultMod}+Shift+N`)
137136
if (window.electronAPI.updateGlobalShortcut) {
138137
window.electronAPI.updateGlobalShortcut('new-note', oldShortcut, shortcutNewNote)
139138
}
140-
localStorage.setItem('papercache-shortcut-newnote', shortcutNewNote)
139+
localStorage.setItem(SETTINGS_KEYS.SHORTCUT_NEWNOTE, shortcutNewNote)
141140

142-
const oldToggleShortcut =
143-
localStorage.getItem('papercache-shortcut-toggle') || `${defaultMod}+Shift+C`
141+
const oldToggleShortcut = getShortcut(SETTINGS_KEYS.SHORTCUT_TOGGLE, `${defaultMod}+Shift+C`)
144142
if (window.electronAPI.updateGlobalShortcut) {
145143
window.electronAPI.updateGlobalShortcut('toggle', oldToggleShortcut, shortcutToggle)
146144
}
145+
localStorage.setItem(SETTINGS_KEYS.SHORTCUT_TOGGLE, shortcutToggle)
147146

148147
// Dispatch storage event manually for the same window to pick it up immediately
149148
window.dispatchEvent(new Event('storage'))

src/components/KeybindsModal.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect } from 'react'
2-
import { SETTINGS_KEYS } from '../lib/settingsKeys'
2+
import { SETTINGS_KEYS, getShortcut } from '../lib/settingsKeys'
33
import { useAppStore } from '../store/useAppStore'
44
import { ShortcutInput } from './ShortcutInput'
55

@@ -13,39 +13,39 @@ export function KeybindsModal({ onClose }: KeybindsModalProps) {
1313

1414
// Global Shortcuts
1515
const [shortcutNewNote, setShortcutNewNote] = useState(
16-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_NEWNOTE) || `${defaultMod}+Shift+N`
16+
getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE, `${defaultMod}+Shift+N`)
1717
)
1818
const [shortcutToggle, setShortcutToggle] = useState(
19-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TOGGLE) || `${defaultMod}+Shift+C`
19+
getShortcut(SETTINGS_KEYS.SHORTCUT_TOGGLE, `${defaultMod}+Shift+C`)
2020
)
2121

2222
// In-App Shortcuts
2323
const [shortcutTasks, setShortcutTasks] = useState(
24-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TASKS) || `${defaultMod}+R`
24+
getShortcut(SETTINGS_KEYS.SHORTCUT_TASKS, `${defaultMod}+R`)
2525
)
2626
const [shortcutTimers, setShortcutTimers] = useState(
27-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TIMERS) || `${defaultMod}+T`
27+
getShortcut(SETTINGS_KEYS.SHORTCUT_TIMERS, `${defaultMod}+T`)
2828
)
2929
const [shortcutSearch, setShortcutSearch] = useState(
30-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_SEARCH) || `${defaultMod}+P`
30+
getShortcut(SETTINGS_KEYS.SHORTCUT_SEARCH, `${defaultMod}+P`)
3131
)
3232
const [shortcutGraph, setShortcutGraph] = useState(
33-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_GRAPH) || `${defaultMod}+G`
33+
getShortcut(SETTINGS_KEYS.SHORTCUT_GRAPH, `${defaultMod}+G`)
3434
)
3535
const [shortcutActionMenu, setShortcutActionMenu] = useState(
36-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_ACTION_MENU) || `${defaultMod}+K`
36+
getShortcut(SETTINGS_KEYS.SHORTCUT_ACTION_MENU, `${defaultMod}+K`)
3737
)
3838
const [shortcutExport, setShortcutExport] = useState(
39-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_EXPORT) || `${defaultMod}+E`
39+
getShortcut(SETTINGS_KEYS.SHORTCUT_EXPORT, `${defaultMod}+E`)
4040
)
4141
const [shortcutRef, setShortcutRef] = useState(
42-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_REF) || `${defaultMod}+/`
42+
getShortcut(SETTINGS_KEYS.SHORTCUT_REF, `${defaultMod}+/`)
4343
)
4444
const [shortcutSettings, setShortcutSettings] = useState(
45-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_SETTINGS) || `${defaultMod}+Shift+S`
45+
getShortcut(SETTINGS_KEYS.SHORTCUT_SETTINGS, `${defaultMod}+Shift+S`)
4646
)
4747
const [shortcutNewNoteInApp, setShortcutNewNoteInApp] = useState(
48-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_NEWNOTE_INAPP) || `${defaultMod}+N`
48+
getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE_INAPP, `${defaultMod}+N`)
4949
)
5050

5151
useEffect(() => {

src/components/ShortcutInput.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ export function ShortcutInput({
2222
} else {
2323
if (window.electronAPI.resumeShortcuts) window.electronAPI.resumeShortcuts()
2424
}
25-
}, [recording])
25+
return () => {
26+
if (recording) {
27+
if (window.electronAPI.resumeShortcuts) window.electronAPI.resumeShortcuts()
28+
setIsRecordingShortcut(false)
29+
}
30+
}
31+
}, [recording, setIsRecordingShortcut])
2632

2733
const renderShortcutDisplay = (shortcut: string) => {
2834
if (!shortcut)

src/hooks/useGlobalHotkey.ts

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect } from 'react'
22
import { useAppStore } from '../store/useAppStore'
3-
import { SETTINGS_KEYS } from '../lib/settingsKeys'
3+
import { SETTINGS_KEYS, getShortcut } from '../lib/settingsKeys'
44
import { getCurrentWindow } from '@tauri-apps/api/window'
55

66
function matchShortcut(e: KeyboardEvent, configuredStr: string): boolean {
@@ -47,6 +47,7 @@ export function useGlobalHotkey() {
4747
useEffect(() => {
4848
const handleGlobalKeyDown = async (e: KeyboardEvent) => {
4949
const state = useAppStore.getState()
50+
if (state.isRecordingShortcut) return
5051
const defaultMod = isHyprland ? 'Alt' : 'CommandOrControl'
5152

5253
if (e.key === 'Escape') {
@@ -90,18 +91,17 @@ export function useGlobalHotkey() {
9091
}
9192

9293
// Read current configured shortcuts or defaults
93-
const scSettings =
94-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_SETTINGS) || `${defaultMod}+Shift+S`
95-
const scGraph = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_GRAPH) || `${defaultMod}+G`
96-
const scNewNoteInApp =
97-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_NEWNOTE_INAPP) || `${defaultMod}+N`
98-
const scExport = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_EXPORT) || `${defaultMod}+E`
99-
const scSearch = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_SEARCH) || `${defaultMod}+P`
100-
const scTasks = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TASKS) || `${defaultMod}+R`
101-
const scTimers = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TIMERS) || `${defaultMod}+T`
102-
const scActionMenu =
103-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_ACTION_MENU) || `${defaultMod}+K`
104-
const scRef = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_REF) || `${defaultMod}+/`
94+
const scSettings = getShortcut(SETTINGS_KEYS.SHORTCUT_SETTINGS, `${defaultMod}+Shift+S`)
95+
const scGraph = getShortcut(SETTINGS_KEYS.SHORTCUT_GRAPH, `${defaultMod}+G`)
96+
const scNewNoteInApp = getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE_INAPP, `${defaultMod}+N`)
97+
const scExport = getShortcut(SETTINGS_KEYS.SHORTCUT_EXPORT, `${defaultMod}+E`)
98+
const scSearch = getShortcut(SETTINGS_KEYS.SHORTCUT_SEARCH, `${defaultMod}+P`)
99+
const scTasks = getShortcut(SETTINGS_KEYS.SHORTCUT_TASKS, `${defaultMod}+R`)
100+
const scTimers = getShortcut(SETTINGS_KEYS.SHORTCUT_TIMERS, `${defaultMod}+T`)
101+
const scActionMenu = getShortcut(SETTINGS_KEYS.SHORTCUT_ACTION_MENU, `${defaultMod}+K`)
102+
const scRef = getShortcut(SETTINGS_KEYS.SHORTCUT_REF, `${defaultMod}+/`)
103+
const scToggle = getShortcut(SETTINGS_KEYS.SHORTCUT_TOGGLE, `${defaultMod}+Shift+C`)
104+
const scNewNote = getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE, `${defaultMod}+Shift+N`)
105105

106106
// Settings Shortcut
107107
if (matchShortcut(e, scSettings)) {
@@ -185,20 +185,26 @@ export function useGlobalHotkey() {
185185
if (existingIndex !== -1) {
186186
setCurrentNoteIndex(existingIndex)
187187
} else {
188+
const fmt = (sc: string) =>
189+
sc
190+
.replace(/CommandOrControl/g, isHyprland ? 'Alt' : 'Cmd')
191+
.replace(/Command/g, 'Cmd')
192+
.replace(/Control/g, 'Ctrl')
193+
188194
const shortcutsContent = `# Shortcuts
189195
190-
- \`Cmd+Shift+C\` — Toggle visibility (global, configurable)
191-
- \`Cmd+Shift+N\` — New note (global, configurable)
192-
- \`Cmd+Shift+S\` — Open settings
193-
- \`Cmd+N\` — New note
194-
- \`Cmd+R\` — Tasks / Reminders
195-
- \`Cmd+T\` — Timers Panel
196-
- \`Cmd+K\` — Main action menu
197-
- \`Cmd+P\` — Search notes
198-
- \`Cmd+G\` — Graph view
196+
- \`${fmt(scToggle)}\` — Toggle visibility (global, configurable)
197+
- \`${fmt(scNewNote)}\` — New note (global, configurable)
198+
- \`${fmt(scSettings)}\` — Open settings
199+
- \`${fmt(scNewNoteInApp)}\` — New note
200+
- \`${fmt(scTasks)}\` — Tasks / Reminders
201+
- \`${fmt(scTimers)}\` — Timers Panel
202+
- \`${fmt(scActionMenu)}\` — Main action menu
203+
- \`${fmt(scSearch)}\` — Search notes
204+
- \`${fmt(scGraph)}\` — Graph view
199205
- \`Cmd+F\` — Search in graph
200-
- \`Cmd+E\` — Export note
201-
- \`Cmd+/\` — Show this shortcuts reference
206+
- \`${fmt(scExport)}\` — Export note
207+
- \`${fmt(scRef)}\` — Show this shortcuts reference
202208
- \`Esc\` — Close menus / modals
203209
204210
### Slash Commands
@@ -222,12 +228,11 @@ Type \`/\` in the editor for inline suggestions:
222228

223229
// Sync global shortcut on load
224230
const defaultMod = isHyprland ? 'Alt' : 'CommandOrControl'
225-
const shortcut = localStorage.getItem(SETTINGS_KEYS.SHORTCUT_NEWNOTE) || `${defaultMod}+Shift+N`
231+
const shortcut = getShortcut(SETTINGS_KEYS.SHORTCUT_NEWNOTE, `${defaultMod}+Shift+N`)
226232
if (window.electronAPI.updateGlobalShortcut) {
227233
window.electronAPI.updateGlobalShortcut('new-note', '', shortcut)
228234
}
229-
const toggleShortcut =
230-
localStorage.getItem(SETTINGS_KEYS.SHORTCUT_TOGGLE) || `${defaultMod}+Shift+C`
235+
const toggleShortcut = getShortcut(SETTINGS_KEYS.SHORTCUT_TOGGLE, `${defaultMod}+Shift+C`)
231236
if (window.electronAPI.updateGlobalShortcut) {
232237
window.electronAPI.updateGlobalShortcut('toggle', '', toggleShortcut)
233238
}

src/lib/settingsKeys.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ export const SETTINGS_KEYS = {
2727
LAUNCH_STARTUP: 'papercache-launch-startup',
2828
NOTIFIED_REMINDERS: 'papercache_notified',
2929
} as const
30+
31+
export function getShortcut(key: string, fallback: string): string {
32+
const val = localStorage.getItem(key)
33+
return val !== null ? val : fallback
34+
}

0 commit comments

Comments
 (0)