diff --git a/package-lock.json b/package-lock.json index a419b7f..c4379f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,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", @@ -700,6 +701,13 @@ "node": ">=20.19.0" } }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -2290,6 +2298,42 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.185.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.0.tgz", + "integrity": "sha512-O2Uy8Cj4Nonr8dWUUbifMdPe8B0Mq7EdOHb89S4+kjUw/KhbjTZrUuYlrQ1bpUKG+EP9QJnN7qNxbHGlGoLHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.2", + "meshoptimizer": "~1.1.1" + } + }, + "node_modules/@types/three/node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", @@ -3800,6 +3844,13 @@ } } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4773,6 +4824,13 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "dev": true, + "license": "MIT" + }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", diff --git a/package.json b/package.json index 0a74087..2dc75e1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.tsx b/src/App.tsx index 5f0e9df..dabe65d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -173,7 +173,7 @@ function App() { } as React.CSSProperties if (bgType === 'color') { - containerStyle['--bg-color' as string] = bgColor + ;(containerStyle as Record)['--bg-color'] = bgColor containerStyle.backgroundImage = 'none' } else if (bgType === 'image' && bgImage) { containerStyle.backgroundImage = `url(${bgImage})` @@ -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) diff --git a/src/GraphView.tsx b/src/GraphView.tsx index 135f592..07e0470 100644 --- a/src/GraphView.tsx +++ b/src/GraphView.tsx @@ -87,7 +87,7 @@ export default function GraphView({ bgColor, accentColor, }: GraphViewProps) { - const fgRef = useRef | undefined>(undefined) + const fgRef = useRef>(null) const draggedNodesRef = useRef>(new Set()) const graphDataRef = useRef<{ nodes: GraphNode[]; links: GraphLink[] }>({ nodes: [], links: [] }) @@ -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' } @@ -180,14 +180,14 @@ 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) @@ -195,7 +195,7 @@ export default function GraphView({ 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) } @@ -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) @@ -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' @@ -510,7 +511,7 @@ export default function GraphView({ } > setValues((prev) => ({ ...prev, [sc.key]: val }))} /> ))} @@ -187,7 +187,7 @@ export function KeybindsModal({ onClose }: KeybindsModalProps) { setValues((prev) => ({ ...prev, [sc.key]: val }))} /> ))} diff --git a/src/components/NoteSearch.tsx b/src/components/NoteSearch.tsx index a38b0f5..847cac0 100644 --- a/src/components/NoteSearch.tsx +++ b/src/components/NoteSearch.tsx @@ -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[] => { @@ -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) @@ -404,7 +404,7 @@ export function NoteSearch() { width: 8, height: 8, borderRadius: '50%', - backgroundColor: getFolderColor(pathParts[0]), + backgroundColor: getFolderColor(pathParts[0]!), }} /> {pathParts.join(' / ')} diff --git a/src/components/NoteTitleBar.tsx b/src/components/NoteTitleBar.tsx index 5984687..d530064 100644 --- a/src/components/NoteTitleBar.tsx +++ b/src/components/NoteTitleBar.tsx @@ -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) diff --git a/src/hooks/useNoteStorage.ts b/src/hooks/useNoteStorage.ts index 9883610..73ea997 100644 --- a/src/hooks/useNoteStorage.ts +++ b/src/hooks/useNoteStorage.ts @@ -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]) diff --git a/src/hooks/useReminders.test.ts b/src/hooks/useReminders.test.ts index 3fbc814..8b7eea1 100644 --- a/src/hooks/useReminders.test.ts +++ b/src/hooks/useReminders.test.ts @@ -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 () => { @@ -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) }) @@ -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) }) diff --git a/src/hooks/useVariables.ts b/src/hooks/useVariables.ts index fa17578..9c1d276 100644 --- a/src/hooks/useVariables.ts +++ b/src/hooks/useVariables.ts @@ -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() } } } diff --git a/src/lib/editor/MathEvaluator.test.ts b/src/lib/editor/MathEvaluator.test.ts index 83d7af0..b66c557 100644 --- a/src/lib/editor/MathEvaluator.test.ts +++ b/src/lib/editor/MathEvaluator.test.ts @@ -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') }) diff --git a/src/lib/editor/MathEvaluator.ts b/src/lib/editor/MathEvaluator.ts index 27ef220..2a195b2 100644 --- a/src/lib/editor/MathEvaluator.ts +++ b/src/lib/editor/MathEvaluator.ts @@ -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('=')) { @@ -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 diff --git a/src/lib/editor/VariableScope.ts b/src/lib/editor/VariableScope.ts index 6fa6f3c..916ff50 100644 --- a/src/lib/editor/VariableScope.ts +++ b/src/lib/editor/VariableScope.ts @@ -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() diff --git a/src/lib/editor/markdownPlugin.ts b/src/lib/editor/markdownPlugin.ts index a57e074..95ab569 100644 --- a/src/lib/editor/markdownPlugin.ts +++ b/src/lib/editor/markdownPlugin.ts @@ -53,7 +53,7 @@ export const markdownPlugin = ViewPlugin.fromClass( // Lists const reList = /^(\s*)\*\s+/gm while ((match = reList.exec(text)) !== null) { - const start = from + match.index + match[1].length + const start = from + match.index + match[1]!.length const end = start + 1 if (!isCursorInMatch(start, end + 1)) { decos.push({ from: start, to: end, deco: Decoration.replace({}) }) @@ -78,12 +78,12 @@ export const markdownPlugin = ViewPlugin.fromClass( linkRanges.push({ from: start, to: end }) const textStart = start + 1 - const textEnd = start + 1 + match[1].length + const textEnd = start + 1 + match[1]!.length const urlStart = textEnd const urlEnd = end let isFile = false - let linkPath = match[2].trim() + let linkPath = match[2]!.trim() if (linkPath.startsWith('/file')) { isFile = true diff --git a/src/lib/editor/taskPlugin.ts b/src/lib/editor/taskPlugin.ts index 5490b15..173460b 100644 --- a/src/lib/editor/taskPlugin.ts +++ b/src/lib/editor/taskPlugin.ts @@ -35,7 +35,7 @@ export const taskPlugin = ViewPlugin.fromClass( while ((match = reRem.exec(text)) !== null) { const start = from + match.index const end = start + match[0].length - const isChecked = match[1] === 'task-done' + const isChecked = match[1]! === 'task-done' const line = view.state.doc.lineAt(start) let isOverdue = false @@ -114,7 +114,7 @@ class RemConverterPlugin implements PluginValue { changes.push({ from: match.index, to: match.index + match[0].length, - insert: `/${match[1]} ${timestamp} `, + insert: `/${match[1]!} ${timestamp} `, }) } @@ -123,7 +123,7 @@ class RemConverterPlugin implements PluginValue { /(?<=^|[ \t])(\/(?:task|task-done)[^\n]*?@\s*)((?:[0-9]+[smhd])+|tmrw)([ \t]+|\n|(?:\r\n))/gm while ((match = reShort.exec(docStr)) !== null) { const now = new Date() - const short = match[2] + const short = match[2]! if (short === 'tmrw') { now.setDate(now.getDate() + 1) now.setHours(9, 0, 0, 0) @@ -131,8 +131,8 @@ class RemConverterPlugin implements PluginValue { const partRe = /([0-9]+)([smhd])/g let partMatch while ((partMatch = partRe.exec(short)) !== null) { - const val = parseInt(partMatch[1]) - const unit = partMatch[2] + const val = parseInt(partMatch[1]!) + const unit = partMatch[2]! if (unit === 's') now.setSeconds(now.getSeconds() + val) else if (unit === 'm') now.setMinutes(now.getMinutes() + val) else if (unit === 'h') now.setHours(now.getHours() + val) @@ -149,8 +149,8 @@ class RemConverterPlugin implements PluginValue { const absoluteDate = `${dd}-${mm}-${yyyy} ${hh}:${mins}` changes.push({ - from: match.index + match[1].length, - to: match.index + match[1].length + match[2].length, + from: match.index + match[1]!.length, + to: match.index + match[1]!.length + match[2]!.length, insert: absoluteDate, }) } diff --git a/src/lib/editor/variablePlugin.ts b/src/lib/editor/variablePlugin.ts index 93aa27a..cbcc14b 100644 --- a/src/lib/editor/variablePlugin.ts +++ b/src/lib/editor/variablePlugin.ts @@ -53,7 +53,7 @@ export const variablePlugin = ViewPlugin.fromClass( decos.push({ from: start, to: end, - deco: Decoration.replace({ widget: new VariableWidget(String(scope[match[1]])) }), + deco: Decoration.replace({ widget: new VariableWidget(String(scope[match[1]!])) }), }) } else { decos.push({ diff --git a/src/lib/evaluator.ts b/src/lib/evaluator.ts index 075ecac..fb94764 100644 --- a/src/lib/evaluator.ts +++ b/src/lib/evaluator.ts @@ -29,7 +29,7 @@ function tokenize(input: string): Token[] { const tokens: Token[] = [] let i = 0 while (i < input.length) { - const ch = input[i] + const ch = input[i]! if (ch === ' ' || ch === '\t') { i++ continue @@ -37,9 +37,9 @@ function tokenize(input: string): Token[] { if (ch >= '0' && ch <= '9') { let num = '' let dotCount = 0 - while (i < input.length && ((input[i] >= '0' && input[i] <= '9') || input[i] === '.')) { - if (input[i] === '.') dotCount++ - num += input[i] + while (i < input.length && ((input[i]! >= '0' && input[i]! <= '9') || input[i]! === '.')) { + if (input[i]! === '.') dotCount++ + num += input[i]! i++ } if (dotCount > 1) { @@ -52,12 +52,12 @@ function tokenize(input: string): Token[] { let ident = '' while ( i < input.length && - ((input[i] >= 'a' && input[i] <= 'z') || - (input[i] >= 'A' && input[i] <= 'Z') || - (input[i] >= '0' && input[i] <= '9') || - input[i] === '_') + ((input[i]! >= 'a' && input[i]! <= 'z') || + (input[i]! >= 'A' && input[i]! <= 'Z') || + (input[i]! >= '0' && input[i]! <= '9') || + input[i]! === '_') ) { - ident += input[i] + ident += input[i]! i++ } tokens.push({ type: IDENTIFIER, value: ident }) diff --git a/src/store/useAppStore.test.ts b/src/store/useAppStore.test.ts index 7b1dec9..4ad8b87 100644 --- a/src/store/useAppStore.test.ts +++ b/src/store/useAppStore.test.ts @@ -33,7 +33,7 @@ describe('useAppStore', () => { // Set notes directly setNotes([{ id: '1', content: 'test', mtime: 123 }]) expect(useAppStore.getState().notes).toHaveLength(1) - expect(useAppStore.getState().notes[0].content).toBe('test') + expect(useAppStore.getState().notes[0]!.content).toBe('test') // Functional update setNotes((prev) => [...prev, { id: '2', content: 'test2', mtime: 456 }]) diff --git a/src/utils.test.ts b/src/utils.test.ts index 383b0c6..2e65130 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -37,13 +37,13 @@ describe('utils', () => { const color1 = getFolderColor('f1') const color2 = getFolderColor('f2') - const hue1 = parseInt(color1.match(/hsl\((\d+)/)![1]) - const hue2 = parseInt(color2.match(/hsl\((\d+)/)![1]) + const hue1 = parseInt(color1.match(/hsl\((\d+)/)![1]!) + const hue2 = parseInt(color2.match(/hsl\((\d+)/)![1]!) expect(hue1).not.toBe(hue2) const color3 = getFolderColor('f3') - const hue3 = parseInt(color3.match(/hsl\((\d+)/)![1]) + const hue3 = parseInt(color3.match(/hsl\((\d+)/)![1]!) expect(hue3).not.toBe(hue1) expect(hue3).not.toBe(hue2) }) diff --git a/src/utils.ts b/src/utils.ts index 057646d..bc8ec4b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,7 +12,7 @@ export const getFolderColor = (str: string): string => { const usedHues = Object.values(colors) .map((c) => { const match = c.match(/hsl\((\d+)/) - return match ? parseInt(match[1]) : null + return match ? parseInt(match[1]!) : null }) .filter((h) => h !== null) as number[] @@ -22,11 +22,11 @@ export const getFolderColor = (str: string): string => { let maxDist = 0 for (let i = 0; i < usedHues.length; i++) { const next = (i + 1) % usedHues.length - let dist = usedHues[next] - usedHues[i] + let dist = usedHues[next]! - usedHues[i]! if (dist <= 0) dist += 360 if (dist > maxDist) { maxDist = dist - bestHue = (usedHues[i] + dist / 2) % 360 + bestHue = (usedHues[i]! + dist / 2) % 360 } } } else {