replace complex eslint config with biome

This commit is contained in:
Athou
2024-06-13 21:54:14 +02:00
parent 9115797dee
commit 3810dedf47
102 changed files with 7186 additions and 9488 deletions

View File

@@ -1,8 +0,0 @@
dist
node_modules
vite.config.ts
# compiled linguijs locales
# they no longer exist but we keep this to avoid issues with people still having those files on disk
src/locales/**/*.ts

View File

@@ -1,52 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"eslint:recommended",
"standard",
"love",
"plugin:@typescript-eslint/strict-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
],
settings: {
react: {
version: "detect",
},
},
overrides: [
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parserOptions: {
project: true,
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["react"],
rules: {
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as" }],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/prefer-nullish-coalescing": ["error", { ignoreConditionalTests: true }],
"@typescript-eslint/restrict-template-expressions": ["error", { allowNumber: true }],
"@typescript-eslint/strict-boolean-expressions": "off",
"react/jsx-curly-brace-presence": ["error", "never"],
"react/no-unescaped-entities": "off",
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "error",
},
}

View File

@@ -1,8 +0,0 @@
{
"printWidth": 140,
"semi": false,
"tabWidth": 4,
"arrowParens": "avoid",
"endOfLine": "auto",
"trailingComma": "es5"
}

View File

@@ -0,0 +1,19 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
"formatter": {
"indentStyle": "space",
"indentWidth": 4,
"lineEnding": "lf",
"lineWidth": 140
},
"javascript": {
"formatter": {
"trailingCommas": "es5",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded"
}
},
"files": {
"ignore": ["dist", "node_modules", "target", "target-ide"]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +1,79 @@
{
"name": "commafeed-client",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"dev:typescript": "tsc --watch",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:ci": "vitest run",
"eslint": "eslint --ext=.js,.jsx,.ts,.tsx src",
"i18n:extract": "lingui extract --clean"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@fontsource/open-sans": "^5.0.28",
"@lingui/core": "^4.11.1",
"@lingui/macro": "^4.11.1",
"@lingui/react": "^4.11.1",
"@mantine/core": "^7.10.1",
"@mantine/form": "^7.10.1",
"@mantine/hooks": "^7.10.1",
"@mantine/modals": "^7.10.1",
"@mantine/notifications": "^7.10.1",
"@mantine/spotlight": "^7.10.1",
"@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.2.5",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"escape-string-regexp": "^5.0.0",
"interweave": "^13.1.0",
"monaco-editor": "^0.49.0",
"mousetrap": "^1.6.5",
"react": "^18.3.1",
"react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.3.1",
"react-draggable": "^4.4.6",
"react-ga4": "^2.1.0",
"react-helmet": "^6.1.0",
"react-icons": "^5.2.1",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-swipeable": "^7.0.1",
"redoc": "^2.1.5",
"throttle-debounce": "^5.0.0",
"tinycon": "^0.6.8",
"tss-react": "^4.9.10",
"use-local-storage": "^3.0.0",
"websocket-heartbeat-js": "^1.1.3"
},
"devDependencies": {
"@lingui/cli": "^4.11.1",
"@lingui/vite-plugin": "^4.11.1",
"@types/mousetrap": "^1.6.15",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@types/react-infinite-scroller": "^1.2.5",
"@types/swagger-ui-react": "^4.18.3",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycon": "^0.6.5",
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@vitejs/plugin-react": "^4.3.1",
"babel-plugin-macros": "^3.1.0",
"eslint": "^8.57.0",
"eslint-config-love": "^47.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"prettier": "^3.3.2",
"rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.4.5",
"vite": "^5.2.13",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",
"vitest-mock-extended": "^1.3.1"
}
"name": "commafeed-client",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"dev:typescript": "tsc --watch",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:ci": "vitest run",
"lint": "biome check ./src",
"lint:fix": "biome check --write ./src",
"i18n:extract": "lingui extract --clean"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@fontsource/open-sans": "^5.0.28",
"@lingui/core": "^4.11.1",
"@lingui/macro": "^4.11.1",
"@lingui/react": "^4.11.1",
"@mantine/core": "^7.10.1",
"@mantine/form": "^7.10.1",
"@mantine/hooks": "^7.10.1",
"@mantine/modals": "^7.10.1",
"@mantine/notifications": "^7.10.1",
"@mantine/spotlight": "^7.10.1",
"@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.2.5",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"escape-string-regexp": "^5.0.0",
"interweave": "^13.1.0",
"monaco-editor": "^0.49.0",
"mousetrap": "^1.6.5",
"react": "^18.3.1",
"react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.3.1",
"react-draggable": "^4.4.6",
"react-ga4": "^2.1.0",
"react-helmet": "^6.1.0",
"react-icons": "^5.2.1",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-swipeable": "^7.0.1",
"redoc": "^2.1.5",
"throttle-debounce": "^5.0.0",
"tinycon": "^0.6.8",
"tss-react": "^4.9.10",
"use-local-storage": "^3.0.0",
"vite-plugin-biome": "^1.0.10",
"websocket-heartbeat-js": "^1.1.3"
},
"devDependencies": {
"@biomejs/biome": "^1.8.1",
"@lingui/cli": "^4.11.1",
"@lingui/vite-plugin": "^4.11.1",
"@types/mousetrap": "^1.6.15",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@types/react-infinite-scroller": "^1.2.5",
"@types/swagger-ui-react": "^4.18.3",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycon": "^0.6.5",
"@vitejs/plugin-react": "^4.3.1",
"babel-plugin-macros": "^3.1.0",
"rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.4.5",
"vite": "^5.2.13",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",
"vitest-mock-extended": "^1.3.1"
}
}

