Compare commits

...

22 Commits
3.2.0 ... 3.3.2

Author SHA1 Message Date
Athou
6de817f539 release 3.3.2 2023-05-20 14:00:54 +02:00
Athou
08a2746921 restore entry selection indicator 2023-05-19 10:37:59 +02:00
Athou
bc28727e39 remove warnings 2023-05-17 16:14:21 +02:00
Athou
eceaf3a98d remove lodash to reduce bundle size by 100kb 2023-05-17 16:11:32 +02:00
Athou
4a8939e5e5 optimized png sizes 2023-05-17 15:47:22 +02:00
Athou
e90b80c641 send GA pageviews only if initialized 2023-05-17 13:43:25 +02:00
Athou
2979600cc2 add dividers to separate read-only information from forms 2023-05-11 11:45:23 +02:00
Athou
a2deef7f7f release 3.3.1 2023-05-10 20:26:45 +02:00
Athou
b5097d4fc3 fix long feed names not being shortened to respect tree max width (#1055) 2023-05-10 20:25:50 +02:00
Athou
f858eed150 release 3.3.0 2023-05-10 08:34:09 +02:00
Athou
bbdd712b01 compiler is only needed in the java module 2023-05-09 14:45:19 +02:00
Athou
c0875971e9 no longer need to insert code between imports 2023-05-08 17:30:32 +02:00
Athou
0199ebb6c3 major mantine update 2023-05-08 17:30:32 +02:00
Athou
c5763e2f8f update all dependencies 2023-05-08 13:42:16 +02:00
Athou
5338ec0c34 lingui major update 2023-05-08 13:38:27 +02:00
Athou
8b5735f521 use Trans as much as possible to ease lingui upgrade to 4.0 2023-05-08 12:51:51 +02:00
Athou
3d1a1cd033 add support for custom js code that will be executed on page load (#1032) 2023-05-05 20:23:23 +02:00
Athou
b1b5eeb0e0 delete removed settings 2023-05-05 17:50:06 +02:00
Athou
49e37587f9 show alert on error 2023-05-05 14:56:53 +02:00
Athou
01102ae973 use absolute imports 2023-05-05 14:55:03 +02:00
Athou
e7931bf360 call reload() only once 2023-05-05 14:47:15 +02:00
Athou
d095e4b35a restore custom css setting (#1024) 2023-05-05 14:12:31 +02:00
89 changed files with 13006 additions and 12663 deletions

View File

@@ -1,5 +1,21 @@
# Changelog # Changelog
## [3.3.2]
- restore entry selection indicator (left orange border) that was lost with the mantine 6.x upgrade (3.3.0)
- add dividers to visually separate read-only information from forms on feed and category details pages
- reduced js bundle size by 10%
## [3.3.1]
- fix long feed names not being shortened to respect tree max width
## [3.3.0]
- there are now database changes, rolling back to 2.x will no longer be possible
- restore support for user custom CSS rules
- add support for user custom JS code that will be executed on page load
## [3.2.0] ## [3.2.0]
- restore the welcome page - restore the welcome page
@@ -21,10 +37,10 @@
## [3.0.1] ## [3.0.1]
- allow env variable substitution in config.yml - allow env variable substitution in config.yml
- e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with - e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with
its value its value
- allow env variable prefixed with `CF_` to override config.yml properties - allow env variable prefixed with `CF_` to override config.yml properties
- e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true` - e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true`
## [3.0.0] ## [3.0.0]
@@ -131,4 +147,4 @@
consumption and better overall performances. consumption and better overall performances.
See the README on how to build CommaFeed from now on. See the README on how to build CommaFeed from now on.
- CommaFeed should no longer fetch the same feed multiple times in a row - CommaFeed should no longer fetch the same feed multiple times in a row
- Users can use their username or email to log in - Users can use their username or email to log in

View File

@@ -48,10 +48,5 @@
"sourceLocale": "en", "sourceLocale": "en",
"fallbackLocales": { "fallbackLocales": {
"default": "en" "default": "en"
},
"extractBabelOptions": {
"presets": [
"@babel/preset-typescript"
]
} }
} }

View File

@@ -1,14 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="favicon.ico" /> <link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <link rel="stylesheet" href="custom_css.css" />
<title>CommaFeed</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head> <title>CommaFeed</title>
<body> </head>
<div id="root"></div> <body>
<script type="module" src="/src/main.tsx"></script> <div id="root"></div>
</body> <script type="module" src="/src/main.tsx"></script>
<script src="custom_js.js"></script>
</body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,84 @@
{ {
"name": "commafeed-client", "name": "commafeed-client",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"dev:typescript": "tsc --watch", "dev:typescript": "tsc --watch",
"build": "npm run i18n:compile && tsc && vite build", "build": "npm run i18n:compile && tsc && vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest", "test": "vitest",
"test:ci": "vitest run", "test:ci": "vitest run",
"eslint": "eslint --ext=.js,.jsx,.ts,.tsx src", "eslint": "eslint --ext=.js,.jsx,.ts,.tsx src",
"i18n": "npm run i18n:extract && npm run i18n:compile", "i18n": "npm run i18n:extract && npm run i18n:compile",
"i18n:extract": "lingui extract --clean", "i18n:extract": "lingui extract --clean",
"i18n:compile": "lingui compile --typescript", "i18n:compile": "lingui compile --typescript",
"postinstall": "npm run i18n:compile" "postinstall": "npm run i18n:compile"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5", "@emotion/react": "^11.11.0",
"@fontsource/open-sans": "^4.5.14", "@fontsource/open-sans": "^4.5.14",
"@lingui/core": "^3.17.0", "@lingui/core": "^4.0.0",
"@lingui/macro": "^3.17.0", "@lingui/macro": "^4.0.0",
"@lingui/react": "^3.17.0", "@lingui/react": "^4.0.0",
"@mantine/core": "^5.10.3", "@mantine/core": "^6.0.10",
"@mantine/form": "^5.10.3", "@mantine/form": "^6.0.10",
"@mantine/hooks": "^5.10.3", "@mantine/hooks": "^6.0.10",
"@mantine/modals": "^5.10.3", "@mantine/modals": "^6.0.10",
"@mantine/notifications": "^5.10.3", "@mantine/notifications": "^6.0.10",
"@mantine/spotlight": "^5.10.3", "@mantine/spotlight": "^6.0.10",
"@mantine/styles": "^5.10.3", "@mantine/styles": "^6.0.10",
"@reduxjs/toolkit": "^1.9.2", "@reduxjs/toolkit": "^1.9.5",
"axios": "^1.3.2", "axios": "^1.4.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"interweave": "^13.0.0", "interweave": "^13.1.0",
"lodash": "^4.17.21", "mousetrap": "^1.6.5",
"make-plural": "^7.2.0", "react": "^18.2.0",
"mousetrap": "^1.6.5", "react-async-hook": "^4.0.0",
"react": "^18.2.0", "react-contexify": "^6.0.0",
"react-async-hook": "^4.0.0", "react-dom": "^18.2.0",
"react-contexify": "^6.0.0", "react-ga4": "^2.1.0",
"react-dom": "^18.2.0", "react-icons": "^4.8.0",
"react-ga4": "^2.1.0", "react-infinite-scroller": "^1.2.6",
"react-icons": "^4.7.1", "react-redux": "^8.0.5",
"react-infinite-scroller": "^1.2.6", "react-router-dom": "^6.11.1",
"react-redux": "^8.0.5", "react-swipeable": "^7.0.0",
"react-router-dom": "^6.8.0", "swagger-ui-react": "^4.18.3",
"react-swipeable": "^7.0.0", "throttle-debounce": "^5.0.0",
"swagger-ui-react": "^4.15.5", "tinycon": "^0.6.8",
"tinycon": "^0.6.8", "use-local-storage": "^3.0.0",
"use-local-storage": "^3.0.0", "websocket-heartbeat-js": "^1.1.2"
"websocket-heartbeat-js": "^1.1.1" },
}, "devDependencies": {
"devDependencies": { "@lingui/cli": "^4.0.0",
"@lingui/cli": "^3.17.0", "@lingui/vite-plugin": "^4.0.0",
"@types/eslint": "^8.21.0", "@types/eslint": "^8.37.0",
"@types/lodash": "^4.14.191", "@types/mousetrap": "^1.6.11",
"@types/mousetrap": "^1.6.11", "@types/react": "^18.2.6",
"@types/react": "^18.0.27", "@types/react-dom": "^18.2.4",
"@types/react-dom": "^18.0.10", "@types/react-infinite-scroller": "^1.2.3",
"@types/react-infinite-scroller": "^1.2.3", "@types/swagger-ui-react": "^4.18.0",
"@types/swagger-ui-react": "^4.11.0", "@types/throttle-debounce": "^5.0.0",
"@types/tinycon": "^0.6.3", "@types/tinycon": "^0.6.3",
"@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.50.0", "@typescript-eslint/parser": "^5.59.2",
"@vitejs/plugin-react": "^3.1.0", "@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.33.0", "babel-plugin-macros": "^3.1.0",
"eslint-config-airbnb": "^19.0.4", "eslint": "^8.40.0",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.6.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-hooks": "^0.4.3", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-hooks": "^0.4.3",
"prettier": "^2.8.3", "eslint-plugin-prettier": "^4.2.1",
"rollup-plugin-visualizer": "^5.9.0", "prettier": "^2.8.8",
"typescript": "^4.9.5", "rollup-plugin-visualizer": "^5.9.0",
"vite": "^4.1.1", "typescript": "^5.0.4",
"vite-plugin-eslint": "^1.8.1", "vite": "^4.3.5",
"vite-tsconfig-paths": "^4.0.5", "vite-plugin-eslint": "^1.8.1",
"vitest": "^0.28.4", "vite-tsconfig-paths": "^4.2.0",
"vitest-mock-extended": "^1.0.9" "vitest": "^0.31.0",
} "vitest-mock-extended": "^1.1.3"
}
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.2.0</version> <version>3.3.2</version>
</parent> </parent>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name> <name>CommaFeed Client</name>

View File

@@ -3,7 +3,7 @@ import { I18nProvider } from "@lingui/react"
import { ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core" import { ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core"
import { useColorScheme } from "@mantine/hooks" import { useColorScheme } from "@mantine/hooks"
import { ModalsProvider } from "@mantine/modals" import { ModalsProvider } from "@mantine/modals"
import { NotificationsProvider } from "@mantine/notifications" import { Notifications } from "@mantine/notifications"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectTo } from "app/slices/redirect" import { redirectTo } from "app/slices/redirect"
import { reloadServerInfos } from "app/slices/server" import { reloadServerInfos } from "app/slices/server"
@@ -26,12 +26,12 @@ import { TagDetailsPage } from "pages/app/TagDetailsPage"
import { LoginPage } from "pages/auth/LoginPage" import { LoginPage } from "pages/auth/LoginPage"
import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage" import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage"
import { RegistrationPage } from "pages/auth/RegistrationPage" import { RegistrationPage } from "pages/auth/RegistrationPage"
import { WelcomePage } from "pages/WelcomePage"
import React, { useEffect } from "react" import React, { useEffect } from "react"
import ReactGA from "react-ga4" import ReactGA from "react-ga4"
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom" import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import Tinycon from "tinycon" import Tinycon from "tinycon"
import useLocalStorage from "use-local-storage" import useLocalStorage from "use-local-storage"
import { WelcomePage } from "./pages/WelcomePage"
function Providers(props: { children: React.ReactNode }) { function Providers(props: { children: React.ReactNode }) {
const preferredColorScheme = useColorScheme() const preferredColorScheme = useColorScheme()
@@ -51,9 +51,8 @@ function Providers(props: { children: React.ReactNode }) {
}} }}
> >
<ModalsProvider> <ModalsProvider>
<NotificationsProvider position="bottom-right" zIndex={9999}> <Notifications position="bottom-right" zIndex={9999} />
<ErrorBoundary>{props.children}</ErrorBoundary> <ErrorBoundary>{props.children}</ErrorBoundary>
</NotificationsProvider>
</ModalsProvider> </ModalsProvider>
</MantineProvider> </MantineProvider>
</ColorSchemeProvider> </ColorSchemeProvider>
@@ -123,7 +122,7 @@ function GoogleAnalyticsHandler() {
}, [googleAnalyticsCode]) }, [googleAnalyticsCode])
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname }) if (ReactGA.isInitialized) ReactGA.send({ hitType: "pageview", page: location.pathname })
}, [location]) }, [location])
return null return null

View File

