Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@types/node": "^24.12.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.185.0",
"@uiw/react-codemirror": "^4.25.10",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/coverage-v8": "^4.1.9",
Expand Down
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ function App() {
} as React.CSSProperties

if (bgType === 'color') {
containerStyle['--bg-color' as string] = bgColor
;(containerStyle as Record<string, string>)['--bg-color'] = bgColor
containerStyle.backgroundImage = 'none'
} else if (bgType === 'image' && bgImage) {
containerStyle.backgroundImage = `url(${bgImage})`
Expand Down Expand Up @@ -211,7 +211,7 @@ function App() {
const currentNotes = useAppStore.getState().notes
const idx = currentNotes.findIndex((n) => n.id === noteId)
if (idx === -1) return
const note = currentNotes[idx]
const note = currentNotes[idx]!
const newContent = note.content.slice(0, from) + insert + note.content.slice(to)

window.electronAPI.saveNote(note.id, newContent)
Expand Down
25 changes: 13 additions & 12 deletions src/GraphView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function GraphView({
bgColor,
accentColor,
}: GraphViewProps) {
const fgRef = useRef<ForceGraphMethods<GraphNode, GraphLink> | undefined>(undefined)
const fgRef = useRef<ForceGraphMethods<GraphNode, GraphLink>>(null)

const draggedNodesRef = useRef<Set<string>>(new Set())
const graphDataRef = useRef<{ nodes: GraphNode[]; links: GraphLink[] }>({ nodes: [], links: [] })
Expand Down Expand Up @@ -158,12 +158,12 @@ export default function GraphView({
const nodes: GraphNode[] = notes.map((n) => {
const isAuto = /^\d+\.md$/.test(n.id)
let title = n.id.replace(/\.md$/, '')
const folder = n.id.includes('/') ? n.id.split('/')[0] : ''
const folder = n.id.includes('/') ? n.id.split('/')[0]! : ''

if (isAuto) {
title =
n.content
.split('\n')[0]
.split('\n')[0]!
.trim()
.replace(/^#+\s*/, '') || 'New Note'
}
Expand All @@ -180,22 +180,22 @@ export default function GraphView({
const reFile = /\]\(\/file\s+([^)]+)\)/g
let match
while ((match = reFile.exec(note.content)) !== null) {
let targetId = match[1].trim().replace(/\\/g, '/')
let targetId = match[1]!.trim().replace(/\\/g, '/')
if (!targetId.endsWith('.md')) targetId += '.md'
targets.add(targetId)
}

const reMd = /\]\(([^)]+\.md)\)/g
while ((match = reMd.exec(note.content)) !== null) {
let targetId = match[1].trim().replace(/\\/g, '/')
let targetId = match[1]!.trim().replace(/\\/g, '/')
if (targetId.startsWith('./')) targetId = targetId.slice(2)
if (targetId.startsWith('/')) targetId = targetId.slice(1)
targets.add(targetId)
}

const reWiki = /\[\[([^\]]+)\]\]/g
while ((match = reWiki.exec(note.content)) !== null) {
let targetId = match[1].split('|')[0].trim().replace(/\\/g, '/')
let targetId = match[1]!.split('|')[0]!.trim().replace(/\\/g, '/')
if (!targetId.endsWith('.md')) targetId += '.md'
targets.add(targetId)
}
Expand Down Expand Up @@ -299,8 +299,9 @@ export default function GraphView({
}, [])

const nodeThreeObject = useCallback(
(node: GraphNode) => {
const color = node.folder ? getFolderColor(node.folder) : accentColor
(node: object) => {
const gNode = node as GraphNode
const color = gNode.folder ? getFolderColor(gNode.folder) : accentColor
const group = new THREE.Group()

const geometry = new THREE.CircleGeometry(NODE_RADIUS, 32)
Expand All @@ -315,9 +316,9 @@ export default function GraphView({
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, 256, 64)
const displayName =
node.name.length > MAX_NAME_LENGTH
? node.name.slice(0, MAX_NAME_LENGTH - 3) + '…'
: node.name
gNode.name.length > MAX_NAME_LENGTH
? gNode.name.slice(0, MAX_NAME_LENGTH - 3) + '…'
: gNode.name
ctx.fillStyle = textColor
ctx.font = `bold ${LABEL_FONT_SIZE}px sans-serif`
ctx.textAlign = 'center'
Expand Down Expand Up @@ -510,7 +511,7 @@ export default function GraphView({
}
>
<ForceGraph3D
ref={fgRef}
ref={fgRef as never}
graphData={graphData}
numDimensions={2}
nodeLabel="name"
Expand Down
10 changes: 5 additions & 5 deletions src/components/KeybindsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ export function KeybindsModal({ onClose }: KeybindsModalProps) {
if (sc.section === 'global' && sc.action && sc.oldShortcutStorageKey) {
const oldShortcut = localStorage.getItem(sc.oldShortcutStorageKey) || sc.defaultKey
if (window.electronAPI.updateGlobalShortcut) {
window.electronAPI.updateGlobalShortcut(sc.action, oldShortcut, values[sc.key])
window.electronAPI.updateGlobalShortcut(sc.action, oldShortcut, values[sc.key]!)
}
localStorage.setItem(sc.oldShortcutStorageKey, values[sc.key])
localStorage.setItem(sc.oldShortcutStorageKey, values[sc.key]!)
}
localStorage.setItem(sc.storageKey, values[sc.key])
localStorage.setItem(sc.storageKey, values[sc.key]!)
}

useAppStore
Expand Down Expand Up @@ -175,7 +175,7 @@ export function KeybindsModal({ onClose }: KeybindsModalProps) {
<KeybindRow
key={sc.key}
label={sc.label}
value={values[sc.key]}
value={values[sc.key]!}
onChange={(val) => setValues((prev) => ({ ...prev, [sc.key]: val }))}
/>
))}
Expand All @@ -187,7 +187,7 @@ export function KeybindsModal({ onClose }: KeybindsModalProps) {
<KeybindRow
key={sc.key}
label={sc.label}
value={values[sc.key]}
value={values[sc.key]!}
onChange={(val) => setValues((prev) => ({ ...prev, [sc.key]: val }))}
/>
))}
Expand Down
6 changes: 3 additions & 3 deletions src/components/NoteSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function NoteSearch() {
const getNoteTitle = (n: Note) => {
const isAuto = /^\d+\.md$/.test(n.id)
const fileName = n.id.replace(/\.md$/, '').split('/').pop() || ''
return isAuto ? n.content.split('\n')[0].trim() || 'New Note' : fileName
return isAuto ? n.content.split('\n')[0]!.trim() || 'New Note' : fileName
}

const getNoteTags = (n: Note): string[] => {
Expand Down Expand Up @@ -197,7 +197,7 @@ export function NoteSearch() {
e.preventDefault()
if (showNoteActionMenu) return
if (filteredNotes.length > 0) {
const selNote = filteredNotes[searchSelectedIndex]
const selNote = filteredNotes[searchSelectedIndex]!
const idx = notes.findIndex((note) => note.id === selNote.id)
if (idx !== -1) setCurrentNoteIndex(idx)
setShowNoteSearch(false)
Expand Down Expand Up @@ -404,7 +404,7 @@ export function NoteSearch() {
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: getFolderColor(pathParts[0]),
backgroundColor: getFolderColor(pathParts[0]!),
}}
/>
<span className="ns-folder">{pathParts.join(' / ')}</span>
Expand Down
4 changes: 2 additions & 2 deletions src/components/NoteTitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export function NoteTitleBar() {

const isAutoNamed = /^\d+\.md$/.test(activeNote.id)
const displayTitle = isAutoNamed
? activeNote.content.split('\n')[0].trim() || 'New Note'
: activeNote.id.split('/').pop() || ''
? activeNote.content.split('\n')[0]!.trim() || 'New Note'
: activeNote.id.split('/').pop()! || ''

const startRename = () => {
setRenameValue(displayTitle)
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useNoteStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useNoteStorage() {
// Save current note index to localStorage
useEffect(() => {
if (notes.length > 0 && currentNoteIndex >= 0 && currentNoteIndex < notes.length) {
localStorage.setItem('papercache-last-open-note', notes[currentNoteIndex].id)
localStorage.setItem('papercache-last-open-note', notes[currentNoteIndex]!.id)
}
}, [currentNoteIndex, notes])

Expand Down
10 changes: 5 additions & 5 deletions src/hooks/useReminders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ describe('useReminders', () => {

// Should have called the backend to schedule the reminder
expect(scheduleRemindersMock).toHaveBeenCalledTimes(1)
const reminders = scheduleRemindersMock.mock.calls[0][0] as {
const reminders = scheduleRemindersMock.mock.calls[0]![0] as {
key: string
label: string
dueAt: number
}[]
expect(reminders.length).toBe(1)
expect(reminders[0].label).toBe('Buy bread')
expect(reminders[0].dueAt).toBeGreaterThan(Date.now())
expect(reminders[0]!.label).toBe('Buy bread')
expect(reminders[0]!.dueAt).toBeGreaterThan(Date.now())
})

it('should NOT schedule past-due reminders (already notified by backend on last run)', async () => {
Expand All @@ -73,7 +73,7 @@ describe('useReminders', () => {

// Called but with empty array – past reminders are not re-scheduled
expect(scheduleRemindersMock).toHaveBeenCalledTimes(1)
const reminders = scheduleRemindersMock.mock.calls[0][0] as unknown[]
const reminders = scheduleRemindersMock.mock.calls[0]![0] as unknown[]
expect(reminders.length).toBe(0)
})

Expand All @@ -90,7 +90,7 @@ describe('useReminders', () => {

renderHook(() => useReminders())

const reminders = scheduleRemindersMock.mock.calls[0][0] as unknown[]
const reminders = scheduleRemindersMock.mock.calls[0]![0] as unknown[]
expect(reminders.length).toBe(0)
})

Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export function useVariables() {
for (const note of notes) {
let varMatch
while ((varMatch = reVar.exec(note.content)) !== null) {
const name = varMatch[1]
const name = varMatch[1]!
try {
globals[name] = evaluate(varMatch[2], globals)
globals[name] = evaluate(varMatch[2]!, globals)
} catch (e) {
// eslint-disable-next-line no-console
console.error(`useVariables evaluation error for ${name}:`, e)
globals[name] = varMatch[2].trim()
globals[name] = varMatch[2]!.trim()
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/editor/MathEvaluator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { evaluateMath } from './MathEvaluator'
test('evaluates simple math', () => {
const changes = evaluateMath('25 * 4 =', {})
expect(changes.length).toBe(1)
expect(changes[0].insert).toBe('\u200B100')
expect(changes[0]!.insert).toBe('\u200B100')
})

test('re-evaluates existing math', () => {
const changes = evaluateMath('25 * 5 =\u200B100', {})
expect(changes.length).toBe(1)
expect(changes[0].insert).toBe('125')
expect(changes[0]!.insert).toBe('125')
})
6 changes: 3 additions & 3 deletions src/lib/editor/MathEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function evaluateMath(
const lines = docStr.split('\n')
let offset = 0
for (let i = 0; i < lines.length; i++) {
const text = lines[i]
const text = lines[i]!
const lineLen = text.length

if (!text.includes('\u200B') && text.trim().endsWith('=')) {
Expand Down Expand Up @@ -49,8 +49,8 @@ export function evaluateMath(
const reCalc = /^(.*?=\s*)\u200B(.*)$/gm
let calcMatch
while ((calcMatch = reCalc.exec(docStr)) !== null) {
const exprPart = calcMatch[1]
const oldResult = calcMatch[2]
const exprPart = calcMatch[1]!
const oldResult = calcMatch[2]!
const fullExpr = exprPart.replace(/=\s*$/, '').trim()
if (fullExpr) {
let newResult: string | null = null
Expand Down
6 changes: 3 additions & 3 deletions src/lib/editor/VariableScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ export class VariableScope {
const globalVars = useVariableStore.getState().getGlobals() || {}

while ((varMatch = reVar.exec(docStr)) !== null) {
const name = varMatch[1]
const name = varMatch[1]!
try {
const mergedScope = Object.assign({}, globalVars, newScope)
const val = evaluate(varMatch[2], mergedScope)
const val = evaluate(varMatch[2]!, mergedScope)
newScope[name] = val
} catch (e) {
// eslint-disable-next-line no-console
console.error(`VariableScope evaluation error for ${name}:`, e)
newScope[name] = varMatch[2].trim()
newScope[name] = varMatch[2]!.trim()
}

const currentNoteScope = useVariableStore.getState().getNoteScope()
Expand Down
Loading
Loading