Compare commits

...

13 Commits
3.2.0 ... 3.3.0

Author SHA1 Message Date
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
85 changed files with 3615 additions and 3304 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## [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

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

@@ -17,67 +17,68 @@
"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", "lodash": "^4.17.21",
"make-plural": "^7.2.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-icons": "^4.7.1", "react-icons": "^4.8.0",
"react-infinite-scroller": "^1.2.6", "react-infinite-scroller": "^1.2.6",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.8.0", "react-router-dom": "^6.11.1",
"react-swipeable": "^7.0.0", "react-swipeable": "^7.0.0",
"swagger-ui-react": "^4.15.5", "swagger-ui-react": "^4.18.3",
"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.1" "websocket-heartbeat-js": "^1.1.2"
}, },
"devDependencies": { "devDependencies": {
"@lingui/cli": "^3.17.0", "@lingui/cli": "^4.0.0",
"@types/eslint": "^8.21.0", "@lingui/vite-plugin": "^4.0.0",
"@types/lodash": "^4.14.191", "@types/eslint": "^8.37.0",
"@types/lodash": "^4.14.194",
"@types/mousetrap": "^1.6.11", "@types/mousetrap": "^1.6.11",
"@types/react": "^18.0.27", "@types/react": "^18.2.6",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.2.4",
"@types/react-infinite-scroller": "^1.2.3", "@types/react-infinite-scroller": "^1.2.3",
"@types/swagger-ui-react": "^4.11.0", "@types/swagger-ui-react": "^4.18.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": "^8.40.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.8.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-hooks": "^0.4.3", "eslint-plugin-hooks": "^0.4.3",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"prettier": "^2.8.3", "prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^5.9.0",
"typescript": "^4.9.5", "typescript": "^5.0.4",
"vite": "^4.1.1", "vite": "^4.3.5",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.0.5", "vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.28.4", "vitest": "^0.31.0",
"vitest-mock-extended": "^1.0.9" "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.0</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>

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

@@ -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 { useViewMode } from "hooks/useViewMode"
import throttle from "lodash/throttle" import throttle from "lodash/throttle"
import { useEffect } from "react" import { useEffect } from "react"
import InfiniteScroll from "react-infinite-scroller" import InfiniteScroll from "react-infinite-scroller"
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)
@@ -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,6 +53,10 @@ const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: View
}, },
}, },
}, },
headerLink: {
color: "inherit",
textDecoration: "none",
},
body: { body: {
maxWidth: Constants.layout.entryMaxWidth, maxWidth: Constants.layout.entryMaxWidth,
}, },
@@ -91,8 +95,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 +108,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,4 +1,4 @@
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"
@@ -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))}>

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"
@@ -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)
@@ -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,14 @@ export function CategoryDetailsPage() {
{editable && ( {editable && (
<> <>
<TextInput label={t`Name`} {...form.getInputProps("name")} required /> <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,11 @@ export function FeedDetailsPage() {
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<TextInput label={t`Name`} {...form.getInputProps("name")} required /> <TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
<CategorySelect label={t`Category`} {...form.getInputProps("categoryId")} clearable /> <CategorySelect label={<Trans>Category</Trans>} {...form.getInputProps("categoryId")} clearable />
<NumberInput label={t`Position`} {...form.getInputProps("position")} required min={0} /> <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} - ${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,12 +6,15 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
</parent> </parent>
<artifactId>commafeed-server</artifactId> <artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name> <name>CommaFeed Server</name>
<properties> <properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<guice.version>5.1.0</guice.version> <guice.version>5.1.0</guice.version>
<querydsl.version>4.4.0</querydsl.version> <querydsl.version>4.4.0</querydsl.version>
<rome.version>2.1.0</rome.version> <rome.version>2.1.0</rome.version>
@@ -42,6 +45,7 @@
<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>
<configuration> <configuration>
<parameters>true</parameters> <parameters>true</parameters>
</configuration> </configuration>
@@ -233,7 +237,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.0</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

@@ -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,51 @@
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 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,27 @@
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") @Inject
@RequiredArgsConstructor(onConstructor = @__({ @Inject })) public CustomCssServlet(SessionFactory sessionFactory, UserSettingsDAO userSettingsDAO) {
@Singleton super(sessionFactory, userSettingsDAO);
public class CustomCssServlet extends HttpServlet { }
private final SessionFactory sessionFactory;
private final UserSettingsDAO 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,29 @@
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 {
@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>

28
pom.xml
View File

@@ -5,40 +5,14 @@
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<name>CommaFeed</name> <name>CommaFeed</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<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>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
</plugins>
</build>
<modules> <modules>
<module>commafeed-client</module> <module>commafeed-client</module>
<module>commafeed-server</module> <module>commafeed-server</module>