diff --git a/biome.json b/biome.json index bc5a279b5..20771d458 100644 --- a/biome.json +++ b/biome.json @@ -8,7 +8,12 @@ "rules": { "recommended": true, "correctness": { - "useExhaustiveDependencies": "warn" + "useExhaustiveDependencies": { + "level": "warn", + "options": { + "hooks": [{ "name": "useNavigate", "stableResult": true }] + } + } } } }, diff --git a/first-run.js b/first-run.js index 81ec75c0e..c81ff2b71 100644 --- a/first-run.js +++ b/first-run.js @@ -1,7 +1,7 @@ const { app, dialog } = require('electron'); -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); async function onFirstRunMaybe() { if (isFirstRun()) { @@ -49,7 +49,7 @@ function isFirstRun() { fs.writeFileSync(configPath, ''); } catch (error) { - console.warn(`First run: Unable to write firstRun file`, error); + console.warn('First run: Unable to write firstRun file', error); } return true; diff --git a/main.js b/main.js index 832a0162a..ddf8ab0b7 100644 --- a/main.js +++ b/main.js @@ -2,7 +2,7 @@ const { ipcMain, app, nativeTheme } = require('electron'); const { menubar } = require('menubar'); const { autoUpdater } = require('electron-updater'); const { onFirstRunMaybe } = require('./first-run'); -const path = require('path'); +const path = require('node:path'); require('@electron/remote/main').initialize(); diff --git a/src/__mocks__/@electron/remote.js b/src/__mocks__/@electron/remote.js index 5f7c61648..c2250c249 100644 --- a/src/__mocks__/@electron/remote.js +++ b/src/__mocks__/@electron/remote.js @@ -5,6 +5,7 @@ class BrowserWindow { if (!instance) { instance = this; } + // biome-ignore lint/correctness/noConstructorReturn: This is a mock class return instance; } loadURL = jest.fn(); diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx index 53d45818f..760b51cfc 100644 --- a/src/components/Logo.tsx +++ b/src/components/Logo.tsx @@ -13,7 +13,7 @@ const DARK_GRADIENT_END = '#555B6E'; export const Logo = ({ isDark, onClick, className = '', ...props }: IProps) => ( onClick && onClick()} + onClick={() => onClick?.()} xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500" @@ -32,11 +32,11 @@ export const Logo = ({ isDark, onClick, className = '', ...props }: IProps) => ( + /> + /> @@ -48,7 +48,7 @@ export const Logo = ({ isDark, onClick, className = '', ...props }: IProps) => ( })`} fillRule="nonzero" > - + diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx index b7a2d63ee..98cfcdf2a 100644 --- a/src/components/NotificationRow.test.tsx +++ b/src/components/NotificationRow.test.tsx @@ -22,7 +22,7 @@ describe('components/NotificationRow.tsx', () => { it('should render itself & its children', async () => { jest .spyOn(global.Date, 'now') - .mockImplementation(() => new Date('2014').valueOf()); + .mockImplementation(() => new Date('2024').valueOf()); const props = { notification: mockedSingleNotification, @@ -34,7 +34,9 @@ describe('components/NotificationRow.tsx', () => { }); it('should render itself & its children without avatar', async () => { - (global as any).Date.now = jest.fn(() => new Date('2024')); + jest + .spyOn(global.Date, 'now') + .mockImplementation(() => new Date('2024').valueOf()); const mockNotification = mockedSingleNotification; mockNotification.subject.user = null; diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index 8968245e4..58e7fe31a 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -5,7 +5,13 @@ import { ReadIcon, } from '@primer/octicons-react'; import { formatDistanceToNow, parseISO } from 'date-fns'; -import { type FC, type MouseEvent, useCallback, useContext } from 'react'; +import { + type FC, + type KeyboardEvent, + type MouseEvent, + useCallback, + useContext, +} from 'react'; import { AppContext } from '../context/App'; import type { Notification } from '../typesGithub'; @@ -55,7 +61,9 @@ export const NotificationRow: FC = ({ notification, hostname }) => { unsubscribeNotification(notification.id, hostname); }; - const openUserProfile = (event: MouseEvent) => { + const openUserProfile = ( + event: MouseEvent | KeyboardEvent, + ) => { // Don't trigger onClick of parent element. event.stopPropagation(); @@ -89,7 +97,11 @@ export const NotificationRow: FC = ({ notification, hostname }) => { - pressTitle()}> + pressTitle()} + onKeyDown={() => pressTitle()} + > = ({ notification, hostname }) => { {notification.subject.user ? ( - + ) : ( @@ -128,6 +145,7 @@ export const NotificationRow: FC = ({ notification, hostname }) => { markNotificationDone(notification.id, hostname)} @@ -136,6 +154,7 @@ export const NotificationRow: FC = ({ notification, hostname }) => { = ({ notification, hostname }) => { markNotificationRead(notification.id, hostname)} diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx index 80847e70f..28bef678f 100644 --- a/src/components/Repository.tsx +++ b/src/components/Repository.tsx @@ -38,23 +38,33 @@ export const RepositoryNotifications: FC = ({ }, [repoNotifications, hostname]); const avatarUrl = repoNotifications[0].repository.owner.avatar_url; + const repoSlug = repoNotifications[0].repository.full_name; return ( <> {avatarUrl ? ( - + ) : ( )} - + {repoName} = ({ { }, []); const onOpenGitHubNotifications = useCallback(() => { - openExternalLink(`https://github.com/notifications`); + openExternalLink('https://github.com/notifications'); }, []); const quitApp = useCallback(() => { @@ -46,6 +46,7 @@ export const Sidebar: FC = () => { { 0 ? 'text-green-500' : 'text-white' }`} @@ -73,6 +75,7 @@ export const Sidebar: FC = () => { {isLoggedIn && ( <> { @@ -89,6 +92,7 @@ export const Sidebar: FC = () => { { @@ -106,6 +110,7 @@ export const Sidebar: FC = () => { {!isLoggedIn && ( - in over 3 years + over 6 years ago @@ -87,6 +90,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`] className="focus:outline-none h-full hover:text-green-500" onClick={[Function]} title="Mark as Done" + type="button" > manosim/gitify @@ -26,6 +28,7 @@ exports[`components/Repository.tsx should render itself & its children 1`] = ` className="focus:outline-none h-full hover:text-green-500" onClick={[Function]} title="Mark Repository as Done" + type="button" > manosim/gitify @@ -137,6 +142,7 @@ exports[`components/Repository.tsx should use default repository icon when avata className="focus:outline-none h-full hover:text-green-500" onClick={[Function]} title="Mark Repository as Done" + type="button" > ) => void; placeholder?: string; disabled?: boolean; } diff --git a/src/context/App.tsx b/src/context/App.tsx index 6d9d5c813..96fb5e7b8 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -65,7 +65,10 @@ interface AppContextState { markRepoNotificationsDone: (id: string, hostname: string) => Promise; settings: SettingsState; - updateSetting: (name: keyof SettingsState, value: any) => void; + updateSetting: ( + name: keyof SettingsState, + value: boolean | Theme | string | null, + ) => void; } export const AppContext = createContext>({}); diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx index acd373d2f..3de4d5f7c 100644 --- a/src/routes/Login.tsx +++ b/src/routes/Login.tsx @@ -51,6 +51,7 @@ export const LoginRoute: FC = () => { } { { navigate(-1)} diff --git a/src/routes/LoginWithToken.tsx b/src/routes/LoginWithToken.tsx index 91f45db2f..fa533303d 100644 --- a/src/routes/LoginWithToken.tsx +++ b/src/routes/LoginWithToken.tsx @@ -62,7 +62,8 @@ export const LoginWithToken: FC = () => { helpText={ <> To generate a token, go to GitHub,{' '} - openLink( @@ -71,7 +72,7 @@ export const LoginWithToken: FC = () => { } > personal access tokens - {' '} + {' '} and create one with the {Constants.AUTH_SCOPE.length} scopes{' '} {Constants.AUTH_SCOPE.join(', ')}{' '} @@ -120,6 +121,7 @@ export const LoginWithToken: FC = () => { navigate(-1)} diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx index 684785795..310e03f65 100644 --- a/src/routes/Settings.tsx +++ b/src/routes/Settings.tsx @@ -98,6 +98,7 @@ export const SettingsRoute: FC = () => { > navigate(-1)} @@ -219,15 +220,17 @@ export const SettingsRoute: FC = () => { - openGitHubReleaseNotes(appVersion)} > Gitify v{appVersion} - + { { Login with Personal Access Token @@ -69,6 +70,7 @@ exports[`routes/Login.tsx should render itself & its children 1`] = ` className="w-50 px-2 py-2 my-2 bg-gray-300 font-semibold rounded text-xs text-center dark:text-black hover:bg-gray-500 hover:text-white focus:outline-none" onClick={[Function]} title="Login with GitHub Enterprise" + type="button" > Login to GitHub Enterprise diff --git a/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap b/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap index 43d74e232..c913860c7 100644 --- a/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap +++ b/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap @@ -11,6 +11,7 @@ exports[`routes/LoginEnterprise.tsx renders correctly 1`] = ` className="focus:outline-none" onClick={[Function]} title="Go Back" + type="button" > To generate a token, go to GitHub, - personal access tokens - + and create one with the 3 diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap index d6bf020ab..34cf42d08 100644 --- a/src/routes/__snapshots__/Settings.test.tsx.snap +++ b/src/routes/__snapshots__/Settings.test.tsx.snap @@ -39,6 +39,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = ` - Gitify v 0.0.1 - + { const response: GraphQLSearch = await apiRequestAuth( - `https://api.github.com/graphql`, + 'https://api.github.com/graphql', 'POST', token, { @@ -242,7 +240,7 @@ export async function fetchDiscussion( export function getLatestDiscussionComment( comments: DiscussionComment[], ): DiscussionComment | null { - if (!comments || comments.length == 0) { + if (!comments || comments.length === 0) { return null; } diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 57f902026..42bac5950 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -25,10 +25,7 @@ export const setTheme = (mode?: Theme) => { break; default: - if ( - window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) { + if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) { setDarkMode(); } else { setLightMode(); diff --git a/webpack.common.js b/webpack.common.js index 94dd07b18..b821c3e33 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,4 +1,4 @@ -const path = require('path'); +const path = require('node:path'); const webpack = require('webpack'); module.exports = {