View File

@@ -14,6 +14,7 @@ import { Header } from "components/header/Header"
import { Tree } from "components/sidebar/Tree"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useI18n } from "i18n"
import { WelcomePage } from "pages/WelcomePage"
import { AdminUsersPage } from "pages/admin/AdminUsersPage"
import { MetricsPage } from "pages/admin/MetricsPage"
import { AboutPage } from "pages/app/AboutPage"
@@ -28,7 +29,6 @@ import { TagDetailsPage } from "pages/app/TagDetailsPage"
import { LoginPage } from "pages/auth/LoginPage"
import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage"
import { RegistrationPage } from "pages/auth/RegistrationPage"
import { WelcomePage } from "pages/WelcomePage"
import React, { useEffect } from "react"
import { isSafari } from "react-device-detect"
import ReactGA from "react-ga4"

View File

@@ -1,5 +1,5 @@
import { createAsyncThunk } from "@reduxjs/toolkit"
import { type AppDispatch, type RootState } from "app/store"
import type { AppDispatch, RootState } from "app/store"
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState

View File

@@ -1,31 +1,31 @@
import axios, { type AxiosError } from "axios"
import {
type AddCategoryRequest,
type AdminSaveUserRequest,
type AuthenticationError,
type Category,
type CategoryModificationRequest,
type CollapseRequest,
type Entries,
type FeedInfo,
type FeedInfoRequest,
type FeedModificationRequest,
type GetEntriesPaginatedRequest,
type IDRequest,
type LoginRequest,
type MarkRequest,
type Metrics,
type MultipleMarkRequest,
type PasswordResetRequest,
type ProfileModificationRequest,
type RegistrationRequest,
type ServerInfo,
type Settings,
type StarRequest,
type SubscribeRequest,
type Subscription,
type TagRequest,
type UserModel,
import type {
AddCategoryRequest,
AdminSaveUserRequest,
AuthenticationError,
Category,
CategoryModificationRequest,
CollapseRequest,
Entries,
FeedInfo,
FeedInfoRequest,
FeedModificationRequest,
GetEntriesPaginatedRequest,
IDRequest,
LoginRequest,
MarkRequest,
Metrics,
MultipleMarkRequest,
PasswordResetRequest,
ProfileModificationRequest,
RegistrationRequest,
ServerInfo,
Settings,
StarRequest,
SubscribeRequest,
Subscription,
TagRequest,
UserModel,
} from "./types"
const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })

View File

