forked from Archives/Athou_commafeed
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff81749559 | ||
|
|
34db9baa7b | ||
|
|
541b5ef085 | ||
|
|
a974164ac8 | ||
|
|
9ca8358900 | ||
|
|
2bd7b46f11 | ||
|
|
eafe56a967 | ||
|
|
c18cd62d24 | ||
|
|
180385d6ab | ||
|
|
838eb8b725 | ||
|
|
b7cb3ee3f7 | ||
|
|
0a97e3f8f0 | ||
|
|
0229292b48 | ||
|
|
c87a965ae1 | ||
|
|
baa4122793 | ||
|
|
e9a4cb3432 | ||
|
|
30fc2cb8a4 | ||
|
|
25ccece76c | ||
|
|
2cb9d2285a | ||
|
|
0bb922fb99 | ||
|
|
1c59ec5857 | ||
|
|
f4e97f6350 | ||
|
|
fb355187ee | ||
|
|
89eb4d0535 | ||
|
|
11fe2f9db8 | ||
|
|
86acc3850a | ||
|
|
77bf97c6d6 | ||
|
|
1a96579292 | ||
|
|
caccd3802c | ||
|
|
dce899186b | ||
|
|
5626b39ffa | ||
|
|
5386c99c6b | ||
|
|
c27fae140f | ||
|
|
b8b67132f4 | ||
|
|
5623039084 | ||
|
|
39ddb256de | ||
|
|
71801718dc | ||
|
|
b728e28081 | ||
|
|
bf2de7aecd | ||
|
|
010fb2dccb | ||
|
|
8517c0f4eb | ||
|
|
09737b4d4c | ||
|
|
88e9a2c2e1 | ||
|
|
0d7300c192 | ||
|
|
cb1a00c5cd | ||
|
|
07a07006cc | ||
|
|
bae7f94f8c | ||
|
|
b0832c5917 | ||
|
|
f72e70cb56 | ||
|
|
8cc24e054f | ||
|
|
48e42228b1 | ||
|
|
46c1af65f0 | ||
|
|
2989407d16 | ||
|
|
2401e36486 | ||
|
|
4ee396e667 | ||
|
|
08180dd373 | ||
|
|
561513b7ed | ||
|
|
9cd7053a90 | ||
|
|
72d510bd47 | ||
|
|
1085d6aa7a | ||
|
|
9e0ef9461f | ||
|
|
650acb62d5 | ||
|
|
ff1c8a1eff | ||
|
|
62a4ac46a0 | ||
|
|
fafd4c9d54 | ||
|
|
73b472bc8a | ||
|
|
1c3be67f76 | ||
|
|
2a5988b3e7 | ||
|
|
c5757849f3 | ||
|
|
b6107c3330 | ||
|
|
3efeed6c85 | ||
|
|
be44b0aad1 | ||
|
|
36152dc47f |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [4.3.2]
|
||||||
|
|
||||||
|
- added support for unix sockets (#1278)
|
||||||
|
|
||||||
|
## [4.3.1]
|
||||||
|
|
||||||
|
- fix an issue that prevents new feeds from being added when mysql/mariadb is used as the database and the database
|
||||||
|
timezone is not UTC (#1239)
|
||||||
|
- videos in enclosures can no longer have a width larger than the page (#1240)
|
||||||
|
|
||||||
## [4.3.0]
|
## [4.3.0]
|
||||||
|
|
||||||
- h2 (the embedded database) has been upgraded to 2.2.224
|
- h2 (the embedded database) has been upgraded to 2.2.224
|
||||||
|
|||||||
@@ -1,41 +1,47 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true
|
es2021: true,
|
||||||
},
|
},
|
||||||
extends: ["standard-with-typescript", "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:prettier/recommended"],
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"standard",
|
||||||
|
"plugin:@typescript-eslint/strict-type-checked",
|
||||||
|
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
version: "detect"
|
version: "detect",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
env: {
|
env: {
|
||||||
node: true
|
node: true,
|
||||||
},
|
},
|
||||||
files: [".eslintrc.{js,cjs}"],
|
files: [".eslintrc.{js,cjs}"],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: "script"
|
sourceType: "script",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
ecmaVersion: "latest",
|
ecmaVersion: "latest",
|
||||||
sourceType: "module"
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
plugins: ["react"],
|
plugins: ["react"],
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as" }],
|
"@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-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
|
||||||
"@typescript-eslint/no-floating-promises": "off",
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
"@typescript-eslint/no-misused-promises": "off",
|
"@typescript-eslint/no-misused-promises": "off",
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": ["error", { ignoreConditionalTests: true }],
|
"@typescript-eslint/prefer-nullish-coalescing": ["error", { ignoreConditionalTests: true }],
|
||||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
|
||||||
"@typescript-eslint/unbound-method": "off",
|
|
||||||
"react/no-unescaped-entities": "off",
|
"react/no-unescaped-entities": "off",
|
||||||
"react/react-in-jsx-scope": "off",
|
"react/react-in-jsx-scope": "off",
|
||||||
"react-hooks/exhaustive-deps": "error"
|
"react-hooks/exhaustive-deps": "error",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
1279
commafeed-client/package-lock.json
generated
1279
commafeed-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,19 +14,19 @@
|
|||||||
"i18n:extract": "lingui extract --clean"
|
"i18n:extract": "lingui extract --clean"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.4",
|
||||||
"@fontsource/open-sans": "^5.0.22",
|
"@fontsource/open-sans": "^5.0.25",
|
||||||
"@lingui/core": "^4.7.0",
|
"@lingui/core": "^4.7.1",
|
||||||
"@lingui/macro": "^4.7.0",
|
"@lingui/macro": "^4.7.1",
|
||||||
"@lingui/react": "^4.7.0",
|
"@lingui/react": "^4.7.1",
|
||||||
"@mantine/core": "^7.5.1",
|
"@mantine/core": "^7.6.1",
|
||||||
"@mantine/form": "^7.5.1",
|
"@mantine/form": "^7.6.1",
|
||||||
"@mantine/hooks": "^7.5.1",
|
"@mantine/hooks": "^7.6.1",
|
||||||
"@mantine/modals": "^7.5.1",
|
"@mantine/modals": "^7.6.1",
|
||||||
"@mantine/notifications": "^7.5.1",
|
"@mantine/notifications": "^7.6.1",
|
||||||
"@mantine/spotlight": "^7.5.1",
|
"@mantine/spotlight": "^7.6.1",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@reduxjs/toolkit": "^2.1.0",
|
"@reduxjs/toolkit": "^2.2.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-infinite-scroller": "^1.2.6",
|
"react-infinite-scroller": "^1.2.6",
|
||||||
"react-redux": "^9.1.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-router-dom": "^6.22.0",
|
"react-router-dom": "^6.22.2",
|
||||||
"react-swipeable": "^7.0.1",
|
"react-swipeable": "^7.0.1",
|
||||||
"redoc": "^2.1.3",
|
"redoc": "^2.1.3",
|
||||||
"throttle-debounce": "^5.0.0",
|
"throttle-debounce": "^5.0.0",
|
||||||
@@ -52,31 +52,31 @@
|
|||||||
"websocket-heartbeat-js": "^1.1.3"
|
"websocket-heartbeat-js": "^1.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lingui/cli": "^4.7.0",
|
"@lingui/cli": "^4.7.1",
|
||||||
"@lingui/vite-plugin": "^4.7.0",
|
"@lingui/vite-plugin": "^4.7.1",
|
||||||
"@types/mousetrap": "^1.6.15",
|
"@types/mousetrap": "^1.6.15",
|
||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.61",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.19",
|
||||||
"@types/react-infinite-scroller": "^1.2.5",
|
"@types/react-infinite-scroller": "^1.2.5",
|
||||||
"@types/swagger-ui-react": "^4.18.3",
|
"@types/swagger-ui-react": "^4.18.3",
|
||||||
"@types/throttle-debounce": "^5.0.2",
|
"@types/throttle-debounce": "^5.0.2",
|
||||||
"@types/tinycon": "^0.6.5",
|
"@types/tinycon": "^0.6.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"babel-plugin-macros": "^3.1.0",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
"eslint-config-standard": "^17.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.34.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.1",
|
"vite": "^5.1.4",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-tsconfig-paths": "^4.3.1",
|
"vite-tsconfig-paths": "^4.3.1",
|
||||||
"vitest": "^1.2.2",
|
"vitest": "^1.3.1",
|
||||||
"vitest-mock-extended": "^1.3.1"
|
"vitest-mock-extended": "^1.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.3.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commafeed-client</artifactId>
|
<artifactId>commafeed-client</artifactId>
|
||||||
<name>CommaFeed Client</name>
|
<name>CommaFeed Client</name>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import axios from "axios"
|
import axios, { AxiosError } from "axios"
|
||||||
import {
|
import {
|
||||||
type AddCategoryRequest,
|
type AddCategoryRequest,
|
||||||
type AdminSaveUserRequest,
|
type AdminSaveUserRequest,
|
||||||
|
AuthenticationError,
|
||||||
type Category,
|
type Category,
|
||||||
type CategoryModificationRequest,
|
type CategoryModificationRequest,
|
||||||
type CollapseRequest,
|
type CollapseRequest,
|
||||||
@@ -31,17 +32,18 @@ const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
|
|||||||
axiosInstance.interceptors.response.use(
|
axiosInstance.interceptors.response.use(
|
||||||
response => response,
|
response => response,
|
||||||
error => {
|
error => {
|
||||||
const { status, data } = error.response
|
if (isAuthenticationError(error)) {
|
||||||
if (
|
const data = error.response?.data
|
||||||
(status === 401 && data?.message === "Credentials are required to access this resource.") ||
|
|
||||||
(status === 403 && data?.message === "You don't have the required role to access this resource.")
|
|
||||||
) {
|
|
||||||
window.location.hash = data?.allowRegistrations ? "/welcome" : "/login"
|
window.location.hash = data?.allowRegistrations ? "/welcome" : "/login"
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function isAuthenticationError(error: unknown): error is AxiosError<AuthenticationError> {
|
||||||
|
return axios.isAxiosError(error) && !!error.response && [401, 403].includes(error.response.status)
|
||||||
|
}
|
||||||
|
|
||||||
export const client = {
|
export const client = {
|
||||||
category: {
|
category: {
|
||||||
getRoot: async () => await axiosInstance.get<Category>("category/get"),
|
getRoot: async () => await axiosInstance.get<Category>("category/get"),
|
||||||
@@ -107,14 +109,19 @@ export const client = {
|
|||||||
export const errorToStrings = (err: unknown) => {
|
export const errorToStrings = (err: unknown) => {
|
||||||
let strings: string[] = []
|
let strings: string[] = []
|
||||||
|
|
||||||
if (axios.isAxiosError(err)) {
|
if (axios.isAxiosError(err) && err.response) {
|
||||||
if (err.response) {
|
if (typeof err.response.data === "string") strings.push(err.response.data)
|
||||||
const { data } = err.response
|
if (isMessageError(err)) strings.push(err.response.data.message)
|
||||||
if (typeof data === "string") strings.push(data)
|
if (isMessageArrayError(err)) strings = [...strings, ...err.response.data.errors]
|
||||||
if (typeof data === "object" && data.message) strings.push(data.message as string)
|
|
||||||
if (typeof data === "object" && data.errors) strings = [...strings, ...data.errors]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings
|
return strings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMessageError(err: AxiosError): err is AxiosError<{ message: string }> {
|
||||||
|
return !!err.response && !!err.response.data && typeof err.response.data === "object" && "message" in err.response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMessageArrayError(err: AxiosError): err is AxiosError<{ errors: string[] }> {
|
||||||
|
return !!err.response && !!err.response.data && typeof err.response.data === "object" && "errors" in err.response.data
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable import/first */
|
|
||||||
import { configureStore } from "@reduxjs/toolkit"
|
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 { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "app/entries/thunks"
|
||||||
@@ -90,7 +89,7 @@ describe("entries", () => {
|
|||||||
expect(store.getState().entries.hasMore).toBe(false)
|
expect(store.getState().entries.hasMore).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("marks an entry as read", async () => {
|
it("marks an entry as read", () => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: reducers,
|
reducer: reducers,
|
||||||
preloadedState: {
|
preloadedState: {
|
||||||
@@ -117,7 +116,7 @@ describe("entries", () => {
|
|||||||
expect(mockClient.entry.mark).toHaveBeenCalledWith({ id: "3", read: true })
|
expect(mockClient.entry.mark).toHaveBeenCalledWith({ id: "3", read: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("marks all entries as read", async () => {
|
it("marks all entries as read", () => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: reducers,
|
reducer: reducers,
|
||||||
preloadedState: {
|
preloadedState: {
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ const buildGetEntriesPaginatedRequest = (state: RootState, source: EntrySource,
|
|||||||
tag: source.type === "tag" ? source.id : undefined,
|
tag: source.type === "tag" ? source.id : undefined,
|
||||||
keywords: state.entries.search,
|
keywords: state.entries.search,
|
||||||
})
|
})
|
||||||
export const reloadEntries = createAppAsyncThunk("entries/reload", async (arg, thunkApi) => {
|
export const reloadEntries = createAppAsyncThunk("entries/reload", (arg, thunkApi) => {
|
||||||
const state = thunkApi.getState()
|
const state = thunkApi.getState()
|
||||||
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
|
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
|
||||||
})
|
})
|
||||||
export const search = createAppAsyncThunk("entries/search", async (arg: string, thunkApi) => {
|
export const search = createAppAsyncThunk("entries/search", (arg: string, thunkApi) => {
|
||||||
const state = thunkApi.getState()
|
const state = thunkApi.getState()
|
||||||
thunkApi.dispatch(setSearch(arg))
|
thunkApi.dispatch(setSearch(arg))
|
||||||
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
|
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
|
||||||
@@ -84,7 +84,7 @@ export const markMultipleEntries = createAppAsyncThunk(
|
|||||||
thunkApi.dispatch(reloadTree())
|
thunkApi.dispatch(reloadTree())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
export const markEntriesUpToEntry = createAppAsyncThunk("entries/entry/upToEntry", async (arg: Entry, thunkApi) => {
|
export const markEntriesUpToEntry = createAppAsyncThunk("entries/entry/upToEntry", (arg: Entry, thunkApi) => {
|
||||||
const state = thunkApi.getState()
|
const state = thunkApi.getState()
|
||||||
const { entries } = state.entries
|
const { entries } = state.entries
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,33 @@
|
|||||||
|
export type ReadingMode = "all" | "unread"
|
||||||
|
|
||||||
|
export type ReadingOrder = "asc" | "desc"
|
||||||
|
|
||||||
|
export type ViewMode = "title" | "cozy" | "detailed" | "expanded"
|
||||||
|
|
||||||
|
export type ScrollMode = "always" | "never" | "if_needed"
|
||||||
|
|
||||||
export interface AddCategoryRequest {
|
export interface AddCategoryRequest {
|
||||||
name: string
|
name: string
|
||||||
parentId?: string
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Subscription {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
message?: string
|
||||||
|
errorCount: number
|
||||||
|
lastRefresh?: number
|
||||||
|
nextRefresh?: number
|
||||||
|
feedUrl: string
|
||||||
|
feedLink: string
|
||||||
|
iconUrl: string
|
||||||
|
unread: number
|
||||||
|
categoryId?: string
|
||||||
|
position: number
|
||||||
|
newestItemTime?: number
|
||||||
|
filter?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
id: string
|
id: string
|
||||||
parentId?: string
|
parentId?: string
|
||||||
@@ -26,19 +51,6 @@ export interface CollapseRequest {
|
|||||||
collapse: boolean
|
collapse: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Entries {
|
|
||||||
name: string
|
|
||||||
message?: string
|
|
||||||
errorCount: number
|
|
||||||
feedLink: string
|
|
||||||
timestamp: number
|
|
||||||
hasMore: boolean
|
|
||||||
offset?: number
|
|
||||||
limit?: number
|
|
||||||
entries: Entry[]
|
|
||||||
ignoredReadStatus: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Entry {
|
export interface Entry {
|
||||||
id: string
|
id: string
|
||||||
guid: string
|
guid: string
|
||||||
@@ -67,6 +79,19 @@ export interface Entry {
|
|||||||
tags: string[]
|
tags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Entries {
|
||||||
|
name: string
|
||||||
|
message?: string
|
||||||
|
errorCount: number
|
||||||
|
feedLink: string
|
||||||
|
timestamp: number
|
||||||
|
hasMore: boolean
|
||||||
|
offset?: number
|
||||||
|
limit?: number
|
||||||
|
entries: Entry[]
|
||||||
|
ignoredReadStatus: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface FeedInfo {
|
export interface FeedInfo {
|
||||||
url: string
|
url: string
|
||||||
title: string
|
title: string
|
||||||
@@ -196,6 +221,17 @@ export interface ServerInfo {
|
|||||||
treeReloadInterval: number
|
treeReloadInterval: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SharingSettings {
|
||||||
|
email: boolean
|
||||||
|
gmail: boolean
|
||||||
|
facebook: boolean
|
||||||
|
twitter: boolean
|
||||||
|
tumblr: boolean
|
||||||
|
pocket: boolean
|
||||||
|
instapaper: boolean
|
||||||
|
buffer: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
language: string
|
language: string
|
||||||
readingMode: ReadingMode
|
readingMode: ReadingMode
|
||||||
@@ -212,17 +248,6 @@ export interface Settings {
|
|||||||
sharingSettings: SharingSettings
|
sharingSettings: SharingSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SharingSettings {
|
|
||||||
email: boolean
|
|
||||||
gmail: boolean
|
|
||||||
facebook: boolean
|
|
||||||
twitter: boolean
|
|
||||||
tumblr: boolean
|
|
||||||
pocket: boolean
|
|
||||||
instapaper: boolean
|
|
||||||
buffer: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StarRequest {
|
export interface StarRequest {
|
||||||
id: string
|
id: string
|
||||||
feedId: number
|
feedId: number
|
||||||
@@ -235,34 +260,11 @@ export interface SubscribeRequest {
|
|||||||
categoryId?: string
|
categoryId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Subscription {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
message?: string
|
|
||||||
errorCount: number
|
|
||||||
lastRefresh?: number
|
|
||||||
nextRefresh?: number
|
|
||||||
feedUrl: string
|
|
||||||
feedLink: string
|
|
||||||
iconUrl: string
|
|
||||||
unread: number
|
|
||||||
categoryId?: string
|
|
||||||
position: number
|
|
||||||
newestItemTime?: number
|
|
||||||
filter?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TagRequest {
|
export interface TagRequest {
|
||||||
entryId: number
|
entryId: number
|
||||||
tags: string[]
|
tags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnreadCount {
|
|
||||||
feedId?: number
|
|
||||||
unreadCount?: number
|
|
||||||
newestItemTime?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserModel {
|
export interface UserModel {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
@@ -284,10 +286,7 @@ export interface AdminSaveUserRequest {
|
|||||||
admin: boolean
|
admin: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReadingMode = "all" | "unread"
|
export interface AuthenticationError {
|
||||||
|
message: string
|
||||||
export type ReadingOrder = "asc" | "desc"
|
allowRegistrations: boolean
|
||||||
|
}
|
||||||
export type ViewMode = "title" | "cozy" | "detailed" | "expanded"
|
|
||||||
|
|
||||||
export type ScrollMode = "always" | "never" | "if_needed"
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
|
import { Box, Center } from "@mantine/core"
|
||||||
import { useColorScheme } from "hooks/useColorScheme"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { TbPhoto } from "react-icons/tb"
|
import { TbPhoto } from "react-icons/tb"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
@@ -18,8 +17,6 @@ interface ImageWithPlaceholderWhileLoadingProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
|
||||||
colorScheme: "light" | "dark"
|
|
||||||
placeholderWidth?: number
|
placeholderWidth?: number
|
||||||
placeholderHeight?: number
|
placeholderHeight?: number
|
||||||
placeholderBackgroundColor?: string
|
placeholderBackgroundColor?: string
|
||||||
@@ -46,11 +43,7 @@ export function ImageWithPlaceholderWhileLoading({
|
|||||||
title,
|
title,
|
||||||
width,
|
width,
|
||||||
}: ImageWithPlaceholderWhileLoadingProps) {
|
}: ImageWithPlaceholderWhileLoadingProps) {
|
||||||
const theme = useMantineTheme()
|
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
|
||||||
colorScheme,
|
|
||||||
placeholderWidth,
|
placeholderWidth,
|
||||||
placeholderHeight,
|
placeholderHeight,
|
||||||
placeholderBackgroundColor,
|
placeholderBackgroundColor,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class HighlightMatcher extends Matcher {
|
|||||||
return this.doMatch(string, new RegExp(pattern, "i"), () => ({}))
|
return this.doMatch(string, new RegExp(pattern, "i"), () => ({}))
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceWith(children: ChildrenNode, props: unknown): Node {
|
replaceWith(children: ChildrenNode): Node {
|
||||||
return <Mark>{children}</Mark>
|
return <Mark>{children}</Mark>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
|
|||||||
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
||||||
|
|
||||||
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
|
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
|
||||||
const hasVideo = props.enclosureType?.startsWith("video")
|
const hasVideo = props.enclosureType.startsWith("video")
|
||||||
const hasAudio = props.enclosureType?.startsWith("audio")
|
const hasAudio = props.enclosureType.startsWith("audio")
|
||||||
const hasImage = props.enclosureType?.startsWith("image")
|
const hasImage = props.enclosureType.startsWith("image")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicHtmlStyles>
|
<BasicHtmlStyles>
|
||||||
{hasVideo && (
|
{hasVideo && (
|
||||||
<video controls>
|
<video controls width="100%">
|
||||||
<source src={props.enclosureUrl} type={props.enclosureType} />
|
<source src={props.enclosureUrl} type={props.enclosureType} />
|
||||||
</video>
|
</video>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -295,7 +295,6 @@ export function FeedEntries() {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!entries) return <Loader />
|
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
id="entries"
|
id="entries"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Box, Divider, type MantineRadius, type MantineSpacing, type MantineTheme, Paper, useMantineTheme } from "@mantine/core"
|
import { Box, Divider, type MantineRadius, type MantineSpacing, Paper } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { type Entry, type ViewMode } from "app/types"
|
import { type Entry, type ViewMode } from "app/types"
|
||||||
import { useColorScheme } from "hooks/useColorScheme"
|
|
||||||
import { useViewMode } from "hooks/useViewMode"
|
import { useViewMode } from "hooks/useViewMode"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { useSwipeable } from "react-swipeable"
|
import { useSwipeable } from "react-swipeable"
|
||||||
@@ -26,8 +25,6 @@ interface FeedEntryProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
|
||||||
colorScheme: "light" | "dark"
|
|
||||||
read: boolean
|
read: boolean
|
||||||
expanded: boolean
|
expanded: boolean
|
||||||
viewMode: ViewMode
|
viewMode: ViewMode
|
||||||
@@ -96,12 +93,8 @@ const useStyles = tss
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function FeedEntry(props: FeedEntryProps) {
|
export function FeedEntry(props: FeedEntryProps) {
|
||||||
const theme = useMantineTheme()
|
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const { viewMode } = useViewMode()
|
const { viewMode } = useViewMode()
|
||||||
const { classes, cx } = useStyles({
|
const { classes, cx } = useStyles({
|
||||||
theme,
|
|
||||||
colorScheme,
|
|
||||||
read: props.entry.read,
|
read: props.entry.read,
|
||||||
expanded: props.expanded,
|
expanded: props.expanded,
|
||||||
viewMode,
|
viewMode,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Box, Text } from "@mantine/core"
|
|||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { RelativeDate } from "components/RelativeDate"
|
import { RelativeDate } from "components/RelativeDate"
|
||||||
import { OnDesktop } from "components/responsive/OnDesktop"
|
import { OnDesktop } from "components/responsive/OnDesktop"
|
||||||
import { useColorScheme } from "hooks/useColorScheme"
|
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { FeedEntryTitle } from "./FeedEntryTitle"
|
import { FeedEntryTitle } from "./FeedEntryTitle"
|
||||||
import { FeedFavicon } from "./FeedFavicon"
|
import { FeedFavicon } from "./FeedFavicon"
|
||||||
@@ -13,7 +12,6 @@ export interface FeedEntryHeaderProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
colorScheme: "light" | "dark"
|
|
||||||
read: boolean
|
read: boolean
|
||||||
}>()
|
}>()
|
||||||
.create(({ colorScheme, read }) => ({
|
.create(({ colorScheme, read }) => ({
|
||||||
@@ -42,9 +40,7 @@ const useStyles = tss
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
|
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
colorScheme,
|
|
||||||
read: props.entry.read,
|
read: props.entry.read,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { Group, type MantineTheme, useMantineTheme } from "@mantine/core"
|
import { Group } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks"
|
import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks"
|
||||||
import { redirectToFeed } from "app/redirect/thunks"
|
import { redirectToFeed } from "app/redirect/thunks"
|
||||||
@@ -17,28 +17,19 @@ interface FeedEntryContextMenuProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const iconSize = 16
|
const iconSize = 16
|
||||||
const useStyles = tss
|
const useStyles = tss.create(({ theme, colorScheme }) => ({
|
||||||
.withParams<{
|
menu: {
|
||||||
theme: MantineTheme
|
// apply mantine theme from MenuItem.styles.ts
|
||||||
colorScheme: "light" | "dark"
|
fontSize: theme.fontSizes.sm,
|
||||||
}>()
|
"--contexify-item-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
||||||
.create(({ theme, colorScheme }) => ({
|
"--contexify-activeItem-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
||||||
menu: {
|
"--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`,
|
||||||
// apply mantine theme from MenuItem.styles.ts
|
},
|
||||||
fontSize: theme.fontSizes.sm,
|
}))
|
||||||
"--contexify-item-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
|
||||||
"--contexify-activeItem-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
|
||||||
"--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
|
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
|
||||||
const theme = useMantineTheme()
|
|
||||||
const colorScheme = useColorScheme()
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles()
|
||||||
theme,
|
|
||||||
colorScheme,
|
|
||||||
})
|
|
||||||
const sourceType = useAppSelector(state => state.entries.source.type)
|
const sourceType = useAppSelector(state => state.entries.source.type)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { openLinkInBackgroundTab } = useBrowserExtension()
|
const { openLinkInBackgroundTab } = useBrowserExtension()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Box, Space, Text } from "@mantine/core"
|
import { Box, Space, Text } from "@mantine/core"
|
||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { RelativeDate } from "components/RelativeDate"
|
import { RelativeDate } from "components/RelativeDate"
|
||||||
import { useColorScheme } from "hooks/useColorScheme"
|
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { FeedEntryTitle } from "./FeedEntryTitle"
|
import { FeedEntryTitle } from "./FeedEntryTitle"
|
||||||
import { FeedFavicon } from "./FeedFavicon"
|
import { FeedFavicon } from "./FeedFavicon"
|
||||||
@@ -13,7 +12,6 @@ export interface FeedEntryHeaderProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
colorScheme: "light" | "dark"
|
|
||||||
read: boolean
|
read: boolean
|
||||||
}>()
|
}>()
|
||||||
.create(({ colorScheme, read }) => ({
|
.create(({ colorScheme, read }) => ({
|
||||||
@@ -28,9 +26,7 @@ const useStyles = tss
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
colorScheme,
|
|
||||||
read: props.entry.read,
|
read: props.entry.read,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ActionIcon, Box, type MantineTheme, SimpleGrid, useMantineTheme } from "@mantine/core"
|
import { ActionIcon, Box, SimpleGrid } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { useAppSelector } from "app/store"
|
import { useAppSelector } from "app/store"
|
||||||
import { type SharingSettings } from "app/types"
|
import { type SharingSettings } from "app/types"
|
||||||
import { useColorScheme } from "hooks/useColorScheme"
|
|
||||||
import { type IconType } from "react-icons"
|
import { type IconType } from "react-icons"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
|
|
||||||
@@ -10,8 +9,6 @@ type Color = `#${string}`
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
|
||||||
colorScheme: "light" | "dark"
|
|
||||||
color: Color
|
color: Color
|
||||||
}>()
|
}>()
|
||||||
.create(({ theme, colorScheme, color }) => ({
|
.create(({ theme, colorScheme, color }) => ({
|
||||||
@@ -23,11 +20,7 @@ const useStyles = tss
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
|
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
|
||||||
const theme = useMantineTheme()
|
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
|
||||||
colorScheme,
|
|
||||||
color,
|
color,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,7 +47,7 @@ export function ShareButtons(props: { url: string; description: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleGrid cols={4}>
|
<SimpleGrid cols={4}>
|
||||||
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>)
|
{(Object.keys(Constants.sharing) as (keyof SharingSettings)[])
|
||||||
.filter(site => sharingSettings?.[site])
|
.filter(site => sharingSettings?.[site])
|
||||||
.map(site => (
|
.map(site => (
|
||||||
<ShareButton
|
<ShareButton
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function ImportOpml() {
|
|||||||
|
|
||||||
const form = useForm<{ file: File }>({
|
const form = useForm<{ file: File }>({
|
||||||
validate: {
|
validate: {
|
||||||
file: v => (v ? null : t`file is required`),
|
file: () => t`file is required`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export function DisplaySettings() {
|
|||||||
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
||||||
|
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>).map(site => (
|
{(Object.keys(Constants.sharing) as (keyof SharingSettings)[]).map(site => (
|
||||||
<Switch
|
<Switch
|
||||||
key={site}
|
key={site}
|
||||||
label={Constants.sharing[site].label}
|
label={Constants.sharing[site].label}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
|
import { Box, Center } from "@mantine/core"
|
||||||
import { FeedFavicon } from "components/content/FeedFavicon"
|
import { FeedFavicon } from "components/content/FeedFavicon"
|
||||||
import { useColorScheme } from "hooks/useColorScheme"
|
|
||||||
import React, { type ReactNode } from "react"
|
import React, { type ReactNode } from "react"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { UnreadCount } from "./UnreadCount"
|
import { UnreadCount } from "./UnreadCount"
|
||||||
@@ -20,8 +19,6 @@ interface TreeNodeProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
|
||||||
colorScheme: "dark" | "light"
|
|
||||||
selected: boolean
|
selected: boolean
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
hasUnread: boolean
|
hasUnread: boolean
|
||||||
@@ -60,11 +57,7 @@ const useStyles = tss
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function TreeNode(props: TreeNodeProps) {
|
export function TreeNode(props: TreeNodeProps) {
|
||||||
const theme = useMantineTheme()
|
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
|
||||||
colorScheme,
|
|
||||||
selected: props.selected,
|
selected: props.selected,
|
||||||
hasError: props.hasError,
|
hasError: props.hasError,
|
||||||
hasUnread: props.unread > 0,
|
hasUnread: props.unread > 0,
|
||||||
|
|||||||
@@ -38,9 +38,8 @@ export const useWebSocket = () => {
|
|||||||
ws.onopen = () => dispatch(setWebSocketConnected(true))
|
ws.onopen = () => dispatch(setWebSocketConnected(true))
|
||||||
ws.onclose = () => dispatch(setWebSocketConnected(false))
|
ws.onclose = () => dispatch(setWebSocketConnected(false))
|
||||||
ws.onmessage = event => {
|
ws.onmessage = event => {
|
||||||
const { data } = event
|
if (typeof event.data === "string") {
|
||||||
if (typeof data === "string") {
|
handleMessage(dispatch, event.data)
|
||||||
handleMessage(dispatch, data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export const locales: Locale[] = [
|
|||||||
|
|
||||||
function activateLocale(locale: string) {
|
function activateLocale(locale: string) {
|
||||||
// lingui
|
// lingui
|
||||||
import(`./locales/${locale}/messages.po`).then(data => {
|
import(`./locales/${locale}/messages.po`).then((data: { messages: Messages }) => {
|
||||||
i18n.load(locale, data.messages as Messages)
|
i18n.load(locale, data.messages)
|
||||||
i18n.activate(locale)
|
i18n.activate(locale)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,39 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { Box, Button, Container, Group, type MantineTheme, Text, Title, useMantineTheme } from "@mantine/core"
|
import { Box, Button, Container, Group, Text, Title } from "@mantine/core"
|
||||||
import { TbRefresh } from "react-icons/tb"
|
import { TbRefresh } from "react-icons/tb"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { PageTitle } from "./PageTitle"
|
import { PageTitle } from "./PageTitle"
|
||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss.create(({ theme }) => ({
|
||||||
.withParams<{
|
root: {
|
||||||
theme: MantineTheme
|
paddingTop: 80,
|
||||||
}>()
|
},
|
||||||
.create(({ theme }) => ({
|
|
||||||
root: {
|
|
||||||
paddingTop: 80,
|
|
||||||
},
|
|
||||||
|
|
||||||
label: {
|
label: {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
fontSize: 120,
|
fontSize: 120,
|
||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
||||||
color: theme.colors[theme.primaryColor][3],
|
color: theme.colors[theme.primaryColor][3],
|
||||||
},
|
},
|
||||||
|
|
||||||
title: {
|
title: {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
},
|
},
|
||||||
|
|
||||||
description: {
|
description: {
|
||||||
maxWidth: 540,
|
maxWidth: 540,
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
marginTop: theme.spacing.xl,
|
marginTop: theme.spacing.xl,
|
||||||
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export function ErrorPage(props: { error: Error }) {
|
export function ErrorPage(props: { error: Error }) {
|
||||||
const theme = useMantineTheme()
|
const { classes } = useStyles()
|
||||||
const { classes } = useStyles({ theme })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { Anchor, Box, Center, Container, Divider, Group, Image, Space, Title, us
|
|||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
|
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import welcome_page_dark from "assets/welcome_page_dark.png"
|
import welcomePageDark from "assets/welcome_page_dark.png"
|
||||||
import welcome_page_light from "assets/welcome_page_light.png"
|
import welcomePageLight from "assets/welcome_page_light.png"
|
||||||
import { ActionButton } from "components/ActionButton"
|
import { ActionButton } from "components/ActionButton"
|
||||||
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
||||||
import { useMobile } from "hooks/useMobile"
|
import { useMobile } from "hooks/useMobile"
|
||||||
@@ -19,7 +19,7 @@ export function WelcomePage() {
|
|||||||
const serverInfos = useAppSelector(state => state.server.serverInfos)
|
const serverInfos = useAppSelector(state => state.server.serverInfos)
|
||||||
const { colorScheme } = useMantineColorScheme()
|
const { colorScheme } = useMantineColorScheme()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const image = colorScheme === "light" ? welcome_page_light : welcome_page_dark
|
const image = colorScheme === "light" ? welcomePageLight : welcomePageDark
|
||||||
|
|
||||||
const login = useAsyncCallback(client.user.login, {
|
const login = useAsyncCallback(client.user.login, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function AdminUsersPage() {
|
|||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{users?.map(u => (
|
{users.map(u => (
|
||||||
<Table.Tr key={u.id}>
|
<Table.Tr key={u.id}>
|
||||||
<Table.Td>{u.id}</Table.Td>
|
<Table.Td>{u.id}</Table.Td>
|
||||||
<Table.Td>{u.name}</Table.Td>
|
<Table.Td>{u.name}</Table.Td>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { HistoryService, RedocStandalone } from "redoc"
|
|||||||
|
|
||||||
// disable redoc url sync because it causes issues with hashrouter
|
// disable redoc url sync because it causes issues with hashrouter
|
||||||
Object.defineProperty(HistoryService.prototype, "replace", {
|
Object.defineProperty(HistoryService.prototype, "replace", {
|
||||||
value: () => {},
|
value: () => {
|
||||||
|
// do nothing
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function ApiDocumentationPage() {
|
function ApiDocumentationPage() {
|
||||||
|
|||||||
@@ -49,11 +49,17 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
|
|||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const titleClicked = () => {
|
const titleClicked = () => {
|
||||||
if (props.sourceType === "category") {
|
switch (props.sourceType) {
|
||||||
dispatch(redirectToCategoryDetails(id))
|
case "category":
|
||||||
} else if (props.sourceType === "feed") {
|
dispatch(redirectToCategoryDetails(id))
|
||||||
dispatch(redirectToFeedDetails(id))
|
break
|
||||||
} else if (props.sourceType === "tag") dispatch(redirectToTagDetails(id))
|
case "feed":
|
||||||
|
dispatch(redirectToFeedDetails(id))
|
||||||
|
break
|
||||||
|
case "tag":
|
||||||
|
dispatch(redirectToTagDetails(id))
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
import { useMantineTheme } from "@mantine/core"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { createTss } from "tss-react"
|
import { createTss } from "tss-react"
|
||||||
|
|
||||||
const useContext = () => {
|
const useContext = () => {
|
||||||
// return anything here that will be accessible in tss.create()
|
// return anything here that will be accessible in tss.create()
|
||||||
// we don't need anything right now
|
|
||||||
return {}
|
const theme = useMantineTheme()
|
||||||
|
const colorScheme = useColorScheme()
|
||||||
|
|
||||||
|
return { theme, colorScheme }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { tss } = createTss({ useContext })
|
export const { tss } = createTss({ useContext })
|
||||||
|
|
||||||
export const useStyles = tss.create({})
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default defineConfig(env => ({
|
|||||||
"/openapi.json": "http://localhost:8083",
|
"/openapi.json": "http://localhost:8083",
|
||||||
"/custom_css.css": "http://localhost:8083",
|
"/custom_css.css": "http://localhost:8083",
|
||||||
"/custom_js.js": "http://localhost:8083",
|
"/custom_js.js": "http://localhost:8083",
|
||||||
|
"/logout": "http://localhost:8083",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
@@ -95,11 +95,11 @@ app:
|
|||||||
# -------------------
|
# -------------------
|
||||||
# for MariaDB
|
# for MariaDB
|
||||||
# driverClass is org.mariadb.jdbc.Driver
|
# driverClass is org.mariadb.jdbc.Driver
|
||||||
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
|
||||||
#
|
#
|
||||||
# for MySQL
|
# for MySQL
|
||||||
# driverClass is com.mysql.cj.jdbc.Driver
|
# driverClass is com.mysql.cj.jdbc.Driver
|
||||||
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
|
||||||
#
|
#
|
||||||
# for PostgreSQL
|
# for PostgreSQL
|
||||||
# driverClass is org.postgresql.Driver
|
# driverClass is org.postgresql.Driver
|
||||||
|
|||||||
@@ -95,11 +95,11 @@ app:
|
|||||||
# -------------------
|
# -------------------
|
||||||
# for MariaDB
|
# for MariaDB
|
||||||
# driverClass is org.mariadb.jdbc.Driver
|
# driverClass is org.mariadb.jdbc.Driver
|
||||||
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
|
||||||
#
|
#
|
||||||
# for MySQL
|
# for MySQL
|
||||||
# driverClass is com.mysql.cj.jdbc.Driver
|
# driverClass is com.mysql.cj.jdbc.Driver
|
||||||
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
|
||||||
#
|
#
|
||||||
# for PostgreSQL
|
# for PostgreSQL
|
||||||
# driverClass is org.postgresql.Driver
|
# driverClass is org.postgresql.Driver
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.3.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commafeed-server</artifactId>
|
<artifactId>commafeed-server</artifactId>
|
||||||
<name>CommaFeed Server</name>
|
<name>CommaFeed Server</name>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.5.1</version>
|
<version>3.5.2</version>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.kordamp.shade</groupId>
|
<groupId>org.kordamp.shade</groupId>
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed-client</artifactId>
|
<artifactId>commafeed-client</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.3.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -239,6 +239,10 @@
|
|||||||
<groupId>io.dropwizard</groupId>
|
<groupId>io.dropwizard</groupId>
|
||||||
<artifactId>dropwizard-core</artifactId>
|
<artifactId>dropwizard-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.dropwizard</groupId>
|
||||||
|
<artifactId>dropwizard-unix-socket</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.dropwizard</groupId>
|
<groupId>io.dropwizard</groupId>
|
||||||
<artifactId>dropwizard-hibernate</artifactId>
|
<artifactId>dropwizard-hibernate</artifactId>
|
||||||
@@ -332,7 +336,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
<artifactId>jedis</artifactId>
|
<artifactId>jedis</artifactId>
|
||||||
<version>5.1.0</version>
|
<version>5.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.sun.mail</groupId>
|
<groupId>com.sun.mail</groupId>
|
||||||
@@ -393,13 +397,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.hakky54</groupId>
|
<groupId>io.github.hakky54</groupId>
|
||||||
<artifactId>sslcontext-kickstart-for-apache5</artifactId>
|
<artifactId>sslcontext-kickstart-for-apache5</artifactId>
|
||||||
<version>8.3.1</version>
|
<version>8.3.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.apis</groupId>
|
<groupId>com.google.apis</groupId>
|
||||||
<artifactId>google-api-services-youtube</artifactId>
|
<artifactId>google-api-services-youtube</artifactId>
|
||||||
<version>v3-rev20240123-2.0.0</version>
|
<version>v3-rev20240225-2.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -420,12 +424,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mariadb.jdbc</groupId>
|
<groupId>org.mariadb.jdbc</groupId>
|
||||||
<artifactId>mariadb-java-client</artifactId>
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
<version>3.3.2</version>
|
<version>3.3.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>42.7.1</version>
|
<version>42.7.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.sourceforge.jtds</groupId>
|
<groupId>net.sourceforge.jtds</groupId>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
|
|||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.commafeed.backend.model.Models;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.service.FeedEntryService;
|
import com.commafeed.backend.service.FeedEntryService;
|
||||||
import com.commafeed.backend.service.FeedService;
|
import com.commafeed.backend.service.FeedService;
|
||||||
@@ -177,7 +178,7 @@ public class FeedRefreshUpdater {
|
|||||||
|
|
||||||
if (!processed) {
|
if (!processed) {
|
||||||
// requeue asap
|
// requeue asap
|
||||||
feed.setDisabledUntil(Feed.MINIMUM_DISABLED_UNTIL);
|
feed.setDisabledUntil(Models.MINIMUM_INSTANT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inserted > 0) {
|
if (inserted > 0) {
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ import lombok.Setter;
|
|||||||
@Setter
|
@Setter
|
||||||
public class Feed extends AbstractModel {
|
public class Feed extends AbstractModel {
|
||||||
|
|
||||||
// mariadb timestamp range starts at 1970-01-01 00:00:01
|
|
||||||
public static final Instant MINIMUM_DISABLED_UNTIL = Instant.EPOCH.plusSeconds(1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The url of the feed
|
* The url of the feed
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
package com.commafeed.backend.model;
|
package com.commafeed.backend.model;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
public class Models {
|
public class Models {
|
||||||
|
|
||||||
|
public static final Instant MINIMUM_INSTANT = Instant.EPOCH
|
||||||
|
// mariadb timestamp range starts at 1970-01-01 00:00:01
|
||||||
|
.plusSeconds(1)
|
||||||
|
// make sure the timestamp fits for all timezones
|
||||||
|
.plus(Duration.ofHours(24));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize a proxy
|
* initialize a proxy
|
||||||
*/
|
*/
|
||||||
@@ -18,8 +30,8 @@ public class Models {
|
|||||||
* extract the id from the proxy without initializing it
|
* extract the id from the proxy without initializing it
|
||||||
*/
|
*/
|
||||||
public static Long getId(AbstractModel model) {
|
public static Long getId(AbstractModel model) {
|
||||||
if (model instanceof HibernateProxy) {
|
if (model instanceof HibernateProxy proxy) {
|
||||||
LazyInitializer lazyInitializer = ((HibernateProxy) model).getHibernateLazyInitializer();
|
LazyInitializer lazyInitializer = proxy.getHibernateLazyInitializer();
|
||||||
if (lazyInitializer.isUninitialized()) {
|
if (lazyInitializer.isUninitialized()) {
|
||||||
return (Long) lazyInitializer.getIdentifier();
|
return (Long) lazyInitializer.getIdentifier();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.commafeed.backend.favicon.AbstractFaviconFetcher;
|
|||||||
import com.commafeed.backend.favicon.Favicon;
|
import com.commafeed.backend.favicon.Favicon;
|
||||||
import com.commafeed.backend.feed.FeedUtils;
|
import com.commafeed.backend.feed.FeedUtils;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.Models;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
@@ -45,7 +46,7 @@ public class FeedService {
|
|||||||
feed.setUrl(url);
|
feed.setUrl(url);
|
||||||
feed.setNormalizedUrl(normalizedUrl);
|
feed.setNormalizedUrl(normalizedUrl);
|
||||||
feed.setNormalizedUrlHash(normalizedUrlHash);
|
feed.setNormalizedUrlHash(normalizedUrlHash);
|
||||||
feed.setDisabledUntil(Feed.MINIMUM_DISABLED_UNTIL);
|
feed.setDisabledUntil(Models.MINIMUM_INSTANT);
|
||||||
feedDAO.saveOrUpdate(feed);
|
feedDAO.saveOrUpdate(feed);
|
||||||
}
|
}
|
||||||
return feed;
|
return feed;
|
||||||
|
|||||||
@@ -95,11 +95,11 @@ app:
|
|||||||
# -------------------
|
# -------------------
|
||||||
# for MariaDB
|
# for MariaDB
|
||||||
# driverClass is org.mariadb.jdbc.Driver
|
# driverClass is org.mariadb.jdbc.Driver
|
||||||
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
|
||||||
#
|
#
|
||||||
# for MySQL
|
# for MySQL
|
||||||
# driverClass is com.mysql.cj.jdbc.Driver
|
# driverClass is com.mysql.cj.jdbc.Driver
|
||||||
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
|
||||||
#
|
#
|
||||||
# for PostgreSQL
|
# for PostgreSQL
|
||||||
# driverClass is org.postgresql.Driver
|
# driverClass is org.postgresql.Driver
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.3.2</version>
|
||||||
<name>CommaFeed</name>
|
<name>CommaFeed</name>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user