@@ -1,19 +1,20 @@
/* eslint-disable import/first */ /* eslint-disable import/first */
import { beforeEach, describe, expect, it, vi } from "vitest"
import { DeepMockProxy, mockDeep, mockReset } from "vitest-mock-extended"
vi.doMock("app/client", () => ({ client: mockDeep() }))
import { configureStore } from "@reduxjs/toolkit" import { configureStore } from "@reduxjs/toolkit"
import { client } from "app/client" import { client } from "app/client"
import { reducers } from "app/store" import { reducers } from "app/store"
import { Entries, Entry } from "app/types" import { Entries, Entry } from "app/types"
import { AxiosResponse } from "axios" import { AxiosResponse } from "axios"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { mockReset } from "vitest-mock-extended"
import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "./entries" import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "./entries"
describe("entries", () => { const mockClient = await vi.hoisted(async () => {
const mockClient = client as DeepMockProxy<typeof client> const mockModule = await import("vitest-mock-extended")
return mockModule.mockDeep<typeof client>()
})
vi.mock("app/client", () => ({ client: mockClient }))
describe("entries", () => {
beforeEach(() => { beforeEach(() => {
mockReset(mockClient) mockReset(mockClient)
}) })

View File

@@ -230,8 +230,8 @@ export interface Settings {
readingOrder: ReadingOrder readingOrder: ReadingOrder
showRead: boolean showRead: boolean
scrollMarks: boolean scrollMarks: boolean
theme?: string
customCss?: string customCss?: string
customJs?: string
scrollSpeed: number scrollSpeed: number
sharingSettings: SharingSettings sharingSettings: SharingSettings
} }

View File

@@ -64,3 +64,5 @@ export const openLinkInBackgroundTab = (url: string) => {
}) })
) )
} }
export const truncate = (str: string, n: number) => (str.length > n ? `${str.slice(0, n - 1)}\u2026` : str)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,14 +1,15 @@
import { ActionIcon, Button, ButtonVariant, useMantineTheme } from "@mantine/core" import { ActionIcon, Button, useMantineTheme } from "@mantine/core"
import { ActionIconVariant } from "@mantine/core/lib/ActionIcon/ActionIcon.styles" import { ActionIconProps } from "@mantine/core/lib/ActionIcon/ActionIcon"
import { ButtonProps } from "@mantine/core/lib/Button/Button"
import { useMediaQuery } from "@mantine/hooks" import { useMediaQuery } from "@mantine/hooks"
import { forwardRef, MouseEventHandler, ReactNode } from "react" import { forwardRef, MouseEventHandler, ReactNode } from "react"
interface ActionButtonProps { interface ActionButtonProps {
className?: string className?: string
icon?: ReactNode icon?: ReactNode
label?: string label?: ReactNode
onClick?: MouseEventHandler onClick?: MouseEventHandler
variant?: ActionIconVariant & ButtonVariant variant?: ActionIconProps["variant"] & ButtonProps["variant"]
showLabelOnMobile?: boolean showLabelOnMobile?: boolean
} }
@@ -18,7 +19,7 @@ interface ActionButtonProps {
export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => { export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => {
const theme = useMantineTheme() const theme = useMantineTheme()
const variant = props.variant ?? "subtle" const variant = props.variant ?? "subtle"
const mobile = !useMediaQuery(`(min-width: ${theme.breakpoints.lg}px)`) const mobile = !useMediaQuery(`(min-width: ${theme.breakpoints.lg})`)
const iconOnly = !props.showLabelOnMobile && (mobile || !props.label) const iconOnly = !props.showLabelOnMobile && (mobile || !props.label)
return iconOnly ? ( return iconOnly ? (
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}> <ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}>

View File

@@ -1,5 +1,5 @@
import { t } from "@lingui/macro" 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 { Fragment } from "react"
import { TbAlertCircle, TbAlertTriangle, TbCircleCheck } from "react-icons/tb" import { TbAlertCircle, TbAlertTriangle, TbCircleCheck } from "react-icons/tb"
@@ -10,24 +10,24 @@ export interface ErrorsAlertProps {
} }
export function Alert(props: ErrorsAlertProps) { export function Alert(props: ErrorsAlertProps) {
let title: string let title: React.ReactNode
let color: string let color: string
let icon: React.ReactNode let icon: React.ReactNode
const level = props.level ?? "error" const level = props.level ?? "error"
switch (level) { switch (level) {
case "error": case "error":
title = t`Error` title = <Trans>Error</Trans>
color = "red" color = "red"
icon = <TbAlertCircle /> icon = <TbAlertCircle />
break break
case "warning": case "warning":
title = t`Warning` title = <Trans>Warning</Trans>
color = "orange" color = "orange"
icon = <TbAlertTriangle /> icon = <TbAlertTriangle />
break break
case "success": case "success":
title = t`Success` title = <Trans>Success</Trans>
color = "green" color = "green"
icon = <TbCircleCheck /> icon = <TbCircleCheck />
break break

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Button, Checkbox, Group, PasswordInput, Stack, TextInput } from "@mantine/core" import { Box, Button, Checkbox, Group, PasswordInput, Stack, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
@@ -29,11 +29,11 @@ export function UserEdit(props: UserEditProps) {
<form onSubmit={form.onSubmit(saveUser.execute)}> <form onSubmit={form.onSubmit(saveUser.execute)}>
<Stack> <Stack>
<TextInput label={t`Name`} {...form.getInputProps("name")} required /> <TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
<PasswordInput label={t`Password`} {...form.getInputProps("password")} required={!props.user} /> <PasswordInput label={<Trans>Password</Trans>} {...form.getInputProps("password")} required={!props.user} />
<TextInput type="email" label={t`E-mail`} {...form.getInputProps("email")} /> <TextInput type="email" label={<Trans>E-mail</Trans>} {...form.getInputProps("email")} />
<Checkbox label={t`Admin`} {...form.getInputProps("admin", { type: "checkbox" })} /> <Checkbox label={<Trans>Admin</Trans>} {...form.getInputProps("admin", { type: "checkbox" })} />
<Checkbox label={t`Enabled`} {...form.getInputProps("enabled", { type: "checkbox" })} /> <Checkbox label={<Trans>Enabled</Trans>} {...form.getInputProps("enabled", { type: "checkbox" })} />
<Group> <Group>
<Button variant="default" onClick={props.onCancel}> <Button variant="default" onClick={props.onCancel}>

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { openModal } from "@mantine/modals" import { openModal } from "@mantine/modals"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { import {
@@ -17,11 +17,11 @@ import { openLinkInBackgroundTab } from "app/utils"
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useMousetrap } from "hooks/useMousetrap" import { useMousetrap } from "hooks/useMousetrap"
import throttle from "lodash/throttle" import { useViewMode } from "hooks/useViewMode"
import { useEffect } from "react" import { useEffect } from "react"
import InfiniteScroll from "react-infinite-scroller" import InfiniteScroll from "react-infinite-scroller"
import { throttle } from "throttle-debounce"
import { FeedEntry } from "./FeedEntry" import { FeedEntry } from "./FeedEntry"
import { useViewMode } from "../../hooks/useViewMode"
export function FeedEntries() { export function FeedEntries() {
const source = useAppSelector(state => state.entries.source) const source = useAppSelector(state => state.entries.source)
@@ -82,7 +82,7 @@ export function FeedEntries() {
) )
} }
} }
const throttledListener = throttle(listener, 100) const throttledListener = throttle(100, listener)
scrollArea?.addEventListener("scroll", throttledListener) scrollArea?.addEventListener("scroll", throttledListener)
return () => scrollArea?.removeEventListener("scroll", throttledListener) return () => scrollArea?.removeEventListener("scroll", throttledListener)
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry]) }, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
@@ -234,11 +234,18 @@ export function FeedEntries() {
) )
}) })
useMousetrap("g a", () => dispatch(redirectToRootCategory())) useMousetrap("g a", () => dispatch(redirectToRootCategory()))
useMousetrap("?", () => openModal({ title: t`Keyboard shortcuts`, size: "xl", children: <KeyboardShortcutsHelp /> })) useMousetrap("?", () =>
openModal({
title: <Trans>Keyboard shortcuts</Trans>,
size: "xl",
children: <KeyboardShortcutsHelp />,
})
)
if (!entries) return <Loader /> if (!entries) return <Loader />
return ( return (
<InfiniteScroll <InfiniteScroll
id="entries"
initialLoad={false} initialLoad={false}
loadMore={() => dispatch(loadMoreEntries())} loadMore={() => dispatch(loadMoreEntries())}
hasMore={hasMore} hasMore={hasMore}

View File

@@ -1,12 +1,12 @@
import { Anchor, Box, createStyles, Divider, Paper } from "@mantine/core" import { Box, createStyles, Divider, Paper } from "@mantine/core"
import { MantineNumberSize } from "@mantine/styles" import { MantineNumberSize } from "@mantine/styles"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { markEntry } from "app/slices/entries" import { markEntry } from "app/slices/entries"
import { useAppDispatch } from "app/store" import { useAppDispatch } from "app/store"
import { Entry, ViewMode } from "app/types" import { Entry, ViewMode } from "app/types"
import { useViewMode } from "hooks/useViewMode"
import React from "react" import React from "react"
import { useSwipeable } from "react-swipeable" import { useSwipeable } from "react-swipeable"
import { useViewMode } from "../../hooks/useViewMode"
import { FeedEntryBody } from "./FeedEntryBody" import { FeedEntryBody } from "./FeedEntryBody"
import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader" import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader"
import { FeedEntryContextMenu, useFeedEntryContextMenu } from "./FeedEntryContextMenu" import { FeedEntryContextMenu, useFeedEntryContextMenu } from "./FeedEntryContextMenu"
@@ -25,7 +25,7 @@ const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: View
if (theme.colorScheme === "dark") backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5] if (theme.colorScheme === "dark") backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5]
else backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit" else backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit"
let marginY = theme.spacing.xs let marginY = 10
if (props.viewMode === "title") marginY = 2 if (props.viewMode === "title") marginY = 2
else if (props.viewMode === "cozy") marginY = 6 else if (props.viewMode === "cozy") marginY = 6
@@ -53,13 +53,18 @@ const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: View
}, },
}, },
}, },
headerLink: {
color: "inherit",
textDecoration: "none",
},
body: { body: {
maxWidth: Constants.layout.entryMaxWidth, maxWidth: Constants.layout.entryMaxWidth,
}, },
} }
if (props.showSelectionIndicator) { if (props.showSelectionIndicator) {
styles.paper.borderLeftColor = theme.colorScheme === "dark" ? theme.colors.orange[4] : theme.colors.orange[6] const borderLeftColor = theme.colorScheme === "dark" ? theme.colors.orange[4] : theme.colors.orange[6]
styles.paper.borderLeftColor = `${borderLeftColor} !important`
} }
return styles return styles
@@ -91,8 +96,8 @@ export function FeedEntry(props: FeedEntryProps) {
const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy") const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy")
return ( return (
<Paper withBorder radius={borderRadius} className={classes.paper}> <Paper withBorder radius={borderRadius} className={classes.paper}>
<Anchor <a
variant="text" className={classes.headerLink}
href={props.entry.url} href={props.entry.url}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
@@ -104,7 +109,7 @@ export function FeedEntry(props: FeedEntryProps) {
{compactHeader && <FeedEntryCompactHeader entry={props.entry} />} {compactHeader && <FeedEntryCompactHeader entry={props.entry} />}
{!compactHeader && <FeedEntryHeader entry={props.entry} expanded={props.expanded} />} {!compactHeader && <FeedEntryHeader entry={props.entry} expanded={props.expanded} />}
</Box> </Box>
</Anchor> </a>
{props.expanded && ( {props.expanded && (
<Box px={paddingX} pb={paddingY}> <Box px={paddingX} pb={paddingY}>
<Box className={classes.body} sx={{ direction: props.entry.rtl ? "rtl" : "ltr" }}> <Box className={classes.body} sx={{ direction: props.entry.rtl ? "rtl" : "ltr" }}>

View File

@@ -1,15 +1,15 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { createStyles, Group } from "@mantine/core" import { createStyles, Group } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries" import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
import { redirectToFeed } from "app/slices/redirect" import { redirectToFeed } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types" import { Entry } from "app/types"
import { openLinkInBackgroundTab } from "app/utils" import { openLinkInBackgroundTab, truncate } from "app/utils"
import { throttle, truncate } from "lodash"
import { useEffect } from "react" import { useEffect } from "react"
import { Item, Menu, Separator, useContextMenu } from "react-contexify" import { Item, Menu, Separator, useContextMenu } from "react-contexify"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
import { throttle } from "throttle-debounce"
interface FeedEntryContextMenuProps { interface FeedEntryContextMenuProps {
entry: Entry entry: Entry
@@ -29,6 +29,7 @@ const useStyles = createStyles(theme => ({
})) }))
const menuId = (entry: Entry) => entry.id const menuId = (entry: Entry) => entry.id
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) { export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const { classes, theme } = useStyles() const { classes, theme } = useStyles()
const sourceType = useAppSelector(state => state.entries.source.type) const sourceType = useAppSelector(state => state.entries.source.type)
@@ -64,13 +65,13 @@ export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
<Item onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}> <Item onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}>
<Group> <Group>
{props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />} {props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />}
{props.entry.starred ? t`Unstar` : t`Star`} {props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
</Group> </Group>
</Item> </Item>
<Item onClick={() => dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}> <Item onClick={() => dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}>
<Group> <Group>
{props.entry.read ? <TbEyeOff size={iconSize} /> : <TbEyeCheck size={iconSize} />} {props.entry.read ? <TbEyeOff size={iconSize} /> : <TbEyeCheck size={iconSize} />}
{props.entry.read ? t`Keep unread` : t`Mark as read`} {props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
</Group> </Group>
</Item> </Item>
<Item onClick={() => dispatch(markEntriesUpToEntry(props.entry))}> <Item onClick={() => dispatch(markEntriesUpToEntry(props.entry))}>
@@ -91,7 +92,7 @@ export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
> >
<Group> <Group>
<TbRss size={iconSize} /> <TbRss size={iconSize} />
<Trans>Go to {truncate(props.entry.feedName)}</Trans> <Trans>Go to {truncate(props.entry.feedName, 30)}</Trans>
</Group> </Group>
</Item> </Item>
</> </>
@@ -117,7 +118,7 @@ export function useFeedEntryContextMenu(entry: Entry) {
const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId) const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId)
const listener = () => contextMenu.hideAll() const listener = () => contextMenu.hideAll()
const throttledListener = throttle(listener, 100) const throttledListener = throttle(100, listener)
scrollArea?.addEventListener("scroll", throttledListener) scrollArea?.addEventListener("scroll", throttledListener)
return () => scrollArea?.removeEventListener("scroll", throttledListener) return () => scrollArea?.removeEventListener("scroll", throttledListener)

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { Group, Indicator, MultiSelect, Popover } from "@mantine/core" import { Group, Indicator, MultiSelect, Popover } from "@mantine/core"
import { useMediaQuery } from "@mantine/hooks" import { useMediaQuery } from "@mantine/hooks"
import { Constants } from "app/constants" import { Constants } from "app/constants"
@@ -7,9 +7,9 @@ import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types" import { Entry } from "app/types"
import { ActionButton } from "components/ActionButtton" import { ActionButton } from "components/ActionButtton"
import { ButtonToolbar } from "components/ButtonToolbar" import { ButtonToolbar } from "components/ButtonToolbar"
import { throttle } from "lodash"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb"
import { throttle } from "throttle-debounce"
import { ShareButtons } from "./ShareButtons" import { ShareButtons } from "./ShareButtons"
interface FeedEntryFooterProps { interface FeedEntryFooterProps {
@@ -20,7 +20,7 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
const [scrollPosition, setScrollPosition] = useState(0) const [scrollPosition, setScrollPosition] = useState(0)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings) const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const tags = useAppSelector(state => state.user.tags) const tags = useAppSelector(state => state.user.tags)
const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint}px)`) const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint})`)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v) const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v)
@@ -38,7 +38,7 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId) const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId)
const listener = () => setScrollPosition(scrollArea ? scrollArea.scrollTop : 0) const listener = () => setScrollPosition(scrollArea ? scrollArea.scrollTop : 0)
const throttledListener = throttle(listener, 100) const throttledListener = throttle(100, listener)
scrollArea?.addEventListener("scroll", throttledListener) scrollArea?.addEventListener("scroll", throttledListener)
return () => scrollArea?.removeEventListener("scroll", throttledListener) return () => scrollArea?.removeEventListener("scroll", throttledListener)
@@ -50,20 +50,20 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
{props.entry.markable && ( {props.entry.markable && (
<ActionButton <ActionButton
icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />} icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />}
label={props.entry.read ? t`Keep unread` : t`Mark as read`} label={props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
onClick={readStatusButtonClicked} onClick={readStatusButtonClicked}
/> />
)} )}
<ActionButton <ActionButton
icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />} icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />}
label={props.entry.starred ? t`Unstar` : t`Star`} label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))} onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}
/> />
{showSharingButtons && ( {showSharingButtons && (
<Popover withArrow withinPortal shadow="md" positionDependencies={[scrollPosition]} closeOnClickOutside={!mobile}> <Popover withArrow withinPortal shadow="md" positionDependencies={[scrollPosition]} closeOnClickOutside={!mobile}>
<Popover.Target> <Popover.Target>
<ActionButton icon={<TbShare size={18} />} label={t`Share`} /> <ActionButton icon={<TbShare size={18} />} label={<Trans>Share</Trans>} />
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
<ShareButtons url={props.entry.url} description={props.entry.title} /> <ShareButtons url={props.entry.url} description={props.entry.title} />
@@ -74,8 +74,8 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
{tags && ( {tags && (
<Popover withArrow withinPortal shadow="md" positionDependencies={[scrollPosition]} closeOnClickOutside={!mobile}> <Popover withArrow withinPortal shadow="md" positionDependencies={[scrollPosition]} closeOnClickOutside={!mobile}>
<Popover.Target> <Popover.Target>
<Indicator label={props.entry.tags.length} showZero={false} dot={false} inline size={16}> <Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}>
<ActionButton icon={<TbTag size={18} />} label={t`Tags`} /> <ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} />
</Indicator> </Indicator>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
@@ -94,13 +94,13 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
)} )}
<a href={props.entry.url} target="_blank" rel="noreferrer"> <a href={props.entry.url} target="_blank" rel="noreferrer">
<ActionButton icon={<TbExternalLink size={18} />} label={t`Open link`} /> <ActionButton icon={<TbExternalLink size={18} />} label={<Trans>Open link</Trans>} />
</a> </a>
</ButtonToolbar> </ButtonToolbar>
<ActionButton <ActionButton
icon={<TbArrowBarToDown size={18} />} icon={<TbArrowBarToDown size={18} />}
label={t`Mark as read up to here`} label={<Trans>Mark as read up to here</Trans>}
onClick={() => dispatch(markEntriesUpToEntry(props.entry))} onClick={() => dispatch(markEntriesUpToEntry(props.entry))}
/> />
</Group> </Group>

View File

@@ -33,8 +33,8 @@ export function AddCategory() {
<form onSubmit={form.onSubmit(addCategory.execute)}> <form onSubmit={form.onSubmit(addCategory.execute)}>
<Stack> <Stack>
<TextInput label={t`Category`} placeholder={t`Category`} {...form.getInputProps("name")} required /> <TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required />
<CategorySelect label={t`Parent`} {...form.getInputProps("parentId")} clearable /> <CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable />
<Group position="center"> <Group position="center">
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>

View File

@@ -36,9 +36,14 @@ export function ImportOpml() {
<form onSubmit={form.onSubmit(v => importOpml.execute(v.file))}> <form onSubmit={form.onSubmit(v => importOpml.execute(v.file))}>
<Stack> <Stack>
<FileInput <FileInput
label={t`OPML file`} label={<Trans>OPML file</Trans>}
placeholder={t`OPML file`} placeholder={t`OPML file`}
description={t`An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your data from other feed reading services.`} description={
<Trans>
An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your
data from other feed reading services.
</Trans>
}
{...form.getInputProps("file")} {...form.getInputProps("file")}
required required
accept="application/xml" accept="application/xml"

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core" import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
@@ -74,24 +74,33 @@ export function Subscribe() {
<form onSubmit={nextStep}> <form onSubmit={nextStep}>
<Stepper active={activeStep} onStepClick={setActiveStep}> <Stepper active={activeStep} onStepClick={setActiveStep}>
<Stepper.Step <Stepper.Step
label={t`Analyze feed`} label={<Trans>Analyze feed</Trans>}
description={t`Check that the feed is working`} description={<Trans>Check that the feed is working</Trans>}
allowStepSelect={activeStep === 1} allowStepSelect={activeStep === 1}
> >
<TextInput <TextInput
label={t`Feed URL`} label={<Trans>Feed URL</Trans>}
placeholder="http://www.mysite.com/rss" placeholder="http://www.mysite.com/rss"
description={t`The URL for the feed you want to subscribe to. You can also use the website's url directly and CommaFeed will try to find the feed in the page.`} description={
<Trans>
The URL for the feed you want to subscribe to. You can also use the website's url directly and CommaFeed
will try to find the feed in the page.
</Trans>
}
required required
autoFocus autoFocus
{...step0Form.getInputProps("url")} {...step0Form.getInputProps("url")}
/> />
</Stepper.Step> </Stepper.Step>
<Stepper.Step label={t`Subscribe`} description={t`Subscribe to the feed`} allowStepSelect={false}> <Stepper.Step
label={<Trans>Subscribe</Trans>}
description={<Trans>Subscribe to the feed</Trans>}
allowStepSelect={false}
>
<Stack> <Stack>
<TextInput label={t`Feed URL`} {...step1Form.getInputProps("url")} disabled /> <TextInput label={<Trans>Feed URL</Trans>} {...step1Form.getInputProps("url")} disabled />
<TextInput label={t`Feed name`} {...step1Form.getInputProps("title")} required autoFocus /> <TextInput label={<Trans>Feed name</Trans>} {...step1Form.getInputProps("title")} required autoFocus />
<CategorySelect label={t`Category`} {...step1Form.getInputProps("categoryId")} clearable /> <CategorySelect label={<Trans>Category</Trans>} {...step1Form.getInputProps("categoryId")} clearable />
</Stack> </Stack>
</Stepper.Step> </Stepper.Step>
</Stepper> </Stepper>

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { ActionIcon, Center, Divider, Indicator, Popover, TextInput } from "@mantine/core" import { ActionIcon, Center, Divider, Indicator, Popover, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { reloadEntries, search } from "app/slices/entries" import { reloadEntries, search } from "app/slices/entries"
@@ -17,6 +17,7 @@ function HeaderDivider() {
} }
const iconSize = 18 const iconSize = 18
export function Header() { export function Header() {
const settings = useAppSelector(state => state.user.settings) const settings = useAppSelector(state => state.user.settings)
const profile = useAppSelector(state => state.user.profile) const profile = useAppSelector(state => state.user.profile)
@@ -40,26 +41,30 @@ export function Header() {
return ( return (
<Center> <Center>
<ButtonToolbar> <ButtonToolbar>
<ActionButton icon={<TbRefresh size={iconSize} />} label={t`Refresh`} onClick={() => dispatch(reloadEntries())} /> <ActionButton
icon={<TbRefresh size={iconSize} />}
label={<Trans>Refresh</Trans>}
onClick={() => dispatch(reloadEntries())}
/>
<MarkAllAsReadButton iconSize={iconSize} /> <MarkAllAsReadButton iconSize={iconSize} />
<HeaderDivider /> <HeaderDivider />
<ActionButton <ActionButton
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />} icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
label={settings.readingMode === "all" ? t`All` : t`Unread`} label={settings.readingMode === "all" ? <Trans>All</Trans> : <Trans>Unread</Trans>}
onClick={() => dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))} onClick={() => dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))}
/> />
<ActionButton <ActionButton
icon={settings.readingOrder === "asc" ? <TbArrowUp size={iconSize} /> : <TbArrowDown size={iconSize} />} icon={settings.readingOrder === "asc" ? <TbArrowUp size={iconSize} /> : <TbArrowDown size={iconSize} />}
label={settings.readingOrder === "asc" ? t`Asc` : t`Desc`} label={settings.readingOrder === "asc" ? <Trans>Asc</Trans> : <Trans>Desc</Trans>}
onClick={() => dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))} onClick={() => dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))}
/> />
<Popover> <Popover>
<Popover.Target> <Popover.Target>
<Indicator disabled={!searchFromStore}> <Indicator disabled={!searchFromStore}>
<ActionButton icon={<TbSearch size={iconSize} />} label={t`Search`} /> <ActionButton icon={<TbSearch size={iconSize} />} label={<Trans>Search</Trans>} />
</Indicator> </Indicator>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core" import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core"
import { markAllEntries } from "app/slices/entries" import { markAllEntries } from "app/slices/entries"
@@ -17,7 +17,7 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
return ( return (
<> <>
<Modal opened={opened} onClose={() => setOpened(false)} title={t`Mark all entries as read`}> <Modal opened={opened} onClose={() => setOpened(false)} title={<Trans>Mark all entries as read</Trans>}>
<Stack> <Stack>
<Text size="sm"> <Text size="sm">
{threshold === 0 && ( {threshold === 0 && (
@@ -72,7 +72,7 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
</Modal> </Modal>
<ActionButton <ActionButton
icon={<TbChecks size={props.iconSize} />} icon={<TbChecks size={props.iconSize} />}
label={t`Mark all as read`} label={<Trans>Mark all as read</Trans>}
onClick={() => { onClick={() => {
setThreshold(0) setThreshold(0)
setOpened(true) setOpened(true)

View File

@@ -1,10 +1,11 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Divider, Group, Menu, SegmentedControl, SegmentedControlItem, useMantineColorScheme } from "@mantine/core" import { Box, Divider, Group, Menu, SegmentedControl, SegmentedControlItem, useMantineColorScheme } from "@mantine/core"
import { showNotification } from "@mantine/notifications" import { showNotification } from "@mantine/notifications"
import { client } from "app/client" import { client } from "app/client"
import { redirectToAbout, redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect" import { redirectToAbout, redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { ViewMode } from "app/types" import { ViewMode } from "app/types"
import { useViewMode } from "hooks/useViewMode"
import { useState } from "react" import { useState } from "react"
import { import {
TbChartLine, TbChartLine,
@@ -20,7 +21,6 @@ import {
TbUsers, TbUsers,
TbWorldDownload, TbWorldDownload,
} from "react-icons/tb" } from "react-icons/tb"
import { useViewMode } from "../../hooks/useViewMode"
interface ProfileMenuProps { interface ProfileMenuProps {
control: React.ReactElement control: React.ReactElement
@@ -111,7 +111,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
onClick={() => onClick={() =>
client.feed.refreshAll().then(() => { client.feed.refreshAll().then(() => {
showNotification({ showNotification({
message: t`Your feeds have been queued for refresh.`, message: <Trans>Your feeds have been queued for refresh.</Trans>,
color: "green", color: "green",
autoClose: 1000, autoClose: 1000,
}) })

View File

@@ -0,0 +1,96 @@
import { Trans } from "@lingui/macro"
import { Box, Button, Group, Stack, Textarea } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { redirectToSelectedSource } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store"
import { Alert } from "components/Alert"
import { useEffect } from "react"
import { useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy } from "react-icons/tb"
interface FormData {
customCss: string
customJs: string
}
export function CustomCodeSettings() {
const settings = useAppSelector(state => state.user.settings)
const dispatch = useAppDispatch()
const form = useForm<FormData>()
const { setValues } = form
const saveCustomCode = useAsyncCallback(
async (d: FormData) => {
if (!settings) return
await client.user.saveSettings({
...settings,
customCss: d.customCss,
customJs: d.customJs,
})
},
{
onSuccess: () => {
window.location.reload()
},
}
)
useEffect(() => {
if (!settings) return
setValues({
customCss: settings.customCss,
customJs: settings.customJs,
})
}, [setValues, settings])
return (
<>
{saveCustomCode.error && (
<Box mb="md">
<Alert messages={errorToStrings(saveCustomCode.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(saveCustomCode.execute)}>
<Stack>
<Textarea
autosize
minRows={4}
maxRows={15}
{...form.getInputProps("customCss")}
description={<Trans>Custom CSS rules that will be applied</Trans>}
styles={{
input: {
fontFamily: "monospace",
},
}}
/>
<Textarea
autosize
minRows={4}
maxRows={15}
{...form.getInputProps("customJs")}
description={<Trans>Custom JS code that will be executed on page load</Trans>}
styles={{
input: {
fontFamily: "monospace",
},
}}
/>
<Group>
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveCustomCode.loading}>
<Trans>Save</Trans>
</Button>
</Group>
</Stack>
</form>
</>
)
}

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core" import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { changeLanguage, changeScrollMarks, changeScrollSpeed, changeSharingSetting, changeShowRead } from "app/slices/user" import { changeLanguage, changeScrollMarks, changeScrollSpeed, changeSharingSetting, changeShowRead } from "app/slices/user"
@@ -17,7 +17,7 @@ export function DisplaySettings() {
return ( return (
<Stack> <Stack>
<Select <Select
description={t`Language`} description={<Trans>Language</Trans>}
value={language} value={language}
data={locales.map(l => ({ data={locales.map(l => ({
value: l.key, value: l.key,
@@ -27,24 +27,24 @@ export function DisplaySettings() {
/> />
<Switch <Switch
label={t`Scroll smoothly when navigating between entries`} label={<Trans>Scroll smoothly when navigating between entries</Trans>}
checked={scrollSpeed ? scrollSpeed > 0 : false} checked={scrollSpeed ? scrollSpeed > 0 : false}
onChange={e => dispatch(changeScrollSpeed(e.currentTarget.checked))} onChange={e => dispatch(changeScrollSpeed(e.currentTarget.checked))}
/> />
<Switch <Switch
label={t`Show feeds and categories with no unread entries`} label={<Trans>Show feeds and categories with no unread entries</Trans>}
checked={showRead} checked={showRead}
onChange={e => dispatch(changeShowRead(e.currentTarget.checked))} onChange={e => dispatch(changeShowRead(e.currentTarget.checked))}
/> />
<Switch <Switch
label={t`In expanded view, scrolling through entries mark them as read`} label={<Trans>In expanded view, scrolling through entries mark them as read</Trans>}
checked={scrollMarks} checked={scrollMarks}
onChange={e => dispatch(changeScrollMarks(e.currentTarget.checked))} onChange={e => dispatch(changeScrollMarks(e.currentTarget.checked))}
/> />
<Divider label={t`Sharing sites`} 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 Array<keyof SharingSettings>).map(site => (

View File

@@ -41,13 +41,13 @@ export function ProfileSettings() {
const openDeleteProfileModal = () => const openDeleteProfileModal = () =>
openConfirmModal({ openConfirmModal({
title: t`Delete account`, title: <Trans>Delete account</Trans>,
children: ( children: (
<Text size="sm"> <Text size="sm">
<Trans>Are you sure you want to delete your account? There's no turning back!</Trans> <Trans>Are you sure you want to delete your account? There's no turning back!</Trans>
</Text> </Text>
), ),
labels: { confirm: t`Confirm`, cancel: t`Cancel` }, labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
confirmProps: { color: "red" }, confirmProps: { color: "red" },
onConfirm: () => deleteProfile.execute(), onConfirm: () => deleteProfile.execute(),
}) })
@@ -77,12 +77,16 @@ export function ProfileSettings() {
<form onSubmit={form.onSubmit(saveProfile.execute)}> <form onSubmit={form.onSubmit(saveProfile.execute)}>
<Stack> <Stack>
<Input.Wrapper label={t`User name`}> <Input.Wrapper label={<Trans>User name</Trans>}>
<Box>{profile?.name}</Box> <Box>{profile?.name}</Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper <Input.Wrapper
label={t`OPML export`} label={<Trans>OPML export</Trans>}
description={t`Export your subscriptions and categories as an OPML file that can be imported in other feed reading services`} description={
<Trans>
Export your subscriptions and categories as an OPML file that can be imported in other feed reading services
</Trans>
}
> >
<Box> <Box>
<Anchor href="rest/feed/export" download="commafeed_opml.xml"> <Anchor href="rest/feed/export" download="commafeed_opml.xml">
@@ -91,20 +95,20 @@ export function ProfileSettings() {
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<PasswordInput <PasswordInput
label={t`Current password`} label={<Trans>Current password</Trans>}
description={t`Enter your current password to change profile settings`} description={<Trans>Enter your current password to change profile settings</Trans>}
required required
{...form.getInputProps("currentPassword")} {...form.getInputProps("currentPassword")}
/> />
<TextInput type="email" label={t`E-mail`} {...form.getInputProps("email")} required /> <TextInput type="email" label={<Trans>E-mail</Trans>} {...form.getInputProps("email")} required />
<PasswordInput <PasswordInput
label={t`New password`} label={<Trans>New password</Trans>}
description={t`Changing password will generate a new API key`} description={<Trans>Changing password will generate a new API key</Trans>}
{...form.getInputProps("newPassword")} {...form.getInputProps("newPassword")}
/> />
<PasswordInput label={t`Confirm password`} {...form.getInputProps("newPasswordConfirmation")} /> <PasswordInput label={<Trans>Confirm password</Trans>} {...form.getInputProps("newPasswordConfirmation")} />
<TextInput label={t`API key`} readOnly value={profile?.apiKey} /> <TextInput label={<Trans>API key</Trans>} readOnly value={profile?.apiKey} />
<Checkbox label={t`Generate new API key`} {...form.getInputProps("newApiKey", { type: "checkbox" })} /> <Checkbox label={<Trans>Generate new API key</Trans>} {...form.getInputProps("newApiKey", { type: "checkbox" })} />
<Group> <Group>
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Stack } from "@mantine/core" import { Box, Stack } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { import {
@@ -27,6 +27,7 @@ const expandedIcon = <TbChevronDown size={16} />
const collapsedIcon = <TbChevronRight size={16} /> const collapsedIcon = <TbChevronRight size={16} />
const errorThreshold = 9 const errorThreshold = 9
export function Tree() { export function Tree() {
const root = useAppSelector(state => state.tree.rootCategory) const root = useAppSelector(state => state.tree.rootCategory)
const source = useAppSelector(state => state.entries.source) const source = useAppSelector(state => state.entries.source)
@@ -63,7 +64,7 @@ export function Tree() {
const allCategoryNode = () => ( const allCategoryNode = () => (
<TreeNode <TreeNode
id={Constants.categories.all.id} id={Constants.categories.all.id}
name={t`All`} name={<Trans>All</Trans>}
icon={allIcon} icon={allIcon}
unread={categoryUnreadCount(root)} unread={categoryUnreadCount(root)}
selected={source.type === "category" && source.id === Constants.categories.all.id} selected={source.type === "category" && source.id === Constants.categories.all.id}
@@ -76,7 +77,7 @@ export function Tree() {
const starredCategoryNode = () => ( const starredCategoryNode = () => (
<TreeNode <TreeNode
id={Constants.categories.starred.id} id={Constants.categories.starred.id}
name={t`Starred`} name={<Trans>Starred</Trans>}
icon={starredIcon} icon={starredIcon}
unread={0} unread={0}
selected={source.type === "category" && source.id === Constants.categories.starred.id} selected={source.type === "category" && source.id === Constants.categories.starred.id}

View File

@@ -5,8 +5,8 @@ import { UnreadCount } from "./UnreadCount"
interface TreeNodeProps { interface TreeNodeProps {
id: string id: string
name: string name: ReactNode
icon: ReactNode | string icon: ReactNode
unread: number unread: number
selected: boolean selected: boolean
expanded?: boolean expanded?: boolean

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { Box, Center, Kbd, TextInput } from "@mantine/core" import { Box, Center, Kbd, TextInput } from "@mantine/core"
import { openSpotlight, SpotlightAction, SpotlightProvider } from "@mantine/spotlight" import { openSpotlight, SpotlightAction, SpotlightProvider } from "@mantine/spotlight"
import { redirectToFeed } from "app/slices/redirect" import { redirectToFeed } from "app/slices/redirect"
@@ -11,6 +11,7 @@ import { TbSearch } from "react-icons/tb"
export interface TreeSearchProps { export interface TreeSearchProps {
feeds: Subscription[] feeds: Subscription[]
} }
export function TreeSearch(props: TreeSearchProps) { export function TreeSearch(props: TreeSearchProps) {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@@ -40,7 +41,7 @@ export function TreeSearch(props: TreeSearchProps) {
searchIcon={searchIcon} searchIcon={searchIcon}
searchPlaceholder={t`Search`} searchPlaceholder={t`Search`}
shortcut="ctrl+k" shortcut="ctrl+k"
nothingFoundMessage={t`Nothing found`} nothingFoundMessage={<Trans>Nothing found</Trans>}
> >
<TextInput <TextInput
placeholder={t`Search`} placeholder={t`Search`}

View File

@@ -1,5 +1,5 @@
import { ViewMode } from "app/types"
import useLocalStorage from "use-local-storage" import useLocalStorage from "use-local-storage"
import { ViewMode } from "../app/types"
export function useViewMode() { export function useViewMode() {
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed") const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed")

View File

@@ -29,37 +29,7 @@ import "dayjs/locale/sk"
import "dayjs/locale/sv" import "dayjs/locale/sv"
import "dayjs/locale/tr" import "dayjs/locale/tr"
import "dayjs/locale/zh" import "dayjs/locale/zh"
import {
ar,
ca,
cs,
cy,
da,
de,
en,
es,
fa,
fi,
fr,
gl,
hu,
id,
it,
ja,
ko,
ms,
nb,
nl,
nn,
pl,
PluralCategory,
pt,
ru,
sk,
sv,
tr,
zh,
} from "make-plural"
import { useEffect } from "react" import { useEffect } from "react"
import { messages as arMessages } from "./locales/ar/messages" import { messages as arMessages } from "./locales/ar/messages"
import { messages as caMessages } from "./locales/ca/messages" import { messages as caMessages } from "./locales/ca/messages"
@@ -94,48 +64,42 @@ interface Locale {
key: string key: string
label: string label: string
messages: Messages messages: Messages
plurals?: (n: number | string, ord?: boolean) => PluralCategory
} }
// add an object to the array to add a new locale // add an object to the array to add a new locale
// don't forget to also add it to the 'locales' array in .linguirc // don't forget to also add it to the 'locales' array in .linguirc
export const locales: Locale[] = [ export const locales: Locale[] = [
{ key: "ar", messages: arMessages, plurals: ar, label: "العربية" }, { key: "ar", messages: arMessages, label: "العربية" },
{ key: "ca", messages: caMessages, plurals: ca, label: "Català" }, { key: "ca", messages: caMessages, label: "Català" },
{ key: "cs", messages: csMessages, plurals: cs, label: "Čeština" }, { key: "cs", messages: csMessages, label: "Čeština" },
{ key: "cy", messages: cyMessages, plurals: cy, label: "Cymraeg" }, { key: "cy", messages: cyMessages, label: "Cymraeg" },
{ key: "da", messages: daMessages, plurals: da, label: "Danish" }, { key: "da", messages: daMessages, label: "Danish" },
{ key: "de", messages: deMessages, plurals: de, label: "Deutsch" }, { key: "de", messages: deMessages, label: "Deutsch" },
{ key: "en", messages: enMessages, plurals: en, label: "English" }, { key: "en", messages: enMessages, label: "English" },
{ key: "es", messages: esMessages, plurals: es, label: "Español" }, { key: "es", messages: esMessages, label: "Español" },
{ key: "fa", messages: faMessages, plurals: fa, label: "فارسی" }, { key: "fa", messages: faMessages, label: "فارسی" },
{ key: "fi", messages: fiMessages, plurals: fi, label: "Suomi" }, { key: "fi", messages: fiMessages, label: "Suomi" },
{ key: "fr", messages: frMessages, plurals: fr, label: "Français" }, { key: "fr", messages: frMessages, label: "Français" },
{ key: "gl", messages: glMessages, plurals: gl, label: "Galician" }, { key: "gl", messages: glMessages, label: "Galician" },
{ key: "hu", messages: huMessages, plurals: hu, label: "Magyar" }, { key: "hu", messages: huMessages, label: "Magyar" },
{ key: "id", messages: idMessages, plurals: id, label: "Indonesian" }, { key: "id", messages: idMessages, label: "Indonesian" },
{ key: "it", messages: itMessages, plurals: it, label: "Italiano" }, { key: "it", messages: itMessages, label: "Italiano" },
{ key: "ja", messages: jaMessages, plurals: ja, label: "日本語" }, { key: "ja", messages: jaMessages, label: "日本語" },
{ key: "ko", messages: koMessages, plurals: ko, label: "한국어" }, { key: "ko", messages: koMessages, label: "한국어" },
{ key: "ms", messages: msMessages, plurals: ms, label: "Bahasa Malaysian" }, { key: "ms", messages: msMessages, label: "Bahasa Malaysian" },
{ key: "nb", messages: nbMessages, plurals: nb, label: "Norsk (bokmål)" }, { key: "nb", messages: nbMessages, label: "Norsk (bokmål)" },
{ key: "nl", messages: nlMessages, plurals: nl, label: "Nederlands" }, { key: "nl", messages: nlMessages, label: "Nederlands" },
{ key: "nn", messages: nnMessages, plurals: nn, label: "Norsk (nynorsk)" }, { key: "nn", messages: nnMessages, label: "Norsk (nynorsk)" },
{ key: "pl", messages: plMessages, plurals: pl, label: "Polski" }, { key: "pl", messages: plMessages, label: "Polski" },
{ key: "pt", messages: ptMessages, plurals: pt, label: "Português" }, { key: "pt", messages: ptMessages, label: "Português" },
{ key: "ru", messages: ruMessages, plurals: ru, label: "Русский" }, { key: "ru", messages: ruMessages, label: "Русский" },
{ key: "sk", messages: skMessages, plurals: sk, label: "Slovenčina" }, { key: "sk", messages: skMessages, label: "Slovenčina" },
{ key: "sv", messages: svMessages, plurals: sv, label: "Svenska" }, { key: "sv", messages: svMessages, label: "Svenska" },
{ key: "tr", messages: trMessages, plurals: tr, label: "Türkçe" }, { key: "tr", messages: trMessages, label: "Türkçe" },
{ key: "zh", messages: zhMessages, plurals: zh, label: "简体中文" }, { key: "zh", messages: zhMessages, label: "简体中文" },
] ]
locales.forEach(l => { locales.forEach(l => {
i18n.loadLocaleData({
[l.key]: {
plurals: l.plurals,
},
})
i18n.load({ i18n.load({
[l.key]: l.messages, [l.key]: l.messages,
}) })

View File

@@ -123,6 +123,7 @@ msgstr "ملحقات المستعرض"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "السيطرة"
msgid "Current password" msgid "Current password"
msgstr "كلمة المرور الحالية" msgstr "كلمة المرور الحالية"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "تاريخ الإنشاء" msgstr "تاريخ الإنشاء"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Extensions del navegador"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Contrasenya actual" msgstr "Contrasenya actual"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Data de creació" msgstr "Data de creació"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Rozšíření prohlížeče"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Aktuální heslo" msgstr "Aktuální heslo"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Datum vytvoření" msgstr "Datum vytvoření"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Estyniadau porwr"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Cyfrinair presennol" msgstr "Cyfrinair presennol"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Dyddiad creu" msgstr "Dyddiad creu"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Browserudvidelser"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Nuværende adgangskode" msgstr "Nuværende adgangskode"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Dato oprettet" msgstr "Dato oprettet"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Browsererweiterungen"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "Strg"
msgid "Current password" msgid "Current password"
msgstr "Aktuelles Passwort" msgstr "Aktuelles Passwort"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Erstellungsdatum" msgstr "Erstellungsdatum"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Browser extentions"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "Ctrl"
msgid "Current password" msgid "Current password"
msgstr "Current password" msgstr "Current password"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr "Custom CSS rules that will be applied"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr "Custom JS code that will be executed on page load"
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr "Custom code"
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Date created" msgstr "Date created"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "Right click" msgstr "Right click"
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Extensiones del navegador"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Contraseña actual" msgstr "Contraseña actual"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Fecha de creación" msgstr "Fecha de creación"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "گسترش مرورگر"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "رمز عبور فعلی" msgstr "رمز عبور فعلی"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "تاریخ ایجاد" msgstr "تاریخ ایجاد"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Selaimen laajennukset"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Nykyinen salasana" msgstr "Nykyinen salasana"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Luontipäivämäärä" msgstr "Luontipäivämäärä"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Extensions pour navigateurs"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "Ctrl"
msgid "Current password" msgid "Current password"
msgstr "Mot de passe actuel" msgstr "Mot de passe actuel"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Date de création" msgstr "Date de création"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Extensións do navegador"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Contrasinal actual" msgstr "Contrasinal actual"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Data de creación" msgstr "Data de creación"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Böngészőbővítések"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Jelenlegi jelszó" msgstr "Jelenlegi jelszó"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Létrehozás dátuma" msgstr "Létrehozás dátuma"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Ekstensi peramban"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Kata sandi saat ini" msgstr "Kata sandi saat ini"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Tanggal dibuat" msgstr "Tanggal dibuat"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Estensioni del browser"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "ctrl"
msgid "Current password" msgid "Current password"
msgstr "Password attuale" msgstr "Password attuale"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Data di creazione" msgstr "Data di creazione"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "ブラウザ拡張機能"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "コントロール"
msgid "Current password" msgid "Current password"
msgstr "現在のパスワード" msgstr "現在のパスワード"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "作成日" msgstr "作成日"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "브라우저 확장"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "컨트롤"
msgid "Current password" msgid "Current password"
msgstr "현재 비밀번호" msgstr "현재 비밀번호"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "생성 날짜" msgstr "생성 날짜"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Peluasan penyemak imbas"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Kata laluan semasa" msgstr "Kata laluan semasa"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Tarikh dibuat" msgstr "Tarikh dibuat"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Nettleserutvidelser"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Gjeldende passord" msgstr "Gjeldende passord"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Dato opprettet" msgstr "Dato opprettet"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Browserextensies"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Huidig wachtwoord" msgstr "Huidig wachtwoord"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Datum gemaakt" msgstr "Datum gemaakt"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Nettleserutvidelser"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Gjeldende passord" msgstr "Gjeldende passord"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Dato opprettet" msgstr "Dato opprettet"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Rozszerzenia przeglądarki"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "aktualne hasło" msgstr "aktualne hasło"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Data utworzenia" msgstr "Data utworzenia"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Extensões do navegador"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Senha atual" msgstr "Senha atual"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Data de criação" msgstr "Data de criação"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Расширения браузера"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Текущий пароль" msgstr "Текущий пароль"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Дата создания" msgstr "Дата создания"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Rozšírenia prehliadača"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Aktuálne heslo" msgstr "Aktuálne heslo"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Dátum vytvorenia" msgstr "Dátum vytvorenia"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Webbläsartillägg"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Aktuellt lösenord" msgstr "Aktuellt lösenord"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Datum skapat" msgstr "Datum skapat"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "Tarayıcı uzantıları"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr ""
msgid "Current password" msgid "Current password"
msgstr "Geçerli şifre" msgstr "Geçerli şifre"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "Oluşturulma tarihi" msgstr "Oluşturulma tarihi"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -123,6 +123,7 @@ msgstr "浏览器扩展"
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
#: src/components/header/MarkAllAsReadButton.tsx #: src/components/header/MarkAllAsReadButton.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
@@ -194,6 +195,18 @@ msgstr "控制"
msgid "Current password" msgid "Current password"
msgstr "当前密码" msgstr "当前密码"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
@@ -622,6 +635,7 @@ msgid "Right click"
msgstr "" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx

View File

@@ -13,7 +13,7 @@ const useStyles = createStyles(theme => ({
fontWeight: "bold", fontWeight: "bold",
fontSize: 120, fontSize: 120,
lineHeight: 1, lineHeight: 1,
marginBottom: theme.spacing.xl * 1.5, marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
color: theme.colors[theme.primaryColor][3], color: theme.colors[theme.primaryColor][3],
}, },
@@ -27,7 +27,7 @@ const useStyles = createStyles(theme => ({
maxWidth: 540, maxWidth: 540,
margin: "auto", margin: "auto",
marginTop: theme.spacing.xl, marginTop: theme.spacing.xl,
marginBottom: theme.spacing.xl * 1.5, marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
}, },
})) }))

View File

@@ -1,5 +1,5 @@
import { Center, Title } from "@mantine/core" import { Center, Title } from "@mantine/core"
import { Logo } from "../components/Logo" import { Logo } from "components/Logo"
export function PageTitle() { export function PageTitle() {
return ( return (

View File

@@ -1,18 +1,18 @@
import { t } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core" import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core"
import { useMediaQuery } from "@mantine/hooks" import { useMediaQuery } from "@mantine/hooks"
import { client } from "app/client"
import { Constants } from "app/constants"
import { redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store"
import welcome_page_dark from "assets/welcome_page_dark.png" import welcome_page_dark from "assets/welcome_page_dark.png"
import welcome_page_light from "assets/welcome_page_light.png" import welcome_page_light from "assets/welcome_page_light.png"
import { ActionButton } from "components/ActionButtton"
import { ButtonToolbar } from "components/ButtonToolbar"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { SiGithub, TbKey, TbUserPlus } from "react-icons/all" import { SiGithub, TbKey, TbUserPlus } from "react-icons/all"
import { SiTwitter } from "react-icons/si" import { SiTwitter } from "react-icons/si"
import { TbClock, TbMoon, TbSun } from "react-icons/tb" import { TbClock, TbMoon, TbSun } from "react-icons/tb"
import { client } from "../app/client"
import { Constants } from "../app/constants"
import { redirectToLogin, redirectToRegistration, redirectToRootCategory } from "../app/slices/redirect"
import { useAppDispatch, useAppSelector } from "../app/store"
import { ActionButton } from "../components/ActionButtton"
import { ButtonToolbar } from "../components/ButtonToolbar"
import { PageTitle } from "./PageTitle" import { PageTitle } from "./PageTitle"
export function WelcomePage() { export function WelcomePage() {
@@ -38,7 +38,7 @@ export function WelcomePage() {
} }
function Header() { function Header() {
const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint}px)`) const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint})`)
if (mobile) { if (mobile) {
return ( return (
@@ -76,7 +76,7 @@ function Buttons() {
<ButtonToolbar> <ButtonToolbar>
{serverInfos?.demoAccountEnabled && ( {serverInfos?.demoAccountEnabled && (
<ActionButton <ActionButton
label={t`Try the demo!`} label={<Trans>Try the demo!</Trans>}
icon={<TbClock size={iconSize} />} icon={<TbClock size={iconSize} />}
variant="outline" variant="outline"
onClick={() => login.execute({ name: "demo", password: "demo" })} onClick={() => login.execute({ name: "demo", password: "demo" })}
@@ -84,7 +84,7 @@ function Buttons() {
/> />
)} )}
<ActionButton <ActionButton
label={t`Log in`} label={<Trans>Log in</Trans>}
icon={<TbKey size={iconSize} />} icon={<TbKey size={iconSize} />}
variant="outline" variant="outline"
onClick={() => dispatch(redirectToLogin())} onClick={() => dispatch(redirectToLogin())}
@@ -92,7 +92,7 @@ function Buttons() {
/> />
{serverInfos?.allowRegistrations && ( {serverInfos?.allowRegistrations && (
<ActionButton <ActionButton
label={t`Sign up`} label={<Trans>Sign up</Trans>}
icon={<TbUserPlus size={iconSize} />} icon={<TbUserPlus size={iconSize} />}
variant="filled" variant="filled"
onClick={() => dispatch(redirectToRegistration())} onClick={() => dispatch(redirectToRegistration())}

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { ActionIcon, Box, Code, Container, Group, Table, Text, Title, useMantineTheme } from "@mantine/core" import { ActionIcon, Box, Code, Container, Group, Table, Text, Title, useMantineTheme } from "@mantine/core"
import { closeAllModals, openConfirmModal, openModal } from "@mantine/modals" import { closeAllModals, openConfirmModal, openModal } from "@mantine/modals"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
@@ -7,6 +7,7 @@ import { UserEdit } from "components/admin/UserEdit"
import { Alert } from "components/Alert" import { Alert } from "components/Alert"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { ReactNode } from "react"
import { useAsync, useAsyncCallback } from "react-async-hook" import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb" import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb"
@@ -26,7 +27,7 @@ export function AdminUsersPage() {
}, },
}) })
const openUserEditModal = (title: string, user?: UserModel) => { const openUserEditModal = (title: ReactNode, user?: UserModel) => {
openModal({ openModal({
title, title,
children: ( children: (
@@ -45,7 +46,7 @@ export function AdminUsersPage() {
const openUserDeleteModal = (user: UserModel) => { const openUserDeleteModal = (user: UserModel) => {
const userName = user.name const userName = user.name
openConfirmModal({ openConfirmModal({
title: t`Delete user`, title: <Trans>Delete user</Trans>,
children: ( children: (
<Text size="sm"> <Text size="sm">
<Trans> <Trans>
@@ -53,7 +54,7 @@ export function AdminUsersPage() {
</Trans> </Trans>
</Text> </Text>
), ),
labels: { confirm: t`Confirm`, cancel: t`Cancel` }, labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
confirmProps: { color: "red" }, confirmProps: { color: "red" },
onConfirm: () => deleteUser.execute({ id: user.id }), onConfirm: () => deleteUser.execute({ id: user.id }),
}) })
@@ -65,7 +66,7 @@ export function AdminUsersPage() {
<Title order={3} mb="md"> <Title order={3} mb="md">
<Group> <Group>
<Trans>Manage users</Trans> <Trans>Manage users</Trans>
<ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(t`Add user`)}> <ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(<Trans>Add user</Trans>)}>
<TbPlus size={20} /> <TbPlus size={20} />
</ActionIcon> </ActionIcon>
</Group> </Group>
@@ -126,7 +127,7 @@ export function AdminUsersPage() {
</td> </td>
<td> <td>
<Group> <Group>
<ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(t`Edit user`, u)}> <ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}>
<TbPencil size={18} /> <TbPencil size={18} />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon

View File

@@ -15,7 +15,7 @@ const useStyles = createStyles(() => ({
}, },
})) }))
function Section(props: { title: string; icon: React.ReactNode; children: React.ReactNode }) { function Section(props: { title: React.ReactNode; icon: React.ReactNode; children: React.ReactNode }) {
const { classes } = useStyles() const { classes } = useStyles()
return ( return (
<Box my="xl"> <Box my="xl">
@@ -38,7 +38,7 @@ function NextUnreadBookmarklet() {
return ( return (
<Box> <Box>
<CategorySelect value={categoryId} onChange={c => c && setCategoryId(c)} withAll description={t`Category`} /> <CategorySelect value={categoryId} onChange={c => c && setCategoryId(c)} withAll description={<Trans>Category</Trans>} />
<NativeSelect <NativeSelect
data={[ data={[
{ value: "desc", label: t`Newest first` }, { value: "desc", label: t`Newest first` },
@@ -46,7 +46,7 @@ function NextUnreadBookmarklet() {
]} ]}
value={order} value={order}
onChange={e => setOrder(e.target.value)} onChange={e => setOrder(e.target.value)}
description={t`Order`} description={<Trans>Order</Trans>}
/> />
<Trans>Drag link to bookmark bar</Trans> <Trans>Drag link to bookmark bar</Trans>
<span> </span> <span> </span>
@@ -58,6 +58,7 @@ function NextUnreadBookmarklet() {
} }
const bitcoinAddress = <Code>{Constants.bitcoinWalletAddress}</Code> const bitcoinAddress = <Code>{Constants.bitcoinWalletAddress}</Code>
export function AboutPage() { export function AboutPage() {
const version = useAppSelector(state => state.server.serverInfos?.version) const version = useAppSelector(state => state.server.serverInfos?.version)
const revision = useAppSelector(state => state.server.serverInfos?.gitCommit) const revision = useAppSelector(state => state.server.serverInfos?.gitCommit)
@@ -65,7 +66,7 @@ export function AboutPage() {
return ( return (
<Container size="xl"> <Container size="xl">
<SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}> <SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}>
<Section title={t`About`} icon={<TbHelp size={24} />}> <Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}>
<Box> <Box>
<Trans> <Trans>
CommaFeed version {version} ({revision}) CommaFeed version {version} ({revision})
@@ -119,7 +120,7 @@ export function AboutPage() {
<Trans>For those of you who prefer bitcoin, here is the address: {bitcoinAddress}</Trans> <Trans>For those of you who prefer bitcoin, here is the address: {bitcoinAddress}</Trans>
</Box> </Box>
</Section> </Section>
<Section title={t`Goodies`} icon={<TbPuzzle size={24} />}> <Section title={<Trans>Goodies</Trans>} icon={<TbPuzzle size={24} />}>
<List> <List>
<List.Item> <List.Item>
<Trans>Browser extentions</Trans> <Trans>Browser extentions</Trans>
@@ -161,10 +162,10 @@ export function AboutPage() {
</List.Item> </List.Item>
</List> </List>
</Section> </Section>
<Section title={t`Keyboard shortcuts`} icon={<TbKeyboard size={24} />}> <Section title={<Trans>Keyboard shortcuts</Trans>} icon={<TbKeyboard size={24} />}>
<KeyboardShortcutsHelp /> <KeyboardShortcutsHelp />
</Section> </Section>
<Section title={t`REST API`} icon={<TbRocket size={24} />}> <Section title={<Trans>REST API</Trans>} icon={<TbRocket size={24} />}>
<Anchor onClick={() => dispatch(redirectToApiDocumentation())}> <Anchor onClick={() => dispatch(redirectToApiDocumentation())}>
<Trans>Go to the API documentation.</Trans> <Trans>Go to the API documentation.</Trans>
</Anchor> </Anchor>

View File

@@ -10,13 +10,13 @@ export function AddPage() {
<Container size="sm" px={0}> <Container size="sm" px={0}>
<Tabs defaultValue="subscribe"> <Tabs defaultValue="subscribe">
<Tabs.List> <Tabs.List>
<Tabs.Tab value="subscribe" icon={<TbRss />}> <Tabs.Tab value="subscribe" icon={<TbRss size={16} />}>
<Trans>Subscribe</Trans> <Trans>Subscribe</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="category" icon={<TbFolderPlus />}> <Tabs.Tab value="category" icon={<TbFolderPlus size={16} />}>
<Trans>Add category</Trans> <Trans>Add category</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="opml" icon={<TbFileImport />}> <Tabs.Tab value="opml" icon={<TbFileImport size={16} />}>
<Trans>OPML</Trans> <Trans>OPML</Trans>
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core" import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { openConfirmModal } from "@mantine/modals" import { openConfirmModal } from "@mantine/modals"
@@ -48,7 +48,7 @@ export function CategoryDetailsPage() {
const openDeleteCategoryModal = () => { const openDeleteCategoryModal = () => {
const categoryName = category?.name const categoryName = category?.name
return openConfirmModal({ return openConfirmModal({
title: t`Delete Category`, title: <Trans>Delete Category</Trans>,
children: ( children: (
<Text size="sm"> <Text size="sm">
<Trans> <Trans>
@@ -56,7 +56,7 @@ export function CategoryDetailsPage() {
</Trans> </Trans>
</Text> </Text>
), ),
labels: { confirm: t`Confirm`, cancel: t`Cancel` }, labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
confirmProps: { color: "red" }, confirmProps: { color: "red" },
onConfirm: () => deleteCategory.execute({ id: +id }), onConfirm: () => deleteCategory.execute({ id: +id }),
}) })
@@ -91,7 +91,7 @@ export function CategoryDetailsPage() {
<form onSubmit={form.onSubmit(modifyCategory.execute)}> <form onSubmit={form.onSubmit(modifyCategory.execute)}>
<Stack> <Stack>
<Title order={3}>{category.name}</Title> <Title order={3}>{category.name}</Title>
<Input.Wrapper label={t`Generated feed url`}> <Input.Wrapper label={<Trans>Generated feed url</Trans>}>
<Box> <Box>
{apiKey && ( {apiKey && (
<Anchor <Anchor
@@ -108,14 +108,16 @@ export function CategoryDetailsPage() {
{editable && ( {editable && (
<> <>
<TextInput label={t`Name`} {...form.getInputProps("name")} required /> <Divider />
<TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
<CategorySelect <CategorySelect
label={t`Parent Category`} label={<Trans>Parent Category</Trans>}
{...form.getInputProps("parentId")} {...form.getInputProps("parentId")}
clearable clearable
withoutCategoryIds={[id]} withoutCategoryIds={[id]}
/> />
<NumberInput label={t`Position`} {...form.getInputProps("position")} required min={0} /> <NumberInput label={<Trans>Position</Trans>} {...form.getInputProps("position")} required min={0} />
</> </>
)} )}

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core" import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { openConfirmModal } from "@mantine/modals" import { openConfirmModal } from "@mantine/modals"
@@ -47,6 +47,7 @@ function FilteringExpressionDescription() {
</div> </div>
) )
} }
export function FeedDetailsPage() { export function FeedDetailsPage() {
const { id } = useParams() const { id } = useParams()
if (!id) throw Error("id required") if (!id) throw Error("id required")
@@ -75,7 +76,7 @@ export function FeedDetailsPage() {
const openUnsubscribeModal = () => { const openUnsubscribeModal = () => {
const feedName = feed?.name const feedName = feed?.name
return openConfirmModal({ return openConfirmModal({
title: t`Unsubscribe`, title: <Trans>Unsubscribe</Trans>,
children: ( children: (
<Text size="sm"> <Text size="sm">
<Trans> <Trans>
@@ -83,7 +84,7 @@ export function FeedDetailsPage() {
</Trans> </Trans>
</Text> </Text>
), ),
labels: { confirm: t`Confirm`, cancel: t`Cancel` }, labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
confirmProps: { color: "red" }, confirmProps: { color: "red" },
onConfirm: () => unsubscribe.execute({ id: +id }), onConfirm: () => unsubscribe.execute({ id: +id }),
}) })
@@ -112,34 +113,34 @@ export function FeedDetailsPage() {
<form onSubmit={form.onSubmit(modifyFeed.execute)}> <form onSubmit={form.onSubmit(modifyFeed.execute)}>
<Stack> <Stack>
<Title order={3}>{feed.name}</Title> <Title order={3}>{feed.name}</Title>
<Input.Wrapper label={t`Feed URL`}> <Input.Wrapper label={<Trans>Feed URL</Trans>}>
<Box> <Box>
<Anchor href={feed.feedUrl} target="_blank" rel="noreferrer"> <Anchor href={feed.feedUrl} target="_blank" rel="noreferrer">
{feed.feedUrl} {feed.feedUrl}
</Anchor> </Anchor>
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper label={t`Website`}> <Input.Wrapper label={<Trans>Website</Trans>}>
<Box> <Box>
<Anchor href={feed.feedLink} target="_blank" rel="noreferrer"> <Anchor href={feed.feedLink} target="_blank" rel="noreferrer">
{feed.feedLink} {feed.feedLink}
</Anchor> </Anchor>
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper label={t`Last refresh`}> <Input.Wrapper label={<Trans>Last refresh</Trans>}>
<Box> <Box>
<RelativeDate date={feed.lastRefresh} /> <RelativeDate date={feed.lastRefresh} />
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper label={t`Last refresh message`}> <Input.Wrapper label={<Trans>Last refresh message</Trans>}>
<Box>{feed.message ?? t`N/A`}</Box> <Box>{feed.message ?? <Trans>N/A</Trans>}</Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper label={t`Next refresh`}> <Input.Wrapper label={<Trans>Next refresh</Trans>}>
<Box> <Box>
<RelativeDate date={feed.nextRefresh} /> <RelativeDate date={feed.nextRefresh} />
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper label={t`Generated feed url`}> <Input.Wrapper label={<Trans>Generated feed url</Trans>}>
<Box> <Box>
{apiKey && ( {apiKey && (
<Anchor href={`rest/feed/entriesAsFeed?id=${feed.id}&apiKey=${apiKey}`} target="_blank" rel="noreferrer"> <Anchor href={`rest/feed/entriesAsFeed?id=${feed.id}&apiKey=${apiKey}`} target="_blank" rel="noreferrer">
@@ -150,11 +151,13 @@ export function FeedDetailsPage() {
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<TextInput label={t`Name`} {...form.getInputProps("name")} required /> <Divider />
<CategorySelect label={t`Category`} {...form.getInputProps("categoryId")} clearable />
<NumberInput label={t`Position`} {...form.getInputProps("position")} required min={0} /> <TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
<CategorySelect label={<Trans>Category</Trans>} {...form.getInputProps("categoryId")} clearable />
<NumberInput label={<Trans>Position</Trans>} {...form.getInputProps("position")} required min={0} />
<TextInput <TextInput
label={t`Filtering expression`} label={<Trans>Filtering expression</Trans>}
description={<FilteringExpressionDescription />} description={<FilteringExpressionDescription />}
{...form.getInputProps("filter")} {...form.getInputProps("filter")}
/> />

View File

@@ -1,5 +1,5 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { ActionIcon, Anchor, Box, Center, Divider, Group, Title, useMantineTheme } from "@mantine/core" import { ActionIcon, Box, Center, createStyles, Divider, Group, Title, useMantineTheme } from "@mantine/core"
import { useViewportSize } from "@mantine/hooks" import { useViewportSize } from "@mantine/hooks"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { EntrySourceType, loadEntries } from "app/slices/entries" import { EntrySourceType, loadEntries } from "app/slices/entries"
@@ -27,7 +27,15 @@ interface FeedEntriesPageProps {
sourceType: EntrySourceType sourceType: EntrySourceType
} }
const useStyles = createStyles(() => ({
sourceWebsiteLink: {
color: "inherit",
textDecoration: "none",
},
}))
export function FeedEntriesPage(props: FeedEntriesPageProps) { export function FeedEntriesPage(props: FeedEntriesPageProps) {
const { classes } = useStyles()
const location = useLocation() const location = useLocation()
const { id = Constants.categories.all.id } = useParams() const { id = Constants.categories.all.id } = useParams()
const viewport = useViewportSize() const viewport = useViewportSize()
@@ -63,9 +71,9 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
<Box mb={viewport.height - Constants.layout.headerHeight - 210}> <Box mb={viewport.height - Constants.layout.headerHeight - 210}>
<Group spacing="xl"> <Group spacing="xl">
{sourceWebsiteUrl && ( {sourceWebsiteUrl && (
<Anchor href={sourceWebsiteUrl} target="_blank" rel="noreferrer" variant="text"> <a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>
<Title order={3}>{sourceLabel}</Title> <Title order={3}>{sourceLabel}</Title>
</Anchor> </a>
)} )}
{!sourceWebsiteUrl && <Title order={3}>{sourceLabel}</Title>} {!sourceWebsiteUrl && <Title order={3}>{sourceLabel}</Title>}
{sourceLabel && ( {sourceLabel && (
@@ -77,7 +85,7 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
<FeedEntries /> <FeedEntries />
{!hasMore && <Divider my="xl" label={t`No more entries`} labelPosition="center" />} {!hasMore && <Divider my="xl" label={<Trans>No more entries</Trans>} labelPosition="center" />}
</Box> </Box>
) )
} }

View File

@@ -1,6 +1,5 @@
import { import {
ActionIcon, ActionIcon,
Anchor,
AppShell, AppShell,
Box, Box,
Burger, Burger,
@@ -37,13 +36,13 @@ interface LayoutProps {
} }
const sidebarPadding = DEFAULT_THEME.spacing.xs const sidebarPadding = DEFAULT_THEME.spacing.xs
const sidebarRightBorderWidth = 1 const sidebarRightBorderWidth = "1px"
const useStyles = createStyles(theme => ({ const useStyles = createStyles(theme => ({
sidebarContent: { sidebarContent: {
maxWidth: Constants.layout.sidebarWidth - sidebarPadding * 2 - sidebarRightBorderWidth, maxWidth: `calc(${Constants.layout.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
maxWidth: `calc(100vw - ${sidebarPadding * 2 + sidebarRightBorderWidth}px)`, maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
}, },
}, },
mainContentWrapper: { mainContentWrapper: {
@@ -68,14 +67,12 @@ const useStyles = createStyles(theme => ({
function LogoAndTitle() { function LogoAndTitle() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return ( return (
<Anchor onClick={() => dispatch(redirectToRootCategory())} variant="text"> <Center inline onClick={() => dispatch(redirectToRootCategory())} style={{ cursor: "pointer" }}>
<Center inline> <Logo size={24} />
<Logo size={24} /> <Title order={3} pl="md">
<Title order={3} pl="md"> CommaFeed
CommaFeed </Title>
</Title> </Center>
</Center>
</Anchor>
) )
} }
@@ -122,6 +119,7 @@ export default function Layout({ sidebar, header }: LayoutProps) {
classNames={{ main: classes.mainContentWrapper }} classNames={{ main: classes.mainContentWrapper }}
navbar={ navbar={
<Navbar <Navbar
id="sidebar"
p={sidebarPadding} p={sidebarPadding}
hiddenBreakpoint={Constants.layout.mobileBreakpoint} hiddenBreakpoint={Constants.layout.mobileBreakpoint}
hidden={!mobileMenuOpen} hidden={!mobileMenuOpen}
@@ -133,7 +131,7 @@ export default function Layout({ sidebar, header }: LayoutProps) {
</Navbar> </Navbar>
} }
header={ header={
<Header height={Constants.layout.headerHeight} p="md"> <Header id="header" height={Constants.layout.headerHeight} p="md">
<OnMobile> <OnMobile>
{mobileMenuOpen && ( {mobileMenuOpen && (
<Group position="apart"> <Group position="apart">
@@ -171,7 +169,7 @@ export default function Layout({ sidebar, header }: LayoutProps) {
if (ref) ref.id = Constants.dom.mainScrollAreaId if (ref) ref.id = Constants.dom.mainScrollAreaId
}} }}
> >
<Box className={classes.mainContent}> <Box id="content" className={classes.mainContent}>
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<Outlet /> <Outlet />
</Suspense> </Suspense>

View File

@@ -1,18 +1,22 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Container, Tabs } from "@mantine/core" import { Container, Tabs } from "@mantine/core"
import { CustomCodeSettings } from "components/settings/CustomCodeSettings"
import { DisplaySettings } from "components/settings/DisplaySettings" import { DisplaySettings } from "components/settings/DisplaySettings"
import { ProfileSettings } from "components/settings/ProfileSettings" import { ProfileSettings } from "components/settings/ProfileSettings"
import { TbPhoto, TbUser } from "react-icons/tb" import { TbCode, TbPhoto, TbUser } from "react-icons/tb"
export function SettingsPage() { export function SettingsPage() {
return ( return (
<Container size="sm" px={0}> <Container size="sm" px={0}>
<Tabs defaultValue="display"> <Tabs defaultValue="display">
<Tabs.List> <Tabs.List>
<Tabs.Tab value="display" icon={<TbPhoto />}> <Tabs.Tab value="display" icon={<TbPhoto size={16} />}>
<Trans>Display</Trans> <Trans>Display</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="profile" icon={<TbUser />}> <Tabs.Tab value="customCode" icon={<TbCode size={16} />}>
<Trans>Custom code</Trans>
</Tabs.Tab>
<Tabs.Tab value="profile" icon={<TbUser size={16} />}>
<Trans>Profile</Trans> <Trans>Profile</Trans>
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>
@@ -21,6 +25,10 @@ export function SettingsPage() {
<DisplaySettings /> <DisplaySettings />
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="customCode" pt="xl">
<CustomCodeSettings />
</Tabs.Panel>
<Tabs.Panel value="profile" pt="xl"> <Tabs.Panel value="profile" pt="xl">
<ProfileSettings /> <ProfileSettings />
</Tabs.Panel> </Tabs.Panel>

View File

@@ -1,4 +1,4 @@
import { t, Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Anchor, Box, Button, Container, Group, Input, Stack, Title } from "@mantine/core" import { Anchor, Box, Button, Container, Group, Input, Stack, Title } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
@@ -16,7 +16,7 @@ export function TagDetailsPage() {
<Container> <Container>
<Stack> <Stack>
<Title order={3}>{id}</Title> <Title order={3}>{id}</Title>
<Input.Wrapper label={t`Generated feed url`}> <Input.Wrapper label={<Trans>Generated feed url</Trans>}>
<Box> <Box>
{apiKey && ( {apiKey && (
<Anchor <Anchor

View File

@@ -42,15 +42,17 @@ export function LoginPage() {
<form onSubmit={form.onSubmit(login.execute)}> <form onSubmit={form.onSubmit(login.execute)}>
<Stack> <Stack>
<TextInput <TextInput
label={t`User Name or E-mail`} label={<Trans>User Name or E-mail</Trans>}
placeholder={t`User Name or E-mail`} placeholder={t`User Name or E-mail`}
{...form.getInputProps("name")} {...form.getInputProps("name")}
description={serverInfos?.demoAccountEnabled ? t`Try out CommaFeed with the demo account: demo/demo` : ""} description={
serverInfos?.demoAccountEnabled ? <Trans>Try out CommaFeed with the demo account: demo/demo</Trans> : ""
}
size="md" size="md"
required required
/> />
<PasswordInput <PasswordInput
label={t`Password`} label={<Trans>Password</Trans>}
placeholder={t`Password`} placeholder={t`Password`}
{...form.getInputProps("password")} {...form.getInputProps("password")}
size="md" size="md"

View File

@@ -53,7 +53,7 @@ export function PasswordRecoveryPage() {
<Stack> <Stack>
<TextInput <TextInput
type="email" type="email"
label={t`E-mail`} label={<Trans>E-mail</Trans>}
placeholder={t`E-mail`} placeholder={t`E-mail`}
{...form.getInputProps("email")} {...form.getInputProps("email")}
size="md" size="md"

View File

@@ -53,14 +53,14 @@ export function RegistrationPage() {
<TextInput label="User Name" placeholder="User Name" {...form.getInputProps("name")} size="md" required /> <TextInput label="User Name" placeholder="User Name" {...form.getInputProps("name")} size="md" required />
<TextInput <TextInput
type="email" type="email"
label={t`E-mail address`} label={<Trans>E-mail address</Trans>}
placeholder={t`E-mail address`} placeholder={t`E-mail address`}
{...form.getInputProps("email")} {...form.getInputProps("email")}
size="md" size="md"
required required
/> />
<PasswordInput <PasswordInput
label={t`Password`} label={<Trans>Password</Trans>}
placeholder={t`Password`} placeholder={t`Password`}
{...form.getInputProps("password")} {...form.getInputProps("password")}
size="md" size="md"

View File

@@ -24,6 +24,8 @@ export default defineConfig({
"/rest": "http://localhost:8083", "/rest": "http://localhost:8083",
"/ws": "ws://localhost:8083", "/ws": "ws://localhost:8083",
"/swagger": "http://localhost:8083", "/swagger": "http://localhost:8083",
"/custom_css.css": "http://localhost:8083",
"/custom_js.js": "http://localhost:8083",
}, },
}, },
build: { build: {

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.2.0</version> <version>3.3.2</version>
</parent> </parent>
<artifactId>commafeed-server</artifactId> <artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name> <name>CommaFeed Server</name>
@@ -39,13 +39,6 @@
</resources> </resources>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@@ -233,7 +226,7 @@
<dependency> <dependency>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<version>3.2.0</version> <version>3.3.2</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -43,6 +43,7 @@ import com.commafeed.frontend.resource.ServerREST;
import com.commafeed.frontend.resource.UserREST; import com.commafeed.frontend.resource.UserREST;
import com.commafeed.frontend.servlet.AnalyticsServlet; import com.commafeed.frontend.servlet.AnalyticsServlet;
import com.commafeed.frontend.servlet.CustomCssServlet; import com.commafeed.frontend.servlet.CustomCssServlet;
import com.commafeed.frontend.servlet.CustomJsServlet;
import com.commafeed.frontend.servlet.LogoutServlet; import com.commafeed.frontend.servlet.LogoutServlet;
import com.commafeed.frontend.servlet.NextUnreadServlet; import com.commafeed.frontend.servlet.NextUnreadServlet;
import com.commafeed.frontend.session.SessionHelperFactoryProvider; import com.commafeed.frontend.session.SessionHelperFactoryProvider;
@@ -173,6 +174,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next"); environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout"); environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout");
environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css"); environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css");
environment.servlets().addServlet("customJs", injector.getInstance(CustomJsServlet.class)).addMapping("/custom_js.js");
environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js"); environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js");
// WebSocket endpoint // WebSocket endpoint

View File

@@ -3,7 +3,6 @@ package com.commafeed.backend.cache;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisClientConfig;
@@ -11,7 +10,6 @@ import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol;
@Slf4j
@Getter @Getter
public class RedisPoolFactory { public class RedisPoolFactory {

View File

@@ -46,24 +46,22 @@ public class UserSettings extends AbstractModel {
@Column(nullable = false) @Column(nullable = false)
private ReadingOrder readingOrder; private ReadingOrder readingOrder;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ViewMode viewMode;
@Column(name = "user_lang", length = 4) @Column(name = "user_lang", length = 4)
private String language; private String language;
private boolean showRead; private boolean showRead;
private boolean scrollMarks; private boolean scrollMarks;
@Column(length = 32)
private String theme;
@Lob @Lob
@Column(length = Integer.MAX_VALUE) @Column(length = Integer.MAX_VALUE)
@Type(type = "org.hibernate.type.TextType") @Type(type = "org.hibernate.type.TextType")
private String customCss; private String customCss;
@Lob
@Column(length = Integer.MAX_VALUE)
@Type(type = "org.hibernate.type.TextType")
private String customJs;
@Column(name = "scroll_speed") @Column(name = "scroll_speed")
private int scrollSpeed; private int scrollSpeed;

View File

@@ -20,21 +20,18 @@ public class Settings implements Serializable {
@ApiModelProperty(value = "user reads entries in ascending or descending order", allowableValues = "asc,desc", required = true) @ApiModelProperty(value = "user reads entries in ascending or descending order", allowableValues = "asc,desc", required = true)
private String readingOrder; private String readingOrder;
@ApiModelProperty(value = "user viewing mode, either title-only or expande view", allowableValues = "title,expanded", required = true)
private String viewMode;
@ApiModelProperty(value = "user wants category and feeds with no unread entries shown", required = true) @ApiModelProperty(value = "user wants category and feeds with no unread entries shown", required = true)
private boolean showRead; private boolean showRead;
@ApiModelProperty(value = "In expanded view, scroll through entries mark them as read", required = true) @ApiModelProperty(value = "In expanded view, scroll through entries mark them as read", required = true)
private boolean scrollMarks; private boolean scrollMarks;
@ApiModelProperty(value = "user's selected theme")
private String theme;
@ApiModelProperty(value = "user's custom css for the website") @ApiModelProperty(value = "user's custom css for the website")
private String customCss; private String customCss;
@ApiModelProperty(value = "user's custom js for the website")
private String customJs;
@ApiModelProperty(value = "user's preferred scroll speed when navigating between entries", required = true) @ApiModelProperty(value = "user's preferred scroll speed when navigating between entries", required = true)
private int scrollSpeed; private int scrollSpeed;

View File

@@ -39,7 +39,6 @@ import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.model.UserSettings.ReadingMode; import com.commafeed.backend.model.UserSettings.ReadingMode;
import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.model.UserSettings.ViewMode;
import com.commafeed.backend.service.MailService; import com.commafeed.backend.service.MailService;
import com.commafeed.backend.service.PasswordEncryptionService; import com.commafeed.backend.service.PasswordEncryptionService;
import com.commafeed.backend.service.UserService; import com.commafeed.backend.service.UserService;
@@ -88,7 +87,6 @@ public class UserREST {
if (settings != null) { if (settings != null) {
s.setReadingMode(settings.getReadingMode().name()); s.setReadingMode(settings.getReadingMode().name());
s.setReadingOrder(settings.getReadingOrder().name()); s.setReadingOrder(settings.getReadingOrder().name());
s.setViewMode(settings.getViewMode().name());
s.setShowRead(settings.isShowRead()); s.setShowRead(settings.isShowRead());
s.getSharingSettings().setEmail(settings.isEmail()); s.getSharingSettings().setEmail(settings.isEmail());
@@ -101,16 +99,14 @@ public class UserREST {
s.getSharingSettings().setBuffer(settings.isBuffer()); s.getSharingSettings().setBuffer(settings.isBuffer());
s.setScrollMarks(settings.isScrollMarks()); s.setScrollMarks(settings.isScrollMarks());
s.setTheme(settings.getTheme());
s.setCustomCss(settings.getCustomCss()); s.setCustomCss(settings.getCustomCss());
s.setCustomJs(settings.getCustomJs());
s.setLanguage(settings.getLanguage()); s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed()); s.setScrollSpeed(settings.getScrollSpeed());
} else { } else {
s.setReadingMode(ReadingMode.unread.name()); s.setReadingMode(ReadingMode.unread.name());
s.setReadingOrder(ReadingOrder.desc.name()); s.setReadingOrder(ReadingOrder.desc.name());
s.setViewMode(ViewMode.title.name());
s.setShowRead(true); s.setShowRead(true);
s.setTheme("default");
s.getSharingSettings().setEmail(true); s.getSharingSettings().setEmail(true);
s.getSharingSettings().setGmail(true); s.getSharingSettings().setGmail(true);
@@ -144,10 +140,9 @@ public class UserREST {
s.setReadingMode(ReadingMode.valueOf(settings.getReadingMode())); s.setReadingMode(ReadingMode.valueOf(settings.getReadingMode()));
s.setReadingOrder(ReadingOrder.valueOf(settings.getReadingOrder())); s.setReadingOrder(ReadingOrder.valueOf(settings.getReadingOrder()));
s.setShowRead(settings.isShowRead()); s.setShowRead(settings.isShowRead());
s.setViewMode(ViewMode.valueOf(settings.getViewMode()));
s.setScrollMarks(settings.isScrollMarks()); s.setScrollMarks(settings.isScrollMarks());
s.setTheme(settings.getTheme());
s.setCustomCss(settings.getCustomCss()); s.setCustomCss(settings.getCustomCss());
s.setCustomJs(settings.getCustomJs());
s.setLanguage(settings.getLanguage()); s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed()); s.setScrollSpeed(settings.getScrollSpeed());

View File

@@ -0,0 +1,53 @@
package com.commafeed.frontend.servlet;
import java.io.IOException;
import java.util.Optional;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.SessionFactory;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings;
import com.commafeed.frontend.session.SessionHelper;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
abstract class AbstractCustomCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final SessionFactory sessionFactory;
private final UserSettingsDAO userSettingsDAO;
@Override
protected final void doGet(final HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType(getMimeType());
final Optional<User> user = new SessionHelper(req).getLoggedInUser();
if (!user.isPresent()) {
return;
}
UserSettings settings = UnitOfWork.call(sessionFactory, () -> userSettingsDAO.findByUser(user.get()));
if (settings == null) {
return;
}
String customCode = getCustomCode(settings);
if (customCode == null) {
return;
}
resp.getWriter().write(customCode);
}
protected abstract String getMimeType();
protected abstract String getCustomCode(UserSettings settings);
}

View File

@@ -1,47 +1,29 @@
package com.commafeed.frontend.servlet; package com.commafeed.frontend.servlet;
import java.io.IOException;
import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserSettingsDAO; import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.model.UserSettings;
import com.commafeed.frontend.session.SessionHelper;
import lombok.RequiredArgsConstructor; public class CustomCssServlet extends AbstractCustomCodeServlet {
@SuppressWarnings("serial") private static final long serialVersionUID = 1L;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class CustomCssServlet extends HttpServlet {
private final SessionFactory sessionFactory; @Inject
private final UserSettingsDAO userSettingsDAO; public CustomCssServlet(SessionFactory sessionFactory, UserSettingsDAO userSettingsDAO) {
super(sessionFactory, userSettingsDAO);
}
@Override @Override
protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected String getMimeType() {
resp.setContentType("text/css"); return "text/css";
final Optional<User> user = new SessionHelper(req).getLoggedInUser();
if (!user.isPresent()) {
return;
}
UserSettings settings = UnitOfWork.call(sessionFactory, () -> userSettingsDAO.findByUser(user.get()));
if (settings == null || settings.getCustomCss() == null) {
return;
}
resp.getWriter().write(settings.getCustomCss());
} }
@Override
protected String getCustomCode(UserSettings settings) {
return settings.getCustomCss();
}
} }

View File

@@ -0,0 +1,31 @@
package com.commafeed.frontend.servlet;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.UserSettings;
@Singleton
public class CustomJsServlet extends AbstractCustomCodeServlet {
private static final long serialVersionUID = 1L;
@Inject
public CustomJsServlet(SessionFactory sessionFactory, UserSettingsDAO userSettingsDAO) {
super(sessionFactory, userSettingsDAO);
}
@Override
protected String getMimeType() {
return "application/javascript";
}
@Override
protected String getCustomCode(UserSettings settings) {
return settings.getCustomJs();
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet id="delete-removed-settings" author="athou">
<dropColumn tableName="USERSETTINGS" columnName="theme" />
<dropColumn tableName="USERSETTINGS" columnName="viewMode" />
</changeSet>
<changeSet id="add-customjs-column" author="athou">
<addColumn tableName="USERSETTINGS">
<column name="customJs" type="CLOB" />
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -1,21 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<property name="blob_type" value="bytea" dbms="postgresql"/> <property name="blob_type" value="bytea" dbms="postgresql"/>
<property name="blob_type" value="blob" dbms="h2" /> <property name="blob_type" value="blob" dbms="h2"/>
<property name="blob_type" value="blob" dbms="mysql,mariadb" /> <property name="blob_type" value="blob" dbms="mysql,mariadb"/>
<property name="blob_type" value="blob" dbms="mssql" /> <property name="blob_type" value="blob" dbms="mssql"/>
<include file="changelogs/db.changelog-1.0.xml"/>
<include file="changelogs/db.changelog-1.1.xml"/>
<include file="changelogs/db.changelog-1.2.xml"/>
<include file="changelogs/db.changelog-1.3.xml"/>
<include file="changelogs/db.changelog-1.4.xml"/>
<include file="changelogs/db.changelog-1.5.xml"/>
<include file="changelogs/db.changelog-2.1.xml"/>
<include file="changelogs/db.changelog-2.2.xml"/>
<include file="changelogs/db.changelog-2.6.xml"/>
<include file="changelogs/db.changelog-3.2.xml"/>
<include file="changelogs/db.changelog-1.0.xml" />
<include file="changelogs/db.changelog-1.1.xml" />
<include file="changelogs/db.changelog-1.2.xml" />
<include file="changelogs/db.changelog-1.3.xml" />
<include file="changelogs/db.changelog-1.4.xml" />
<include file="changelogs/db.changelog-1.5.xml" />
<include file="changelogs/db.changelog-2.1.xml" />
<include file="changelogs/db.changelog-2.2.xml" />
<include file="changelogs/db.changelog-2.6.xml" />
</databaseChangeLog> </databaseChangeLog>

21
pom.xml
View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.2.0</version> <version>3.3.2</version>
<name>CommaFeed</name> <name>CommaFeed</name>
<packaging>pom</packaging> <packaging>pom</packaging>
@@ -15,26 +15,15 @@
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
</properties> </properties>
<profiles>
<profile>
<id>only-eclipse</id>
<activation>
<property>
<name>m2e.version</name>
</property>
</activation>
<build>
<directory>target-ide</directory>
</build>
</profile>
</profiles>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version> <version>3.10.1</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>