@@ -1,8 +1,8 @@
import { t } from "@lingui/macro"
import { type IconType } from "react-icons"
import type { IconType } from "react-icons"
import { FaAt } from "react-icons/fa"
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si"
import { type Category, type Entry, type SharingSettings } from "./types"
import type { Category, Entry, SharingSettings } from "./types"
const categories: Record<string, Category> = {
all: {

View File

@@ -1,9 +1,9 @@
import { configureStore } from "@reduxjs/toolkit"
import { type client } from "app/client"
import type { client } from "app/client"
import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "app/entries/thunks"
import { reducers, type RootState } from "app/store"
import { type Entries, type Entry } from "app/types"
import { type AxiosResponse } from "axios"
import { type RootState, reducers } from "app/store"
import type { Entries, Entry } from "app/types"
import type { AxiosResponse } from "axios"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { mockReset } from "vitest-mock-extended"

View File

@@ -1,7 +1,7 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { Constants } from "app/constants"
import { loadEntries, loadMoreEntries, markAllEntries, markEntry, markMultipleEntries, starEntry, tagEntry } from "app/entries/thunks"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
export type EntrySourceType = "category" | "feed" | "tag"
@@ -51,11 +51,9 @@ export const entriesSlice = createSlice({
state.selectedEntryId = action.payload.id
},
setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => {
state.entries
.filter(e => e.id === action.payload.entry.id)
.forEach(e => {
e.expanded = action.payload.expanded
})
for (const e of state.entries.filter(e => e.id === action.payload.entry.id)) {
e.expanded = action.payload.expanded
}
},
setScrollingToEntry: (state, action: PayloadAction<boolean>) => {
state.scrollingToEntry = action.payload
@@ -66,32 +64,24 @@ export const entriesSlice = createSlice({
},
extraReducers: builder => {
builder.addCase(markEntry.pending, (state, action) => {
state.entries
.filter(e => e.id === action.meta.arg.entry.id)
.forEach(e => {
e.read = action.meta.arg.read
})
for (const e of state.entries.filter(e => e.id === action.meta.arg.entry.id)) {
e.read = action.meta.arg.read
}
})
builder.addCase(markMultipleEntries.pending, (state, action) => {
state.entries
.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id))
.forEach(e => {
e.read = action.meta.arg.read
})
for (const e of state.entries.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id))) {
e.read = action.meta.arg.read
}
})
builder.addCase(markAllEntries.pending, (state, action) => {
state.entries
.filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true))
.forEach(e => {
e.read = true
})
for (const e of state.entries.filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true))) {
e.read = true
}
})
builder.addCase(starEntry.pending, (state, action) => {
state.entries
.filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId)
.forEach(e => {
e.starred = action.meta.arg.starred
})
for (const e of state.entries.filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId)) {
e.starred = action.meta.arg.starred
}
})
builder.addCase(loadEntries.pending, (state, action) => {
state.source = action.meta.arg.source
@@ -122,11 +112,9 @@ export const entriesSlice = createSlice({
state.loading = false
})
builder.addCase(tagEntry.pending, (state, action) => {
state.entries
.filter(e => +e.id === action.meta.arg.entryId)
.forEach(e => {
e.tags = action.meta.arg.tags
})
for (const e of state.entries.filter(e => +e.id === action.meta.arg.entryId)) {
e.tags = action.meta.arg.tags
}
})
},
})

View File

@@ -1,7 +1,7 @@
import { createAppAsyncThunk } from "app/async-thunk"
import { client } from "app/client"
import { Constants } from "app/constants"
import { entriesSlice, type EntrySource, type EntrySourceType, setSearch } from "app/entries/slice"
import { type EntrySource, type EntrySourceType, entriesSlice, setSearch } from "app/entries/slice"
import type { RootState } from "app/store"
import { reloadTree } from "app/tree/thunks"
import type { Entry, MarkRequest, TagRequest } from "app/types"

View File

@@ -1,4 +1,4 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
interface RedirectState {
to?: string

View File

@@ -1,6 +1,6 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { reloadServerInfos } from "app/server/thunks"
import { type ServerInfo } from "app/types"
import type { ServerInfo } from "app/types"
interface ServerState {
serverInfos?: ServerInfo

View File

@@ -1,8 +1,8 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { markEntry } from "app/entries/thunks"
import { redirectTo } from "app/redirect/slice"
import { collapseTreeCategory, reloadTree } from "app/tree/thunks"
import { type Category } from "app/types"
import type { Category } from "app/types"
import { visitCategoryTree } from "app/utils"
interface TreeState {
@@ -34,13 +34,11 @@ export const treeSlice = createSlice({
}>
) => {
if (!state.rootCategory) return
visitCategoryTree(state.rootCategory, c =>
c.feeds
.filter(f => f.id === action.payload.feedId)
.forEach(f => {
f.unread += action.payload.amount
})
)
visitCategoryTree(state.rootCategory, c => {
for (const f of c.feeds.filter(f => f.id === action.payload.feedId)) {
f.unread += action.payload.amount
}
})
},
},
extraReducers: builder => {
@@ -55,13 +53,11 @@ export const treeSlice = createSlice({
})
builder.addCase(markEntry.pending, (state, action) => {
if (!state.rootCategory) return
visitCategoryTree(state.rootCategory, c =>
c.feeds
.filter(f => f.id === +action.meta.arg.entry.feedId)
.forEach(f => {
f.unread = action.meta.arg.read ? f.unread - 1 : f.unread + 1
})
)
visitCategoryTree(state.rootCategory, c => {
for (const f of c.feeds.filter(f => f.id === +action.meta.arg.entry.feedId)) {
f.unread = action.meta.arg.read ? f.unread - 1 : f.unread + 1
}
})
})
builder.addCase(redirectTo, state => {
state.mobileMenuOpen = false

View File

@@ -1,7 +1,7 @@
import { t } from "@lingui/macro"
import { showNotification } from "@mantine/notifications"
import { createSlice, isAnyOf } from "@reduxjs/toolkit"
import { type Settings, type UserModel } from "app/types"
import type { Settings, UserModel } from "app/types"
import {
changeCustomContextMenu,
changeExternalLinkIconDisplayMode,

View File

@@ -1,9 +1,11 @@
import { throttle } from "throttle-debounce"
import { type Category } from "./types"
import type { Category } from "./types"
export function visitCategoryTree(category: Category, visitor: (category: Category) => void): void {
visitor(category)
category.children.forEach(child => visitCategoryTree(child, visitor))
for (const child of category.children) {
visitCategoryTree(child, visitor)
}
}
export function flattenCategoryTree(category: Category): Category[] {

View File

@@ -1,8 +1,8 @@
import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
import { type ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
import type { ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
import { Constants } from "app/constants"
import { useActionButton } from "hooks/useActionButton"
import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
import { type MouseEventHandler, type ReactNode, forwardRef } from "react"
interface ActionButtonProps {
className?: string

View File

@@ -1,5 +1,5 @@
import { Trans } from "@lingui/macro"
import { Alert as MantineAlert, Box } from "@mantine/core"
import { Box, Alert as MantineAlert } from "@mantine/core"
import { Fragment } from "react"
import { TbAlertCircle, TbAlertTriangle, TbCircleCheck } from "react-icons/tb"

View File

@@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro"
import { Box, Button, Checkbox, Group, PasswordInput, Stack, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { type AdminSaveUserRequest, type UserModel } from "app/types"
import type { AdminSaveUserRequest, UserModel } from "app/types"
import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy } from "react-icons/tb"

View File

@@ -1,7 +1,7 @@
import { Input, Textarea } from "@mantine/core"
import RichCodeEditor from "components/code/RichCodeEditor"
import { useMobile } from "hooks/useMobile"
import { type ReactNode } from "react"
import type { ReactNode } from "react"
interface CodeEditorProps {
description?: ReactNode

View File

@@ -5,7 +5,7 @@ import { useAsync } from "react-async-hook"
const init = async () => {
window.MonacoEnvironment = {
async getWorker(_, label) {
let worker
let worker: typeof import("*?worker")
if (label === "css") {
worker = await import("monaco-editor/esm/vs/language/css/css.worker?worker")
} else if (label === "javascript") {
@@ -13,7 +13,6 @@ const init = async () => {
} else {
worker = await import("monaco-editor/esm/vs/editor/editor.worker?worker")
}
// eslint-disable-next-line new-cap
return new worker.default()
},
}

View File

@@ -1,5 +1,5 @@
import { TypographyStylesProvider } from "@mantine/core"
import { type ReactNode } from "react"
import type { ReactNode } from "react"
/**
* This component is used to provide basic styles to html typography elements.

View File

@@ -1,10 +1,10 @@
import { Box, Mark } from "@mantine/core"
import { Constants } from "app/constants"
import { calculatePlaceholderSize } from "app/utils"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import escapeStringRegexp from "escape-string-regexp"
import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave"
import { type ChildrenNode, Interweave, type MatchResponse, Matcher, type Node, type TransformCallback } from "interweave"
import React from "react"
import { tss } from "tss"
@@ -40,8 +40,8 @@ const transform: TransformCallback = node => {
const title = node.getAttribute("title") ?? undefined
const nodeWidth = node.getAttribute("width")
const nodeHeight = node.getAttribute("height")
const width = nodeWidth ? parseInt(nodeWidth, 10) : undefined
const height = nodeHeight ? parseInt(nodeHeight, 10) : undefined
const width = nodeWidth ? Number.parseInt(nodeWidth, 10) : undefined
const height = nodeHeight ? Number.parseInt(nodeHeight, 10) : undefined
const placeholderSize = calculatePlaceholderSize({
width,
height,

View File

@@ -1,7 +1,10 @@
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
export function Enclosure(props: {
enclosureType: string
enclosureUrl: string
}) {
const hasVideo = props.enclosureType.startsWith("video")
const hasAudio = props.enclosureType.startsWith("audio")
const hasImage = props.enclosureType.startsWith("image")
@@ -9,11 +12,13 @@ export function Enclosure(props: { enclosureType: string; enclosureUrl: string }
return (
<BasicHtmlStyles>
{hasVideo && (
// biome-ignore lint/a11y/useMediaCaption: we don't have any captions for videos
<video controls width="100%">
<source src={props.enclosureUrl} type={props.enclosureType} />
</video>
)}
{hasAudio && (
// biome-ignore lint/a11y/useMediaCaption: we don't have any captions for audio
<audio controls>
<source src={props.enclosureUrl} type={props.enclosureType} />
</audio>

View File

@@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro"
import { Box } from "@mantine/core"
import { openModal } from "@mantine/modals"
import { Constants } from "app/constants"
import { type ExpendableEntry } from "app/entries/slice"
import type { ExpendableEntry } from "app/entries/slice"
import {
loadMoreEntries,
markAllEntries,
@@ -126,7 +126,7 @@ export function FeedEntries() {
})
window.addEventListener("scroll", listener)
return () => window.removeEventListener("scroll", listener)
}, [dispatch, contextMenu, entries, viewMode, scrollMarks, scrollingToEntry])
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
useMousetrap("r", async () => await dispatch(reloadEntries()))
useMousetrap(

View File

@@ -1,12 +1,12 @@
import { Box, Divider, type MantineRadius, type MantineSpacing, Paper } from "@mantine/core"
import { Constants } from "app/constants"
import { useAppSelector } from "app/store"
import { type Entry, type ViewMode } from "app/types"
import type { Entry, ViewMode } from "app/types"
import { FeedEntryCompactHeader } from "components/content/header/FeedEntryCompactHeader"
import { FeedEntryHeader } from "components/content/header/FeedEntryHeader"
import { useMobile } from "hooks/useMobile"
import { useViewMode } from "hooks/useViewMode"
import React from "react"
import type React from "react"
import { useSwipeable } from "react-swipeable"
import { tss } from "tss"
import { FeedEntryBody } from "./FeedEntryBody"
@@ -35,7 +35,7 @@ const useStyles = tss
maxWidth?: number
}>()
.create(({ theme, colorScheme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => {
let backgroundColor
let backgroundColor: string
if (colorScheme === "dark") {
backgroundColor = read ? "inherit" : theme.colors.dark[5]
} else {
@@ -61,7 +61,7 @@ const useStyles = tss
backgroundHoverColor = colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1]
}
let paperBorderLeftColor
let paperBorderLeftColor = ""
if (showSelectionIndicator) {
const borderLeftColor = colorScheme === "dark" ? theme.colors[theme.primaryColor][4] : theme.colors[theme.primaryColor][6]
paperBorderLeftColor = `${borderLeftColor} !important`

View File

@@ -1,6 +1,6 @@
import { Box } from "@mantine/core"
import { useAppSelector } from "app/store"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
import { Content } from "./Content"
import { Enclosure } from "./Enclosure"
import { Media } from "./Media"

View File

@@ -4,7 +4,7 @@ import { Constants } from "app/constants"
import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks"
import { redirectToFeed } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
import { truncate } from "app/utils"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useColorScheme } from "hooks/useColorScheme"

View File

@@ -1,8 +1,8 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Group, Indicator, Popover, TagsInput } from "@mantine/core"
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
import { ActionButton } from "components/ActionButton"
import { useActionButton } from "hooks/useActionButton"
import { useMobile } from "hooks/useMobile"

View File

@@ -1,8 +1,8 @@
import { Box } from "@mantine/core"
import { Constants } from "app/constants"
import { calculatePlaceholderSize } from "app/utils"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { Content } from "./Content"
export interface MediaProps {

View File

@@ -2,10 +2,10 @@ import { Trans } from "@lingui/macro"
import { ActionIcon, Box, CopyButton, Divider, SimpleGrid } from "@mantine/core"
import { Constants } from "app/constants"
import { useAppSelector } from "app/store"
import { type SharingSettings } from "app/types"
import type { SharingSettings } from "app/types"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMobile } from "hooks/useMobile"
import { type IconType } from "react-icons"
import type { IconType } from "react-icons"
import { TbCheck, TbCopy, TbDeviceDesktopShare, TbDeviceMobileShare } from "react-icons/tb"
import { tss } from "tss"

View File

@@ -1,11 +1,11 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Box, Button, Group, Stack, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch } from "app/store"
import { reloadTree } from "app/tree/thunks"
import { type AddCategoryRequest } from "app/types"
import type { AddCategoryRequest } from "app/types"
import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook"
import { TbFolderPlus } from "react-icons/tb"

View File

@@ -1,9 +1,9 @@
import { t } from "@lingui/macro"
import { Select, type SelectProps } from "@mantine/core"
import { type ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types"
import type { ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types"
import { Constants } from "app/constants"
import { useAppSelector } from "app/store"
import { type Category } from "app/types"
import type { Category } from "app/types"
import { flattenCategoryTree } from "app/utils"
type CategorySelectProps = Partial<SelectProps> & {
@@ -18,7 +18,8 @@ export function CategorySelect(props: CategorySelectProps) {
map.set(c.id, c)
return map
}, new Map<string, Category>())
const categoryLabel = (cat: Category) => {
const categoryLabel = (category: Category) => {
let cat = category
let label = cat.name
while (cat.parentId) {

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Box, Button, FileInput, Group, Stack } from "@mantine/core"
import { isNotEmpty, useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"

View File

@@ -6,7 +6,7 @@ import { Constants } from "app/constants"
import { redirectToFeed, redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch } from "app/store"
import { reloadTree } from "app/tree/thunks"
import { type FeedInfoRequest, type SubscribeRequest } from "app/types"
import type { FeedInfoRequest, SubscribeRequest } from "app/types"
import { Alert } from "components/Alert"
import { useState } from "react"
import { useAsyncCallback } from "react-async-hook"

View File

@@ -1,9 +1,9 @@
import { Box, Text } from "@mantine/core"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate"
import { FeedFavicon } from "components/content/FeedFavicon"
import { OpenExternalLink } from "components/content/header/OpenExternalLink"
import { Star } from "components/content/header/Star"
import { RelativeDate } from "components/RelativeDate"
import { OnDesktop } from "components/responsive/OnDesktop"
import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle"

View File

@@ -1,9 +1,9 @@
import { Box, Flex, Space, Text } from "@mantine/core"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate"
import { FeedFavicon } from "components/content/FeedFavicon"
import { OpenExternalLink } from "components/content/header/OpenExternalLink"
import { Star } from "components/content/header/Star"
import { RelativeDate } from "components/RelativeDate"
import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle"

View File

@@ -1,6 +1,6 @@
import { Highlight } from "@mantine/core"
import { useAppSelector } from "app/store"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
export interface FeedEntryTitleProps {
entry: Entry

View File

@@ -3,7 +3,7 @@ import { ActionIcon, Anchor, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { markEntry } from "app/entries/thunks"
import { useAppDispatch } from "app/store"
import { type Entry } from "app/types"
import type { Entry } from "app/types"
import { TbExternalLink } from "react-icons/tb"
export function OpenExternalLink(props: { entry: Entry }) {

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form"
import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks"

View File

@@ -13,7 +13,7 @@ import { showNotification } from "@mantine/notifications"
import { client } from "app/client"
import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { type ViewMode } from "app/types"
import type { ViewMode } from "app/types"
import { useViewMode } from "hooks/useViewMode"
import { type ReactNode, useState } from "react"
import {

View File

@@ -1,4 +1,4 @@
import { type MetricGauge } from "app/types"
import type { MetricGauge } from "app/types"
interface MeterProps {
gauge: MetricGauge

View File

@@ -1,5 +1,5 @@
import { Box } from "@mantine/core"
import { type MetricMeter } from "app/types"
import type { MetricMeter } from "app/types"
interface MeterProps {
meter: MetricMeter

View File

@@ -1,5 +1,5 @@
import { Box } from "@mantine/core"
import { type MetricTimer } from "app/types"
import type { MetricTimer } from "app/types"
interface MetricTimerProps {
timer: MetricTimer

View File

@@ -1,6 +1,6 @@
import { Box } from "@mantine/core"
import { useMobile } from "hooks/useMobile"
import React from "react"
import type React from "react"
export function OnDesktop(props: { children: React.ReactNode }) {
const mobile = useMobile()

View File

@@ -1,6 +1,6 @@
import { Box } from "@mantine/core"
import { useMobile } from "hooks/useMobile"
import React from "react"
import type React from "react"
export function OnMobile(props: { children: React.ReactNode }) {
const mobile = useMobile()

View File

@@ -1,9 +1,9 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Divider, Group, Radio, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
import { type ComboboxData } from "@mantine/core/lib/components/Combobox/Combobox.types"
import type { ComboboxData } from "@mantine/core/lib/components/Combobox/Combobox.types"
import { Constants } from "app/constants"
import { useAppDispatch, useAppSelector } from "app/store"
import { type IconDisplayMode, type ScrollMode, type SharingSettings } from "app/types"
import type { IconDisplayMode, ScrollMode, SharingSettings } from "app/types"
import {
changeCustomContextMenu,
changeExternalLinkIconDisplayMode,
@@ -18,7 +18,7 @@ import {
changeStarIconDisplayMode,
} from "app/user/thunks"
import { locales } from "i18n"
import { type ReactNode } from "react"
import type { ReactNode } from "react"
export function DisplaySettings() {
const language = useAppSelector(state => state.user.settings?.language)

View File

@@ -1,11 +1,11 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, Stack, Text, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form"
import { openConfirmModal } from "@mantine/modals"
import { client, errorToStrings } from "app/client"
import { redirectToLogin, redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { type ProfileModificationRequest } from "app/types"
import type { ProfileModificationRequest } from "app/types"
import { reloadProfile } from "app/user/thunks"
import { Alert } from "components/Alert"
import { useEffect } from "react"

View File

@@ -11,7 +11,7 @@ import {
} from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { collapseTreeCategory } from "app/tree/thunks"
import { type Category, type Subscription } from "app/types"
import type { Category, Subscription } from "app/types"
import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
import { Loader } from "components/Loader"
import { OnDesktop } from "components/responsive/OnDesktop"

View File

@@ -1,13 +1,13 @@
import { Box, Center } from "@mantine/core"
import { FeedFavicon } from "components/content/FeedFavicon"
import React, { type ReactNode } from "react"
import type React from "react"
import { tss } from "tss"
import { UnreadCount } from "./UnreadCount"
interface TreeNodeProps {
id: string
name: ReactNode
icon: ReactNode
name: React.ReactNode
icon: React.ReactNode
unread: number
selected: boolean
expanded?: boolean
@@ -27,7 +27,7 @@ const useStyles = tss
let backgroundColor = "inherit"
if (selected) backgroundColor = colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]
let color
let color: string
if (hasError) {
color = theme.colors.red[6]
} else if (colorScheme === "dark") {

View File

@@ -1,10 +1,10 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Box, Center, Kbd, TextInput } from "@mantine/core"
import { useOs } from "@mantine/hooks"
import { Spotlight, spotlight, type SpotlightActionData } from "@mantine/spotlight"
import { Spotlight, type SpotlightActionData, spotlight } from "@mantine/spotlight"
import { redirectToFeed } from "app/redirect/thunks"
import { useAppDispatch } from "app/store"
import { type Subscription } from "app/types"
import type { Subscription } from "app/types"
import { FeedFavicon } from "components/content/FeedFavicon"
import { useMousetrap } from "hooks/useMousetrap"
import { TbSearch } from "react-icons/tb"
@@ -63,7 +63,7 @@ export function TreeSearch(props: TreeSearchProps) {
placeholder: t`Search`,
}}
nothingFound={<Trans>Nothing found</Trans>}
></Spotlight>
/>
</>
)
}

View File

@@ -9,13 +9,13 @@ export const useBrowserExtension = () => {
// monitor the attribute on the root element as it may change after the page was loaded
useEffect(() => {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
for (const mutation of mutations) {
if (mutation.type === "attributes") {
const element = mutation.target as Element
const version = element.getAttribute("browser-extension-installed")
if (version) setBrowserExtensionVersion(version)
}
})
}
})
observer.observe(document.documentElement, {

View File

@@ -1,4 +1,4 @@
import { type ViewMode } from "app/types"
import type { ViewMode } from "app/types"
import useLocalStorage from "use-local-storage"
export function useViewMode() {

View File

@@ -1,4 +1,4 @@
import { i18n, type Messages } from "@lingui/core"
import { type Messages, i18n } from "@lingui/core"
import { useAppSelector } from "app/store"
import dayjs from "dayjs"
import { useEffect } from "react"

View File

@@ -2,12 +2,12 @@ import { Trans } from "@lingui/macro"
import { ActionIcon, Box, Code, Container, Group, Table, Text, Title, useMantineTheme } from "@mantine/core"
import { closeAllModals, openConfirmModal, openModal } from "@mantine/modals"
import { client, errorToStrings } from "app/client"
import { type UserModel } from "app/types"
import { UserEdit } from "components/admin/UserEdit"
import type { UserModel } from "app/types"
import { Alert } from "components/Alert"
import { Loader } from "components/Loader"
import { RelativeDate } from "components/RelativeDate"
import { type ReactNode } from "react"
import { UserEdit } from "components/admin/UserEdit"
import type { ReactNode } from "react"
import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb"

View File

@@ -1,12 +1,13 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Anchor, Box, Container, List, NativeSelect, SimpleGrid, Title } from "@mantine/core"
import { Constants } from "app/constants"
import { redirectToApiDocumentation } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { CategorySelect } from "components/content/add/CategorySelect"
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
import { CategorySelect } from "components/content/add/CategorySelect"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import React, { useState } from "react"
import type React from "react"
import { useState } from "react"
import { TbHelp, TbKeyboard, TbPuzzle, TbRocket } from "react-icons/tb"
import { tss } from "tss"

View File

@@ -7,11 +7,11 @@ import { Constants } from "app/constants"
import { redirectToRootCategory, redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { reloadTree } from "app/tree/thunks"
import { type CategoryModificationRequest } from "app/types"
import type { CategoryModificationRequest } from "app/types"
import { flattenCategoryTree } from "app/utils"
import { Alert } from "components/Alert"
import { CategorySelect } from "components/content/add/CategorySelect"
import { Loader } from "components/Loader"
import { CategorySelect } from "components/content/add/CategorySelect"
import { useEffect } from "react"
import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"

View File

@@ -6,11 +6,11 @@ import { client, errorToStrings } from "app/client"
import { redirectToRootCategory, redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { reloadTree } from "app/tree/thunks"
import { type FeedModificationRequest } from "app/types"
import type { FeedModificationRequest } from "app/types"
import { Alert } from "components/Alert"
import { CategorySelect } from "components/content/add/CategorySelect"
import { Loader } from "components/Loader"
import { RelativeDate } from "components/RelativeDate"
import { CategorySelect } from "components/content/add/CategorySelect"
import { useEffect } from "react"
import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"

View File

@@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro"
import { ActionIcon, Box, Center, Divider, Group, Title, useMantineTheme } from "@mantine/core"
import { useViewportSize } from "@mantine/hooks"
import { Constants } from "app/constants"
import { type EntrySourceType } from "app/entries/slice"
import type { EntrySourceType } from "app/entries/slice"
import { loadEntries } from "app/entries/thunks"
import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
@@ -62,6 +62,7 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
}
}
// biome-ignore lint/correctness/useExhaustiveDependencies: we subscribe to state.timestamp because we want to reload entries even if the props are the same
useEffect(() => {
dispatch(
loadEntries({
@@ -72,7 +73,7 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
clearSearch: true,
})
)
}, [dispatch, props.sourceType, id, location.state])
}, [dispatch, props.sourceType, id, location.state?.timestamp])
const noSubscriptions = rootCategory && flattenCategoryTree(rootCategory).every(c => c.feeds.length === 0)
if (noSubscriptions) return <NoSubscriptionHelp />

View File

@@ -104,7 +104,7 @@ export default function Layout(props: LayoutProps) {
label={mobileMenuOpen ? <Trans>Close menu</Trans> : <Trans>Open menu</Trans>}
icon={mobileMenuOpen ? <TbX size={18} /> : <TbMenu2 size={18} />}
onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))}
></ActionButton>
/>
)
const addButton = (
@@ -201,7 +201,7 @@ export default function Layout(props: LayoutProps) {
width: "10px",
cursor: "ew-resize",
}}
></Box>
/>
</Draggable>
</OnDesktop>

View File

@@ -1,10 +1,10 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { redirectToRootCategory } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { type LoginRequest } from "app/types"
import type { LoginRequest } from "app/types"
import { Alert } from "components/Alert"
import { PageTitle } from "pages/PageTitle"
import { useAsyncCallback } from "react-async-hook"

View File

@@ -1,8 +1,8 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Anchor, Box, Button, Center, Container, Group, Paper, Stack, TextInput, Title } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { type PasswordResetRequest } from "app/types"
import type { PasswordResetRequest } from "app/types"
import { Alert } from "components/Alert"
import { PageTitle } from "pages/PageTitle"
import { useState } from "react"

View File

@@ -1,10 +1,10 @@
import { t, Trans } from "@lingui/macro"
import { Trans, t } from "@lingui/macro"
import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { redirectToRootCategory } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { type RegistrationRequest } from "app/types"
import type { RegistrationRequest } from "app/types"
import { Alert } from "components/Alert"
import { PageTitle } from "pages/PageTitle"
import { useAsyncCallback } from "react-async-hook"

View File

@@ -2,7 +2,7 @@ import { lingui } from "@lingui/vite-plugin"
import react from "@vitejs/plugin-react"
import { visualizer } from "rollup-plugin-visualizer"
import { defineConfig } from "vite"
import eslint from "vite-plugin-eslint"
import biomePlugin from "vite-plugin-biome"
import tsconfigPaths from "vite-tsconfig-paths"
// https://vitejs.dev/config/
@@ -16,9 +16,12 @@ export default defineConfig(env => ({
}),
lingui(),
// https://github.com/vitest-dev/vitest/issues/4055#issuecomment-1732994672
env.mode !== "test" && eslint(),
tsconfigPaths(),
visualizer(),
biomePlugin({
mode: "check",
failOnError: true,
}),
],
base: "./",
server: {

View File

@@ -1,7 +1,8 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
"config:recommended",
"customManagers:biomeVersions"
],
"packageRules": [
{