Compare commits

...

35 Commits
3.4.0 ... 3.6.0

Author SHA1 Message Date
Athou
e2eeba90ef release 3.6.0 2023-06-08 08:46:20 +02:00
Athou
662c0fc6b9 add buttons that communicate with the browser extension (Athou/commafeed-browser-extension#1) 2023-06-07 15:28:16 +02:00
Athou
fafc0619ad add tooltip to action buttons when label is hidden because viewport width is below mobile breakpoint (Athou/commafeed-browser-extension#1) 2023-06-07 11:32:54 +02:00
Athou
3a5dc5d0ed open link on click in expanded mode since we don't need to collapse entry (#1073) 2023-06-07 08:58:50 +02:00
Jérémie Panzer
23a696e644 Merge pull request #1071 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-4.3.9
Bump vite from 4.3.8 to 4.3.9 in /commafeed-client
2023-06-06 10:12:41 +02:00
dependabot[bot]
72cb71a2fb Bump vite from 4.3.8 to 4.3.9 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.3.8 to 4.3.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.3.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-06 08:06:36 +00:00
Athou
9b757735b8 a 403 may be returned if the user has been deleted but the session still exists, redirect to welcome page 2023-06-05 18:46:32 +02:00
Athou
6b9f8f268f make /next work in development 2023-06-05 10:17:00 +02:00
Athou
e5b0eb426c change default config to store h2 database in the same directory as the jar 2023-06-04 07:45:01 +02:00
Athou
ea6c83ca33 add link to api documentation on welcome page 2023-06-01 16:12:39 +02:00
Athou
763ce1e4fd correctly invalidate unread count cache when using the next unread servlet 2023-06-01 13:11:19 +02:00
Athou
e748499ed8 Merge branch 'canoine-patch-2' 2023-06-01 08:06:05 +02:00
Athou
e430604528 remove nbsp from label 2023-06-01 08:05:52 +02:00
Athou
5e08c81d12 Merge branch 'patch-2' of https://github.com/canoine/commafeed into canoine-patch-2 2023-06-01 07:58:12 +02:00
Athou
84626e1ef2 release 3.5.0 2023-06-01 07:49:42 +02:00
Athou
191ece0bac update browser extensions link 2023-06-01 07:43:17 +02:00
canoine
24eaff61f2 Update fr/messages.po
Adds, updates and fixes.
2023-06-01 06:25:39 +02:00
Athou
aa5e9bfd83 update readme to point to the new browser extension 2023-05-31 17:55:47 +02:00
Athou
a200147926 remove X-Frame-Options: DENY as it blocks the iframe from the future browser extension 2023-05-31 15:24:17 +02:00
Athou
d6205b7da3 fix typo 2023-05-31 07:36:50 +02:00
Athou
5ecf3e0fbf add setting to disable strict password policy (#1059) 2023-05-31 07:31:40 +02:00
Athou
bb25e0ede6 intellij autofixes 2023-05-31 07:27:24 +02:00
Athou
f5c0e2d375 update documentation: alphabetical ordering is no longer available 2023-05-30 21:22:02 +02:00
Athou
12ab5b1e7b add default value to allow app startup even if the setting is missing in config.yml 2023-05-30 10:53:28 +02:00
Athou
3e6451289f add setting to limit feeds per user 2023-05-30 09:10:20 +02:00
Athou
09d21d88a4 remove usage of deprecated id generator that blocks migration to hibernate 6 2023-05-29 20:36:45 +02:00
Athou
2ec6d0a66a api documentation page no longer requires users to be authenticated 2023-05-28 22:38:57 +02:00
Athou
412fc52f1c add css classes to help with custom css rules (#1061) 2023-05-28 12:57:28 +02:00
Athou
b5e5989604 disable pull-to-refresh on mobile as it messes with vertical scrolling 2023-05-27 19:54:02 +02:00
Athou
105ff46c01 UnitOfWork is now injectable 2023-05-27 19:46:49 +02:00
Athou
f100f3f91a run post login activities in a new transaction to avoid database locks 2023-05-27 19:29:44 +02:00
Athou
45eb436b8f reduce chance of deadlocks 2023-05-27 08:38:23 +02:00
Athou
bf3914e748 remove very slow query 2023-05-27 08:03:36 +02:00
Athou
5df7aaf7cd try to fix redis timeouts (#1060) 2023-05-27 07:58:22 +02:00
Athou
f10cfd7ad0 add feed refresh engine metrics 2023-05-26 15:20:17 +02:00
101 changed files with 885 additions and 418 deletions

View File

@@ -1,5 +1,25 @@
# Changelog
## [3.6.0]
- add a button to open CommaFeed in a new tab and a button to open options when using the browser extension
- clicking on the entry title in expanded mode now opens the link instead of doing nothing
- add tooltips to buttons when the mobile layout is used on desktop
- redirect the user to the welcome page if the user was deleted from the database
- add link to api documentation on welcome page
- the unread count is now correctly updated when using the "/next" bookmarklet while redis cache is enabled
## [3.5.0]
- add compatibility with the new version of the CommaFeed browser extension
- disable pull-to-refresh on mobile as it messes with vertical scrolling
- add css classes to feed entries to help with custom css rules
- api documentation page no longer requires users to be authenticated
- add a setting to limit the number of feeds a user can subscribe to
- add a setting to disable strict password policy
- add feed refresh engine metrics
- fix redis timeouts
## [3.4.0]
- add support for arm64 docker images

View File

@@ -5,6 +5,7 @@ EXPOSE 8082
RUN mkdir -p /commafeed/data
VOLUME /commafeed/data
ENV CF_SESSION_PATH=/commafeed/data/sessions
ENV CF_DATABASE_URL=jdbc:h2:/commafeed/data/db
COPY commafeed-server/config.yml.example config.yml
COPY commafeed-server/target/commafeed.jar .

View File

@@ -15,15 +15,7 @@ Google Reader inspired self-hosted RSS reader, based on Dropwizard and React/Typ
- Supports thousands of users and millions of feeds
- OPML import/export
- REST API
## Related open-source projects
Browser extensions:
- [Chrome](https://github.com/Athou/commafeed-chrome)
- [Firefox](https://github.com/Athou/commafeed-firefox)
- [Opera](https://github.com/Athou/commafeed-opera)
- [Safari](https://github.com/Athou/commafeed-safari)
- [Browser extension](https://github.com/Athou/commafeed-browser-extension)
## Deployment on your own server

View File

@@ -66,7 +66,7 @@
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.0",
"typescript": "^5.0.4",
"vite": "^4.3.8",
"vite": "^4.3.9",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.31.1",
@@ -11829,9 +11829,9 @@
"devOptional": true
},
"node_modules/vite": {
"version": "4.3.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.8.tgz",
"integrity": "sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==",
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
"dev": true,
"dependencies": {
"esbuild": "^0.17.5",

View File

@@ -72,7 +72,7 @@
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.0",
"typescript": "^5.0.4",
"vite": "^4.3.8",
"vite": "^4.3.9",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.31.1",

View File

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

View File

@@ -72,6 +72,7 @@ function AppRoutes() {
<Route path="login" element={<LoginPage />} />
<Route path="register" element={<RegistrationPage />} />
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
<Route path="api" element={<ApiDocumentationPage />} />
<Route path="app" element={<Layout header={<Header />} sidebar={<Tree />} />}>
<Route path="category">
<Route path=":id" element={<FeedEntriesPage sourceType="category" />} />
@@ -93,7 +94,6 @@ function AppRoutes() {
</Route>
<Route path="about" element={<AboutPage />} />
<Route path="donate" element={<DonatePage />} />
<Route path="api" element={<ApiDocumentationPage />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>

View File

@@ -30,7 +30,10 @@ const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
axiosInstance.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401 && error.response.data === "Credentials are required to access this resource.") {
if (
(error.response.status === 401 && error.response.data === "Credentials are required to access this resource.") ||
(error.response.status === 403 && error.response.data === "You don't have the required role to access this resource.")
) {
window.location.hash = "/welcome"
}
throw error

View File

@@ -13,6 +13,8 @@ export const redirectToRegistration = createAsyncThunk("redirect/register", (_,
export const redirectToPasswordRecovery = createAsyncThunk("redirect/passwordRecovery", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/passwordRecovery"))
)
export const redirectToApiDocumentation = createAsyncThunk("redirect/api", (_, thunkApi) => thunkApi.dispatch(redirectTo("/api")))
export const redirectToSelectedSource = createAsyncThunk<
void,
void,
@@ -52,7 +54,6 @@ export const redirectToMetrics = createAsyncThunk("redirect/admin/metrics", (_,
)
export const redirectToDonate = createAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate")))
export const redirectToAbout = createAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about")))
export const redirectToApiDocumentation = createAsyncThunk("redirect/api", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/api")))
export const redirectSlice = createSlice({
name: "redirect",

View File

@@ -1,4 +1,4 @@
import { ActionIcon, Button, useMantineTheme } from "@mantine/core"
import { ActionIcon, Button, Tooltip, useMantineTheme } from "@mantine/core"
import { ActionIconProps } from "@mantine/core/lib/ActionIcon/ActionIcon"
import { ButtonProps } from "@mantine/core/lib/Button/Button"
import { useMediaQuery } from "@mantine/hooks"
@@ -7,9 +7,10 @@ import { forwardRef, MouseEventHandler, ReactNode } from "react"
interface ActionButtonProps {
className?: string
icon?: ReactNode
label?: ReactNode
label: ReactNode
onClick?: MouseEventHandler
variant?: ActionIconProps["variant"] & ButtonProps["variant"]
hideLabelOnDesktop?: boolean
showLabelOnMobile?: boolean
}
@@ -20,11 +21,13 @@ export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((pr
const theme = useMantineTheme()
const variant = props.variant ?? "subtle"
const mobile = !useMediaQuery(`(min-width: ${theme.breakpoints.lg})`)
const iconOnly = !props.showLabelOnMobile && (mobile || !props.label)
const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop)
return iconOnly ? (
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}>
{props.icon}
</ActionIcon>
<Tooltip label={props.label} openDelay={500}>
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}>
{props.icon}
</ActionIcon>
</Tooltip>
) : (
<Button ref={ref} variant={variant} size="xs" className={props.className} leftIcon={props.icon} onClick={props.onClick}>
{props.label}

View File

@@ -37,8 +37,8 @@ export function FeedEntries() {
const selectedEntry = entries.find(e => e.id === selectedEntryId)
const headerClicked = (entry: ExpendableEntry, event: React.MouseEvent) => {
if (event.button === 1 || event.ctrlKey || event.metaKey) {
// middle click
const middleClick = event.button === 1 || event.ctrlKey || event.metaKey
if (middleClick || viewMode === "expanded") {
dispatch(markEntry({ entry, read: true }))
} else if (event.button === 0) {
// main click
@@ -263,6 +263,7 @@ export function FeedEntries() {
<FeedEntry
entry={entry}
expanded={!!entry.expanded || viewMode === "expanded"}
selected={entry.id === selectedEntryId}
showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")}
onHeaderClick={event => headerClicked(entry, event)}
/>

View File

@@ -16,6 +16,7 @@ import { FeedEntryHeader } from "./FeedEntryHeader"
interface FeedEntryProps {
entry: Entry
expanded: boolean
selected: boolean
showSelectionIndicator: boolean
onHeaderClick: (e: React.MouseEvent) => void
}
@@ -72,7 +73,7 @@ const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: View
export function FeedEntry(props: FeedEntryProps) {
const { viewMode } = useViewMode()
const { classes } = useStyles({ ...props, viewMode })
const { classes, cx } = useStyles({ ...props, viewMode })
const dispatch = useAppDispatch()
@@ -95,7 +96,17 @@ export function FeedEntry(props: FeedEntryProps) {
const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy")
return (
<Paper withBorder radius={borderRadius} className={classes.paper}>
<Paper
withBorder
radius={borderRadius}
className={cx(classes.paper, {
read: props.entry.read,
unread: !props.entry.read,
expanded: props.expanded,
selected: props.selected,
"show-selection-indicator": props.showSelectionIndicator,
})}
>
<a
className={classes.headerLink}
href={props.entry.url}

View File

@@ -7,8 +7,9 @@ import { useAppDispatch, useAppSelector } from "app/store"
import { ActionButton } from "components/ActionButtton"
import { ButtonToolbar } from "components/ButtonToolbar"
import { Loader } from "components/Loader"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useEffect } from "react"
import { TbArrowDown, TbArrowUp, TbEye, TbEyeOff, TbRefresh, TbSearch, TbUser, TbX } from "react-icons/tb"
import { TbArrowDown, TbArrowUp, TbExternalLink, TbEye, TbEyeOff, TbRefresh, TbSearch, TbSettings, TbUser, TbX } from "react-icons/tb"
import { MarkAllAsReadButton } from "./MarkAllAsReadButton"
import { ProfileMenu } from "./ProfileMenu"
@@ -22,6 +23,7 @@ export function Header() {
const settings = useAppSelector(state => state.user.settings)
const profile = useAppSelector(state => state.user.profile)
const searchFromStore = useAppSelector(state => state.entries.search)
const { isBrowserExtension, openSettingsPage, openAppInNewTab } = useBrowserExtension()
const dispatch = useAppDispatch()
const searchForm = useForm<{ search: string }>({
@@ -87,6 +89,23 @@ export function Header() {
<HeaderDivider />
<ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} />
{isBrowserExtension && (
<>
<HeaderDivider />
<ActionButton
icon={<TbSettings size={iconSize} />}
label={<Trans>Extension options</Trans>}
onClick={() => openSettingsPage()}
/>
<ActionButton
icon={<TbExternalLink size={iconSize} />}
label={<Trans>Open CommaFeed</Trans>}
onClick={() => openAppInNewTab()}
/>
</>
)}
</ButtonToolbar>
</Center>
)

View File

@@ -0,0 +1,9 @@
export const useBrowserExtension = () => {
// when not in an iframe, window.parent is a reference to window
const isBrowserExtension = window.parent !== window
const openSettingsPage = () => window.parent.postMessage("open-settings-page", "*")
const openAppInNewTab = () => window.parent.postMessage("open-app-in-new-tab", "*")
return { isBrowserExtension, openSettingsPage, openAppInNewTab }
}

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "سيؤدي تغيير كلمة المرور إلى إنشاء مفتاح
msgid "Check that the feed is working"
msgstr "تأكد من عمل الخلاصة"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed هو مشروع مفتوح المصدر. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed التالي العنصر غير المقروء"
@@ -308,6 +308,11 @@ msgstr "موسع"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "قم بتصدير اشتراكاتك وفئاتك كملف OPML يمكن استيراده في خدمات قراءة الأعلاف الأخرى"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "اسم الخلاصة"
@@ -540,6 +545,10 @@ msgstr "الأقدم أولا"
msgid "Oops!"
msgstr "اوووه!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "فتح الإدخال الحالي في علامة تبويب جديدة"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "التبديل إلى النسق الداكن"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "قم بالتبديل إلى النسق الفاتح"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Canviar la contrasenya generarà una nova clau d'API"
msgid "Check that the feed is working"
msgstr "Comproveu que el canal funciona"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed és un projecte de codi obert. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed següent element no llegit"
@@ -308,6 +308,11 @@ msgstr "Ampliat"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "exporteu les vostres subscripcions i categories com a fitxer OPML que es pot importar a altres serveis de lectura de feeds"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nom del canal"
@@ -540,6 +545,10 @@ msgstr "el més vell primer"
msgid "Oops!"
msgstr "Vaja!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Obre l'entrada actual en una pestanya nova"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Canvia al tema fosc"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Canvia al tema clar"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Změna hesla vygeneruje nový klíč API"
msgid "Check that the feed is working"
msgstr "Zkontrolujte, zda zdroj funguje"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed je projekt s otevřeným zdrojovým kódem. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed další nepřečtená položka"
@@ -308,6 +308,11 @@ msgstr "Rozbaleno"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exportujte svá předplatná a kategorie jako soubor OPML, který lze importovat do jiných služeb čtení kanálů"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Název zdroje"
@@ -540,6 +545,10 @@ msgstr "Nejdříve nejstarší"
msgid "Oops!"
msgstr "Jejda!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Otevřete aktuální položku na nové kartě"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Přepněte na tmavý motiv"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Přepněte na světlé téma"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Bydd newid cyfrinair yn cynhyrchu allwedd API newydd"
msgid "Check that the feed is working"
msgstr "Gwiriwch fod y porthiant yn gweithio"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "Mae ComaFeed yn brosiect ffynhonnell agored. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed eitem nesaf heb ei darllen"
@@ -308,6 +308,11 @@ msgstr "Ehangu"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Allforio eich tanysgrifiadau a'ch categorïau fel ffeil OPML y gellir ei mewnforio i wasanaethau darllen porthiant eraill"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Enw porthiant"
@@ -540,6 +545,10 @@ msgstr "Hynaf yn gyntaf"
msgid "Oops!"
msgstr "Wps!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Agorwch y cofnod cyfredol mewn tab newydd"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Newid i thema dywyll"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Newid i thema golau"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Ændring af adgangskode vil generere en ny API-nøgle"
msgid "Check that the feed is working"
msgstr "Tjek, at foderet virker"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed er et open source-projekt. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed næste ulæste element"
@@ -308,6 +308,11 @@ msgstr "Udvidet"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Eksporter dine abonnementer og kategorier som en OPML-fil, der kan importeres i andre feed-læsningstjenester"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Feednavn"
@@ -540,6 +545,10 @@ msgstr "Ældst først"
msgid "Oops!"
msgstr "Hovsa!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Åbn den aktuelle post i en ny fane"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Skift til mørkt tema"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Skift til lystema"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Das Ändern des Passworts generiert einen neuen API-Schlüssel"
msgid "Check that the feed is working"
msgstr "Überprüfen Sie, ob der Feed funktioniert"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed ist ein Open-Source-Projekt. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed nächstes ungelesenes Element"
@@ -308,6 +308,11 @@ msgstr "Erweitert"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exportieren Sie Ihre Abonnements und Kategorien als OPML-Datei, die in andere Feed-Lesedienste importiert werden kann"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Feedname"
@@ -540,6 +545,10 @@ msgstr "Älteste zuerst"
msgid "Oops!"
msgstr "Ups!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Aktuellen Eintrag in neuem Tab öffnen"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Zum dunklen Design wechseln"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Wechseln Sie zum Lichtdesign"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr "{0} (in {1})"
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr "<0>Complete syntax is available </0><1>here</1>."
@@ -159,10 +163,6 @@ msgstr "Changing password will generate a new API key"
msgid "Check that the feed is working"
msgstr "Check that the feed is working"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed next unread item"
@@ -308,6 +308,11 @@ msgstr "Expanded"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr "Extension options"
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Feed name"
@@ -540,6 +545,10 @@ msgstr "Oldest first"
msgid "Oops!"
msgstr "Oops!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr "Open CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Open current entry in a new tab"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr "Swipe header to the right"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Switch to dark theme"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Switch to light theme"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Cambiar la contraseña generará una nueva clave API"
msgid "Check that the feed is working"
msgstr "Compruebe que el feed funciona"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed es un proyecto de código abierto. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed siguiente elemento no leído"
@@ -308,6 +308,11 @@ msgstr "Expandido"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exporte sus suscripciones y categorías como un archivo OPML que se puede importar en otros servicios de lectura de feeds"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nombre de alimentación"
@@ -540,6 +545,10 @@ msgstr "más antigua primero"
msgid "Oops!"
msgstr "¡Ups!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Abrir la entrada actual en una nueva pestaña"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Cambiar a tema oscuro"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Cambiar a tema claro"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "تغییر رمز عبور یک کلید API جدید ایجاد می ک
msgid "Check that the feed is working"
msgstr "بررسی کنید که خوراک کار می کند"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed یک پروژه منبع باز است. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "مورد خوانده نشده بعدی CommaFeed"
@@ -308,6 +308,11 @@ msgstr "گسترش یافت"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "اشتراک ها و دسته های خود را به عنوان یک فایل OPML صادر کنید که می تواند در سایر خدمات خواندن فید وارد شود"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "نام فید"
@@ -540,6 +545,10 @@ msgstr "قدیمی ترین اول"
msgid "Oops!"
msgstr "اوه!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "ورودی فعلی را در یک برگه جدید باز کنید"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "تغییر به تم تیره"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "روی زمینه روشن"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Salasanan vaihtaminen luo uuden API-avaimen"
msgid "Check that the feed is working"
msgstr "Tarkista, että syöttö toimii"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed on avoimen lähdekoodin projekti. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed seuraava lukematon kohde"
@@ -308,6 +308,11 @@ msgstr "Laajennettu"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Vie tilauksesi ja luokat OPML-tiedostona, joka voidaan tuoda muihin syötteiden lukupalveluihin"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Syötteen nimi"
@@ -540,6 +545,10 @@ msgstr "Vanhin ensin"
msgid "Oops!"
msgstr "Hups!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Avaa nykyinen merkintä uudessa välilehdessä"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Vaihda tummaan teemaan"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Vaihda vaaleaan teemaan"

View File

@@ -15,7 +15,11 @@ msgstr ""
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
msgstr "{0} (sur {1})"
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "<0>CommaFeed est un projet open-source. Les sources sont hébergées sur </0><1>GitHub</1>."
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
@@ -27,7 +31,7 @@ msgstr "<0>Déjà un compte ?</0><1>Connectez-vous !</1>"
#: src/pages/app/DonatePage.tsx
msgid "<0>Hey,</0><1>I'm Jérémie from Belgium and I've been working on CommaFeed in my free time for over 10 years now. Thanks for taking an interest in helping me continue supporting CommaFeed.</1>"
msgstr ""
msgstr "<0>Salut,</0><1>Je m'appelle Jérémie, je suis belge, et je développe CommaFeed sur mon temps libre depuis maintenant 10 ans. Merci de m'aider à continuer de maintenir CommaFeed.</1>"
#: src/pages/auth/LoginPage.tsx
msgid "<0>Need an account?</0><1>Sign up!</1>"
@@ -36,7 +40,7 @@ msgstr "<0>Besoin d'un compte ?</0><1>Enregistrez-vous !</1>"
#: src/components/header/ProfileMenu.tsx
#: src/pages/app/AboutPage.tsx
msgid "About"
msgstr "A propos"
msgstr "À propos"
#: src/pages/admin/AdminUsersPage.tsx
msgid "Actions"
@@ -85,11 +89,11 @@ msgstr "Clé API"
#: src/pages/app/CategoryDetailsPage.tsx
msgid "Are you sure you want to delete category <0>{categoryName}</0>?"
msgstr "Etes-vous sûr de vouloir supprimer la catégorie <0>{categoryName}</0>?"
msgstr "Êtes-vous sûr de vouloir supprimer la catégorie <0>{categoryName}</0> ?"
#: src/pages/admin/AdminUsersPage.tsx
msgid "Are you sure you want to delete user <0>{userName}</0> ?"
msgstr "Etes-vous sûr de vouloir supprimer l'utilisateur <0>{userName}</0> ?"
msgstr "Êtes-vous sûr de vouloir supprimer l'utilisateur <0>{userName}</0> ?"
#: src/components/settings/ProfileSettings.tsx
msgid "Are you sure you want to delete your account? There's no turning back!"
@@ -97,15 +101,15 @@ msgstr "Êtes-vous sûr de vouloir supprimer définitivement votre compte ?"
#: src/components/header/MarkAllAsReadButton.tsx
msgid "Are you sure you want to mark all entries of <0>{sourceLabel}</0> as read?"
msgstr "Etes-vous sûr de vouloir marquer toutes les entrées de <0>{sourceLabel}</0> comme lues?"
msgstr "Êtes-vous sûr de vouloir marquer toutes les entrées de <0>{sourceLabel}</0> comme lues ?"
#: src/components/header/MarkAllAsReadButton.tsx
msgid "Are you sure you want to mark entries older than {threshold} days of <0>{sourceLabel}</0> as read?"
msgstr "Etes-vous sûr de vouloir marquer les entrées de <0>{sourceLabel}</0> plus anciennes que {threshold} jours comme lues?"
msgstr "Êtes-vous sûr de vouloir marquer les entrées de <0>{sourceLabel}</0> plus anciennes que {threshold} jours comme lues ?"
#: src/pages/app/FeedDetailsPage.tsx
msgid "Are you sure you want to unsubscribe from <0>{feedName}</0>?"
msgstr "Etes-vous sûr de vouloir vous désabonner de <0>{feedName}</0>?"
msgstr "Êtes-vous sûr de vouloir vous désabonner de <0>{feedName}</0> ?"
#: src/components/header/Header.tsx
msgid "Asc"
@@ -159,10 +163,6 @@ msgstr "Changer de mot de passe générera une nouvelle clé API"
msgid "Check that the feed is working"
msgstr "Vérifie que le flux fonctionne"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed est un projet open-source. Les sources sont hébergées sur <0>GitHub</0>."
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed prochain article non lu"
@@ -193,7 +193,7 @@ msgstr "Cozy"
#: src/components/content/FeedEntryFooter.tsx
msgid "Create tag: {query}"
msgstr "Créer le tag: {query}"
msgstr "Créer le marqueur : {query}"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Ctrl"
@@ -205,15 +205,15 @@ msgstr "Mot de passe actuel"
#: src/pages/app/SettingsPage.tsx
msgid "Custom code"
msgstr ""
msgstr "Code personnalisé"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied"
msgstr ""
msgstr "Code CSS personnalisé qui sera appliqué"
#: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load"
msgstr ""
msgstr "Code JS personnalisé qui sera appliqué au chargement des pages"
#: src/pages/admin/AdminUsersPage.tsx
msgid "Date created"
@@ -252,7 +252,7 @@ msgstr "Affichage"
#: src/components/header/ProfileMenu.tsx
#: src/pages/app/DonatePage.tsx
msgid "Donate"
msgstr ""
msgstr "Faites un don"
#: src/components/settings/ProfileSettings.tsx
msgid "Download"
@@ -308,6 +308,11 @@ msgstr "Vue étendue"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exporter vos abonnements et catégories en tant que fichier OPML qui peut être importé dans d'autres services de lecture de flux"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nom du flux"
@@ -320,7 +325,7 @@ msgstr "URL du flux"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
msgstr "Rafraîchir tous mes flux"
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
@@ -352,7 +357,7 @@ msgstr "URL du flux généré"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
msgstr "Aller à {0}"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Go to the All view"
@@ -420,19 +425,19 @@ msgstr "Lien"
#: src/hooks/useAppLoading.ts
msgid "Loading profile..."
msgstr "Chargement du profil ..."
msgstr "Chargement du profil..."
#: src/hooks/useAppLoading.ts
msgid "Loading settings..."
msgstr "Chargement des paramètres ..."
msgstr "Chargement des paramètres..."
#: src/hooks/useAppLoading.ts
msgid "Loading subscriptions..."
msgstr "Chargement des abonnements ..."
msgstr "Chargement des abonnements..."
#: src/hooks/useAppLoading.ts
msgid "Loading tags..."
msgstr "Chargement des tags ..."
msgstr "Chargement des marqueurs..."
#: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx
@@ -446,7 +451,7 @@ msgstr "Déconnexion"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
msgstr "Appui long"
#: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx
@@ -478,7 +483,7 @@ msgstr "Métriques"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
msgstr "Clic milieu"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down"
@@ -526,7 +531,7 @@ msgstr "Bookmarklet vers le prochain article non lu"
#: src/pages/app/FeedEntriesPage.tsx
msgid "No more entries"
msgstr "Plus d'entrées"
msgstr "Fin de la liste"
#: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found"
@@ -540,6 +545,10 @@ msgstr "Du plus ancien au plus récent"
msgid "Oops!"
msgstr "Oups !"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Ouvrir l'entrée actuelle dans un nouvel onglet"
@@ -554,11 +563,11 @@ msgstr "Ouvrir le lien"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
msgstr "Ouvrir le lien dans un nouvel onglet en arrière-plan"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
msgstr "Ouvrir le lien dans un nouvel onglet"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry"
@@ -641,7 +650,7 @@ msgstr "API REST"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
msgstr "Clic droit"
#: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx
@@ -697,11 +706,11 @@ msgstr "Maj"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
msgstr "Afficher les options de l'entrée (ordinateur)"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
msgstr "Afficher les options de l'entrée (mobile)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries"
@@ -759,16 +768,18 @@ msgid "Swipe header to the right"
msgstr "Faire glisser le titre vers la droite"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Activer le mode sombre"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Activer le mode clair"
#: src/components/content/FeedEntryFooter.tsx
msgid "Tags"
msgstr "Tags"
msgstr "Marqueurs"
#: src/components/content/add/Subscribe.tsx
msgid "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."
@@ -788,7 +799,7 @@ msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
msgstr "Essayez la version de démonstration !"
#: src/components/header/Header.tsx
msgid "Unread"
@@ -827,4 +838,4 @@ msgstr "Vous n'avez pas encore d'abonnements. Pourquoi ne pas essayer d'en ajout
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
msgstr "Vos flux sont en cours de rafraîchissement"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "O cambio de contrasinal xerará unha nova clave de API"
msgid "Check that the feed is working"
msgstr "Comproba que a fonte funciona"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed é un proxecto de código aberto. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed seguinte elemento non lido"
@@ -308,6 +308,11 @@ msgstr "Ampliado"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exporta as túas subscricións e categorías como ficheiro OPML que se pode importar noutros servizos de lectura de feeds"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nome do feed"
@@ -540,6 +545,10 @@ msgstr "O máis vello primeiro"
msgid "Oops!"
msgstr "Vaia!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Abrir a entrada actual nunha nova pestana"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Cambiar ao tema escuro"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Cambiar ao tema claro"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "A jelszó megváltoztatása új API-kulcsot generál"
msgid "Check that the feed is working"
msgstr "Ellenőrizze, hogy a feed működik-e"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed következő olvasatlan elem"
@@ -308,6 +308,11 @@ msgstr "Kiterjesztve"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exportálja előfizetéseit és kategóriáit OPML-fájlként, amely importálható más feedolvasó szolgáltatásokba"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Hírcsatorna neve"
@@ -540,6 +545,10 @@ msgstr "A legidősebb első"
msgid "Oops!"
msgstr "Hoppá!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Az aktuális bejegyzés megnyitása új lapon"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Váltás sötét témára"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Váltás világos témára"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Mengubah kata sandi akan menghasilkan kunci API baru"
msgid "Check that the feed is working"
msgstr "Periksa apakah umpannya berfungsi"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed adalah proyek sumber terbuka. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed item yang belum dibaca berikutnya"
@@ -308,6 +308,11 @@ msgstr "Diperluas"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Ekspor langganan dan kategori Anda sebagai file OPML yang dapat diimpor ke layanan membaca feed lainnya"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nama umpan"
@@ -540,6 +545,10 @@ msgstr "Tertua dulu"
msgid "Oops!"
msgstr "Ups!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Buka entri saat ini di tab baru"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Beralih ke tema gelap"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Beralih ke tema terang"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "La modifica della password genererà una nuova chiave API"
msgid "Check that the feed is working"
msgstr "Verifica che il feed funzioni"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed è un progetto open source. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed successivo elemento non letto"
@@ -308,6 +308,11 @@ msgstr "Espanso"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Esporta le tue iscrizioni e categorie come file OPML che può essere importato in altri servizi di lettura feed"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nome del feed"
@@ -540,6 +545,10 @@ msgstr "Il più vecchio prima"
msgid "Oops!"
msgstr "Ops!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Apri la voce corrente in una nuova scheda"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Passa al tema scuro"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Passa al tema della luce"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "パスワードを変更すると、新しい API キーが生成され
msgid "Check that the feed is working"
msgstr "フィードが動作していることを確認してください"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed はオープンソース プロジェクトです。"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "次の未読アイテムをカンマフィード"
@@ -308,6 +308,11 @@ msgstr "拡張"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "サブスクリプションとカテゴリを、他のフィード読み取りサービスにインポートできる OPML ファイルとしてエクスポートします"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "フィード名"
@@ -540,6 +545,10 @@ msgstr "古い順"
msgid "Oops!"
msgstr "おっと!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "現在のエントリを新しいタブで開く"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "ダークテーマに切り替え"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "ライトテーマに切り替え"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "비밀번호를 변경하면 새 API 키가 생성됩니다."
msgid "Check that the feed is working"
msgstr "피드가 작동하는지 확인"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed는 오픈 소스 프로젝트입니다. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "다음 읽지 않은 항목을 쉼표로 피드"
@@ -308,6 +308,11 @@ msgstr "확장"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "구독 및 카테고리를 다른 피드 읽기 서비스에서 가져올 수 있는 OPML 파일로 내보내기"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "피드 이름"
@@ -540,6 +545,10 @@ msgstr "가장 오래된 것부터"
msgid "Oops!"
msgstr "앗!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "새 탭에서 현재 항목 열기"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "어두운 테마로 전환"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "밝은 테마로 전환"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Menukar kata laluan akan menjana kunci API baharu"
msgid "Check that the feed is working"
msgstr "Semak sama ada suapan berfungsi"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed ialah projek sumber terbuka. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed item belum dibaca seterusnya"
@@ -308,6 +308,11 @@ msgstr "Dikembangkan"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Eksport langganan dan kategori anda sebagai fail OPML yang boleh diimport dalam perkhidmatan membaca suapan lain"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nama suapan"
@@ -540,6 +545,10 @@ msgstr "Tertua dahulu"
msgid "Oops!"
msgstr "Aduh!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Buka entri semasa dalam tab baharu"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Tukar kepada tema gelap"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Tukar kepada tema cahaya"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Endring av passord vil generere en ny API-nøkkel"
msgid "Check that the feed is working"
msgstr "Sjekk at feeden fungerer"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed er et åpen kildekode-prosjekt. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed neste uleste element"
@@ -308,6 +308,11 @@ msgstr "Utvidet"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Eksporter abonnementene og kategoriene dine som en OPML-fil som kan importeres i andre feedlesetjenester"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Feednavn"
@@ -540,6 +545,10 @@ msgstr "Eldste først"
msgid "Oops!"
msgstr "Beklager!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Åpne gjeldende oppføring i en ny fane"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Bytt til mørkt tema"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Bytt til lystema"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Het wijzigen van het wachtwoord genereert een nieuwe API-sleutel"
msgid "Check that the feed is working"
msgstr "Controleer of de feed werkt"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed is een open-sourceproject. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed volgende ongelezen item"
@@ -308,6 +308,11 @@ msgstr "Uitgebreid"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exporteer uw abonnementen en categorieën als een OPML-bestand dat kan worden geïmporteerd in andere feedleesservices"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Feednaam"
@@ -540,6 +545,10 @@ msgstr "Oudste eerst"
msgid "Oops!"
msgstr "Oeps!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Huidige invoer openen in een nieuw tabblad"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Overschakelen naar donker thema"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Overschakelen naar lichtthema"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Endring av passord vil generere en ny API-nøkkel"
msgid "Check that the feed is working"
msgstr "Sjekk at feeden fungerer"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed er et åpen kildekode-prosjekt. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed neste uleste element"
@@ -308,6 +308,11 @@ msgstr "Utvidet"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Eksporter abonnementene og kategoriene dine som en OPML-fil som kan importeres i andre feedlesetjenester"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Feednavn"
@@ -540,6 +545,10 @@ msgstr "Eldste først"
msgid "Oops!"
msgstr "Beklager!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Åpne gjeldende oppføring i en ny fane"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Bytt til mørkt tema"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Bytt til lystema"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Zmiana hasła spowoduje wygenerowanie nowego klucza API"
msgid "Check that the feed is working"
msgstr "Sprawdź, czy kanał działa"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed to projekt typu open source. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "Przecinek następny nieprzeczytany element"
@@ -308,6 +308,11 @@ msgstr "Rozszerzony"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Eksportuj swoje subskrypcje i kategorie jako plik OPML, który można zaimportować do innych usług odczytu kanałów"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "nazwa kanału"
@@ -540,6 +545,10 @@ msgstr "Najstarsze jako pierwsze"
msgid "Oops!"
msgstr "Ups!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Otwórz bieżący wpis w nowej karcie"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Przełącz na ciemny motyw"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Przełącz na jasny motyw"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "A alteração da senha gerará uma nova chave de API"
msgid "Check that the feed is working"
msgstr "Verifique se o feed está funcionando"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed é um projeto de código aberto. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed próximo item não lido"
@@ -308,6 +308,11 @@ msgstr "Expandido"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exporte suas inscrições e categorias como um arquivo OPML que pode ser importado em outros serviços de leitura de feed"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Nome do feed"
@@ -540,6 +545,10 @@ msgstr "Mais antigo primeiro"
msgid "Oops!"
msgstr "Opa!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Abrir a entrada atual em uma nova aba"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Mudar para tema escuro"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Mudar para tema claro"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "При изменении пароля будет сгенерирова
msgid "Check that the feed is working"
msgstr "Проверьте, работает ли лента."
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed — это проект с открытым исходным кодом. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed следующий непрочитанный элемент"
@@ -308,6 +308,11 @@ msgstr "Расширенный"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Экспортируйте свои подписки и категории в виде файла OPML, который можно импортировать в другие службы чтения каналов."
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Имя фида"
@@ -540,6 +545,10 @@ msgstr "Сначала самые старые"
msgid "Oops!"
msgstr "Ой!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Открыть текущую запись в новой вкладке"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Переключиться на темную тему"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Переключиться на светлую тему"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Zmena hesla vygeneruje nový kľúč API"
msgid "Check that the feed is working"
msgstr "Skontrolujte, či feed funguje"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed je projekt s otvoreným zdrojom. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed ďalšia neprečítaná položka"
@@ -308,6 +308,11 @@ msgstr "Rozšírené"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exportujte svoje odbery a kategórie ako súbor OPML, ktorý je možné importovať do iných služieb na čítanie informačných kanálov"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Názov informačného kanála"
@@ -540,6 +545,10 @@ msgstr "Najprv najstarší"
msgid "Oops!"
msgstr "Ojoj!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Otvorte aktuálny záznam na novej karte"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Prepnúť na tmavú tému"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Prepnite na svetlú tému"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Ändra lösenord kommer att generera en ny API-nyckel"
msgid "Check that the feed is working"
msgstr "Kontrollera att matningen fungerar"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed är ett projekt med öppen källkod. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed nästa olästa objekt"
@@ -308,6 +308,11 @@ msgstr "Utökad"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Exportera dina prenumerationer och kategorier som en OPML-fil som kan importeras i andra flödesläsningstjänster"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Flödesnamn"
@@ -540,6 +545,10 @@ msgstr "Äldst först"
msgid "Oops!"
msgstr "Hoppsan!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Öppna aktuell post i en ny flik"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Byt till mörkt tema"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Byt till ljustema"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "Şifreyi değiştirmek yeni bir API anahtarı oluşturacak"
msgid "Check that the feed is working"
msgstr "Feed'in çalışıp çalışmadığını kontrol edin"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed açık kaynaklı bir projedir. "
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed sonraki okunmamış öğe"
@@ -308,6 +308,11 @@ msgstr "Genişletilmiş"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "Aboneliklerinizi ve kategorilerinizi diğer besleme okuma hizmetlerinde içe aktarılabilen bir OPML dosyası olarak dışa aktarın"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "Yayın adı"
@@ -540,6 +545,10 @@ msgstr "Önce en eski"
msgid "Oops!"
msgstr "Hata!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "Geçerli girişi yeni bir sekmede aç"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "Karanlık temaya geç"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "Açık temaya geç"

View File

@@ -17,6 +17,10 @@ msgstr ""
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr ""
@@ -159,10 +163,6 @@ msgstr "更改密码将生成新的 API 密钥"
msgid "Check that the feed is working"
msgstr "检查提要是否正常工作"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed is an open-source project. Sources are hosted on <0>GitHub</0>."
msgstr "CommaFeed 是一个开源项目。"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item"
msgstr "CommaFeed 下一个未读项目"
@@ -308,6 +308,11 @@ msgstr "展开"
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
msgstr "将您的订阅和类别导出为 OPML 文件,可以在其他提要阅读服务中导入"
#: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx
msgid "Extension options"
msgstr ""
#: src/components/content/add/Subscribe.tsx
msgid "Feed name"
msgstr "提要名称"
@@ -540,6 +545,10 @@ msgstr "最早的优先"
msgid "Oops!"
msgstr "哎呀!"
#: src/components/header/Header.tsx
msgid "Open CommaFeed"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab"
msgstr "在新选项卡中打开当前条目"
@@ -759,10 +768,12 @@ msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to dark theme"
msgstr "切换到深色主题"
#: src/components/header/ProfileMenu.tsx
#: src/pages/WelcomePage.tsx
msgid "Switch to light theme"
msgstr "切换到浅色主题"

View File

@@ -0,0 +1,4 @@
html, body {
/* disable pull-to-refresh on mobile as it messes with vertical scrolling */
overscroll-behavior: none;
}

View File

@@ -1,11 +1,12 @@
import "@fontsource/open-sans"
import { App } from "App"
import { store } from "app/store"
import dayjs from "dayjs"
import relativeTime from "dayjs/plugin/relativeTime"
import "main.css"
import "react-contexify/ReactContexify.css"
import ReactDOM from "react-dom/client"
import { Provider } from "react-redux"
import { App } from "./App"
dayjs.extend(relativeTime)

View File

@@ -3,16 +3,16 @@ import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantin
import { useMediaQuery } from "@mantine/hooks"
import { client } from "app/client"
import { Constants } from "app/constants"
import { redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/slices/redirect"
import { redirectToApiDocumentation, 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_light from "assets/welcome_page_light.png"
import { ActionButton } from "components/ActionButtton"
import { ButtonToolbar } from "components/ButtonToolbar"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useAsyncCallback } from "react-async-hook"
import { SiGithub, TbKey, TbUserPlus } from "react-icons/all"
import { SiTwitter } from "react-icons/si"
import { TbClock, TbMoon, TbSun } from "react-icons/tb"
import { SiGithub, SiTwitter } from "react-icons/si"
import { TbClock, TbKey, TbMoon, TbSettings, TbSun, TbUserPlus } from "react-icons/tb"
import { PageTitle } from "./PageTitle"
export function WelcomePage() {
@@ -63,8 +63,9 @@ function Buttons() {
const iconSize = 18
const serverInfos = useAppSelector(state => state.server.serverInfos)
const { colorScheme, toggleColorScheme } = useMantineColorScheme()
const { isBrowserExtension, openSettingsPage } = useBrowserExtension()
const dispatch = useAppDispatch()
const dark = colorScheme === "dark"
const login = useAsyncCallback(client.user.login, {
onSuccess: () => {
@@ -101,19 +102,30 @@ function Buttons() {
)}
<ActionButton
label={dark ? <Trans>Switch to light theme</Trans> : <Trans>Switch to dark theme</Trans>}
icon={colorScheme === "dark" ? <TbSun size={18} /> : <TbMoon size={iconSize} />}
onClick={() => toggleColorScheme()}
hideLabelOnDesktop
/>
{isBrowserExtension && (
<ActionButton
label={<Trans>Extension options</Trans>}
icon={<TbSettings size={iconSize} />}
onClick={() => openSettingsPage()}
hideLabelOnDesktop
/>
)}
</ButtonToolbar>
)
}
function Footer() {
const dispatch = useAppDispatch()
return (
<Box>
<Group position="apart">
<Group>
<span>© CommaFeed</span>
<span> - </span>
<Anchor variant="text" href="https://github.com/Athou/commafeed/" target="_blank" rel="noreferrer">
<SiGithub />
</Anchor>
@@ -121,6 +133,11 @@ function Footer() {
<SiTwitter />
</Anchor>
</Group>
</Box>
<Box>
<Anchor variant="text" onClick={() => dispatch(redirectToApiDocumentation())}>
API documentation
</Anchor>
</Box>
</Group>
)
}

View File

@@ -1,6 +1,7 @@
import { Accordion, Tabs } from "@mantine/core"
import { Accordion, Box, Tabs } from "@mantine/core"
import { client } from "app/client"
import { Loader } from "components/Loader"
import { Gauge } from "components/metrics/Gauge"
import { Meter } from "components/metrics/Meter"
import { MetricAccordionItem } from "components/metrics/MetricAccordionItem"
import { Timer } from "components/metrics/Timer"
@@ -15,11 +16,17 @@ const shownMeters: { [key: string]: string } = {
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
}
const shownGauges: { [key: string]: string } = {
"com.commafeed.backend.feed.FeedRefreshEngine.queue.size": "Queue size",
"com.commafeed.backend.feed.FeedRefreshEngine.worker.active": "Feed Worker active",
"com.commafeed.backend.feed.FeedRefreshEngine.updater.active": "Feed Updater active",
}
export function MetricsPage() {
const query = useAsync(() => client.admin.getMetrics(), [])
if (!query.result) return <Loader />
const { meters, timers } = query.result.data
const { meters, gauges, timers } = query.result.data
return (
<Tabs defaultValue="stats">
<Tabs.List>
@@ -39,6 +46,15 @@ export function MetricsPage() {
</MetricAccordionItem>
))}
</Accordion>
<Box pt="xs">
{Object.keys(shownGauges).map(g => (
<Box key={g}>
<span>{shownGauges[g]}&nbsp;</span>
<Gauge gauge={gauges[g]} />
</Box>
))}
</Box>
</Tabs.Panel>
<Tabs.Panel value="timers" pt="xs">

View File

@@ -72,7 +72,7 @@ export function AboutPage() {
</Box>
<Box mt="md">
<Trans>
CommaFeed is an open-source project. Sources are hosted on&nbsp;
<span>CommaFeed is an open-source project. Sources are hosted on </span>
<Anchor href="https://github.com/Athou/commafeed" target="_blank" rel="noreferrer">
GitHub
</Anchor>
@@ -86,28 +86,9 @@ export function AboutPage() {
<Section title={<Trans>Goodies</Trans>} icon={<TbPuzzle size={24} />}>
<List>
<List.Item>
<Trans>Browser extentions</Trans>
<List withPadding>
<List.Item>
<Anchor
href="https://addons.mozilla.org/en-US/firefox/addon/commafeed/"
target="_blank"
rel="noreferrer"
>
Firefox
</Anchor>
</List.Item>
<List.Item>
<Anchor href="https://github.com/Athou/commafeed-chrome" target="_blank" rel="noreferrer">
Chrome
</Anchor>
</List.Item>
<List.Item>
<Anchor href="https://github.com/Athou/commafeed-opera" target="_blank" rel="noreferrer">
Opera
</Anchor>
</List.Item>
</List>
<Anchor href="https://github.com/Athou/commafeed-browser-extension" target="_blank" rel="noreferrer">
<Trans>Browser extentions</Trans>
</Anchor>
</List.Item>
<List.Item>
<Trans>Subscribe URL</Trans>

View File

@@ -24,6 +24,7 @@ export default defineConfig({
port: 8082,
proxy: {
"/rest": "http://localhost:8083",
"/next": "http://localhost:8083",
"/ws": "ws://localhost:8083",
"/swagger": "http://localhost:8083",
"/custom_css.css": "http://localhost:8083",

View File

@@ -4,8 +4,11 @@ app:
# url used to access commafeed
publicUrl: http://localhost:8082/
# wether to allow user registrations
# whether to allow user registrations
allowRegistrations: true
# whether to enable strict password validation (1 uppercase char, 1 lowercase char, 1 digit, 1 special char)
strictPasswordPolicy: true
# create a demo account the first time the app starts
createDemoAccount: true
@@ -37,14 +40,14 @@ app:
graphitePort: 2003
graphiteInterval: 60
# wether this commafeed instance has a lot of feeds to refresh
# whether this commafeed instance has a lot of feeds to refresh
# leave this to false in almost all cases
heavyLoad: false
# minimum amount of time commafeed will wait before refreshing the same feed
refreshIntervalMinutes: 5
# wether to enable pubsub
# whether to enable pubsub
# probably not needed if refreshIntervalMinutes is low
pubsubhubbub: false
@@ -60,7 +63,10 @@ app:
# entries to keep per feed, old entries will be deleted, 0 to disable
maxFeedCapacity: 500
# limit the number of feeds a user can subscribe to, 0 to disable
maxFeedsPerUser: 0
# cache service to use, possible values are 'noop' and 'redis'
cache: noop
@@ -86,7 +92,7 @@ app:
database:
driverClass: org.h2.Driver
url: jdbc:h2:./target/example
url: jdbc:h2:./target/commafeed
user: sa
password: sa
properties:

View File

@@ -6,6 +6,9 @@ app:
# whether to allow user registrations
allowRegistrations: false
# whether to enable strict password validation (1 uppercase char, 1 lowercase char, 1 digit, 1 special char)
strictPasswordPolicy: true
# create a demo account the first time the app starts
createDemoAccount: false
@@ -38,14 +41,14 @@ app:
graphitePort: 2003
graphiteInterval: 60
# wether this commafeed instance has a lot of feeds to refresh
# whether this commafeed instance has a lot of feeds to refresh
# leave this to false in almost all cases
heavyLoad: false
# minimum amount of time commafeed will wait before refreshing the same feed
refreshIntervalMinutes: 5
# wether to enable pubsub
# whether to enable pubsub
# probably not needed if refreshIntervalMinutes is low
pubsubhubbub: false
@@ -61,7 +64,10 @@ app:
# entries to keep per feed, old entries will be deleted, 0 to disable
maxFeedCapacity: 500
# limit the number of feeds a user can subscribe to, 0 to disable
maxFeedsPerUser: 0
# cache service to use, possible values are 'noop' and 'redis'
cache: noop
@@ -87,7 +93,7 @@ app:
database:
driverClass: org.h2.Driver
url: jdbc:h2:/commafeed/data/db
url: jdbc:h2:./db/commafeed
user: sa
password: sa
properties:

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>3.4.0</version>
<version>3.6.0</version>
</parent>
<artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name>
@@ -226,7 +226,7 @@
<dependency>
<groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId>
<version>3.4.0</version>
<version>3.6.0</version>
</dependency>
<dependency>
@@ -278,11 +278,6 @@
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-json</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.modules</groupId>
<artifactId>dropwizard-web</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>be.tomcools</groupId>
<artifactId>dropwizard-websocket-jee7-bundle</artifactId>
@@ -374,7 +369,7 @@
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.2</version>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>

View File

@@ -33,6 +33,7 @@ import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.service.DatabaseStartupService;
import com.commafeed.backend.service.UserService;
import com.commafeed.backend.task.ScheduledTask;
import com.commafeed.frontend.auth.PasswordConstraintValidator;
import com.commafeed.frontend.auth.SecurityCheckFactoryProvider;
import com.commafeed.frontend.resource.AdminREST;
import com.commafeed.frontend.resource.CategoryREST;
@@ -69,8 +70,6 @@ import io.dropwizard.migrations.MigrationsBundle;
import io.dropwizard.servlets.CacheBustingFilter;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.web.WebBundle;
import io.dropwizard.web.conf.WebConfiguration;
import io.whitfin.dropwizard.configuration.EnvironmentSubstitutor;
public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
@@ -118,24 +117,16 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
DataSourceFactory factory = configuration.getDataSourceFactory();
// keep using old id generator for backward compatibility
factory.getProperties().put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false");
factory.getProperties().put(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, "pooled-lo");
factory.getProperties().put(AvailableSettings.STATEMENT_BATCH_SIZE, "50");
factory.getProperties().put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
factory.getProperties().put(AvailableSettings.ORDER_INSERTS, "true");
factory.getProperties().put(AvailableSettings.ORDER_UPDATES, "true");
return factory;
}
});
bootstrap.addBundle(new WebBundle<CommaFeedConfiguration>() {
@Override
public WebConfiguration getWebConfiguration(CommaFeedConfiguration configuration) {
WebConfiguration config = new WebConfiguration();
config.getFrameOptionsHeaderFactory().setEnabled(true);
return config;
}
});
bootstrap.addBundle(new MigrationsBundle<CommaFeedConfiguration>() {
@Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
@@ -149,6 +140,8 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
@Override
public void run(CommaFeedConfiguration config, Environment environment) throws Exception {
PasswordConstraintValidator.setStrict(config.getApplicationSettings().getStrictPasswordPolicy());
// guice init
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));

View File

@@ -17,8 +17,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CommaFeedConfiguration extends Configuration {
public enum CacheType {
@@ -56,6 +58,7 @@ public class CommaFeedConfiguration extends Configuration {
}
@Getter
@Setter
public static class ApplicationSettings {
@NotNull
@NotBlank
@@ -66,6 +69,10 @@ public class CommaFeedConfiguration extends Configuration {
@Valid
private Boolean allowRegistrations;
@NotNull
@Valid
private Boolean strictPasswordPolicy = true;
@NotNull
@Valid
private Boolean createDemoAccount;
@@ -124,6 +131,10 @@ public class CommaFeedConfiguration extends Configuration {
@Valid
private Integer maxFeedCapacity;
@NotNull
@Valid
private Integer maxFeedsPerUser = 0;
@NotNull
@Min(0)
@Valid

View File

@@ -12,7 +12,7 @@ import java.util.List;
*/
public class FixedSizeSortedSet<E> {
private List<E> inner;
private final List<E> inner;
private final Comparator<? super E> comparator;
private final int capacity;

View File

@@ -18,7 +18,7 @@ import com.querydsl.core.types.Predicate;
@Singleton
public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
private QFeedCategory category = QFeedCategory.feedCategory;
private final QFeedCategory category = QFeedCategory.feedCategory;
@Inject
public FeedCategoryDAO(SessionFactory sessionFactory) {

View File

@@ -13,10 +13,8 @@ import org.hibernate.SessionFactory;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.QFeed;
import com.commafeed.backend.model.QFeedSubscription;
import com.commafeed.backend.model.QUser;
import com.google.common.collect.Iterables;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
@Singleton
public class FeedDAO extends GenericDAO<Feed> {
@@ -28,18 +26,12 @@ public class FeedDAO extends GenericDAO<Feed> {
super(sessionFactory);
}
public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) {
JPQLQuery<Feed> query = query().selectFrom(feed);
query.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date())));
if (lastLoginThreshold != null) {
QFeedSubscription subs = QFeedSubscription.feedSubscription;
QUser user = QUser.user;
query.join(subs).on(subs.feed.id.eq(feed.id)).join(subs.user, user).where(user.lastLogin.gt(lastLoginThreshold));
}
return query.orderBy(feed.disabledUntil.asc()).limit(count).fetch();
public List<Feed> findNextUpdatable(int count) {
return query().selectFrom(feed)
.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date())))
.orderBy(feed.disabledUntil.asc())
.limit(count)
.fetch();
}
public void setDisabledUntil(List<Long> feedIds, Date date) {

View File

@@ -21,7 +21,7 @@ import lombok.Getter;
@Singleton
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
private QFeedEntry entry = QFeedEntry.feedEntry;
private final QFeedEntry entry = QFeedEntry.feedEntry;
@Inject
public FeedEntryDAO(SessionFactory sessionFactory) {

View File

@@ -77,7 +77,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
if (status == null) {
Date unreadThreshold = config.getApplicationSettings().getUnreadThreshold();
boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold);
boolean read = unreadThreshold != null && entry.getUpdated().before(unreadThreshold);
status = new FeedEntryStatus(user, sub, entry);
status.setRead(read);
status.setMarkable(!read);

View File

@@ -15,7 +15,7 @@ import com.commafeed.backend.model.User;
@Singleton
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
private QFeedEntryTag tag = QFeedEntryTag.feedEntryTag;
private final QFeedEntryTag tag = QFeedEntryTag.feedEntryTag;
@Inject
public FeedEntryTagDAO(SessionFactory sessionFactory) {

View File

@@ -21,7 +21,7 @@ import com.querydsl.jpa.JPQLQuery;
@Singleton
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
private QFeedSubscription sub = QFeedSubscription.feedSubscription;
private final QFeedSubscription sub = QFeedSubscription.feedSubscription;
@Inject
public FeedSubscriptionDAO(SessionFactory sessionFactory) {
@@ -59,6 +59,10 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
return initRelations(subs);
}
public Long count(User user) {
return query().select(sub.count()).from(sub).where(sub.user.eq(user)).fetchOne();
}
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
JPQLQuery<FeedSubscription> query = query().selectFrom(sub).where(sub.user.eq(user));
if (category == null) {

View File

@@ -15,7 +15,7 @@ import io.dropwizard.hibernate.AbstractDAO;
public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T> {
private JPAQueryFactory factory;
private final JPAQueryFactory factory;
protected GenericDAO(SessionFactory sessionFactory) {
super(sessionFactory);

View File

@@ -1,53 +1,63 @@
package com.commafeed.backend.dao;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.context.internal.ManagedSessionContext;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class UnitOfWork {
public static void run(SessionFactory sessionFactory, SessionRunner sessionRunner) {
call(sessionFactory, () -> {
private final SessionFactory sessionFactory;
public void run(SessionRunner sessionRunner) {
call(() -> {
sessionRunner.runInSession();
return null;
});
}
public static <T> T call(SessionFactory sessionFactory, SessionRunnerReturningValue<T> sessionRunner) {
final Session session = sessionFactory.openSession();
if (ManagedSessionContext.hasBind(sessionFactory)) {
throw new IllegalStateException("Already in a unit of work!");
}
public <T> T call(SessionRunnerReturningValue<T> sessionRunner) {
T t = null;
try {
ManagedSessionContext.bind(session);
session.beginTransaction();
boolean sessionAlreadyBound = ManagedSessionContext.hasBind(sessionFactory);
try (Session session = sessionFactory.openSession()) {
if (!sessionAlreadyBound) {
ManagedSessionContext.bind(session);
}
Transaction tx = session.beginTransaction();
try {
t = sessionRunner.runInSession();
commitTransaction(session);
commitTransaction(tx);
} catch (Exception e) {
rollbackTransaction(session);
UnitOfWork.<RuntimeException> rethrow(e);
rollbackTransaction(tx);
UnitOfWork.rethrow(e);
}
} finally {
session.close();
ManagedSessionContext.unbind(sessionFactory);
if (!sessionAlreadyBound) {
ManagedSessionContext.unbind(sessionFactory);
}
}
return t;
}
private static void rollbackTransaction(Session session) {
final Transaction txn = session.getTransaction();
if (txn != null && txn.isActive()) {
txn.rollback();
private static void rollbackTransaction(Transaction tx) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
}
private static void commitTransaction(Session session) {
final Transaction txn = session.getTransaction();
if (txn != null && txn.isActive()) {
txn.commit();
private static void commitTransaction(Transaction tx) {
if (tx != null && tx.isActive()) {
tx.commit();
}
}

View File

@@ -11,7 +11,7 @@ import com.commafeed.backend.model.User;
@Singleton
public class UserDAO extends GenericDAO<User> {
private QUser user = QUser.user;
private final QUser user = QUser.user;
@Inject
public UserDAO(SessionFactory sessionFactory) {

View File

@@ -17,7 +17,7 @@ import com.commafeed.backend.model.UserRole.Role;
@Singleton
public class UserRoleDAO extends GenericDAO<UserRole> {
private QUserRole role = QUserRole.userRole;
private final QUserRole role = QUserRole.userRole;
@Inject
public UserRoleDAO(SessionFactory sessionFactory) {

View File

@@ -12,7 +12,7 @@ import com.commafeed.backend.model.UserSettings;
@Singleton
public class UserSettingsDAO extends GenericDAO<UserSettings> {
private QUserSettings settings = QUserSettings.userSettings;
private final QUserSettings settings = QUserSettings.userSettings;
@Inject
public UserSettingsDAO(SessionFactory sessionFactory) {

View File

@@ -16,7 +16,7 @@ import lombok.RequiredArgsConstructor;
public class FeedEntryKeyword {
public enum Mode {
INCLUDE, EXCLUDE;
INCLUDE, EXCLUDE
}
private final String keyword;

View File

@@ -73,7 +73,7 @@ public class FeedFetcher {
boolean etagHeaderValueChanged = !StringUtils.equals(eTag, result.getETag());
String hash = DigestUtils.sha1Hex(content);
if (lastContentHash != null && hash != null && lastContentHash.equals(hash)) {
if (lastContentHash != null && lastContentHash.equals(hash)) {
log.debug("content hash not modified: {}", feedUrl);
throw new NotModifiedException("content hash not modified",
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,

View File

@@ -16,8 +16,8 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.SessionFactory;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration;
@@ -33,7 +33,7 @@ import lombok.extern.slf4j.Slf4j;
@Singleton
public class FeedRefreshEngine implements Managed {
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
private final FeedDAO feedDAO;
private final FeedRefreshWorker worker;
private final FeedRefreshUpdater updater;
@@ -45,13 +45,13 @@ public class FeedRefreshEngine implements Managed {
private final ExecutorService feedProcessingLoopExecutor;
private final ExecutorService refillLoopExecutor;
private final ExecutorService refillExecutor;
private final ExecutorService workerExecutor;
private final ExecutorService databaseUpdaterExecutor;
private final ThreadPoolExecutor workerExecutor;
private final ThreadPoolExecutor databaseUpdaterExecutor;
@Inject
public FeedRefreshEngine(SessionFactory sessionFactory, FeedDAO feedDAO, FeedRefreshWorker worker, FeedRefreshUpdater updater,
public FeedRefreshEngine(UnitOfWork unitOfWork, FeedDAO feedDAO, FeedRefreshWorker worker, FeedRefreshUpdater updater,
CommaFeedConfiguration config, MetricRegistry metrics) {
this.sessionFactory = sessionFactory;
this.unitOfWork = unitOfWork;
this.feedDAO = feedDAO;
this.worker = worker;
this.updater = updater;
@@ -65,6 +65,10 @@ public class FeedRefreshEngine implements Managed {
this.refillExecutor = newDiscardingSingleThreadExecutorService();
this.workerExecutor = newBlockingExecutorService(config.getApplicationSettings().getBackgroundThreads());
this.databaseUpdaterExecutor = newBlockingExecutorService(config.getApplicationSettings().getDatabaseUpdateThreads());
metrics.register(MetricRegistry.name(getClass(), "queue", "size"), (Gauge<Integer>) queue::size);
metrics.register(MetricRegistry.name(getClass(), "worker", "active"), (Gauge<Integer>) workerExecutor::getActiveCount);
metrics.register(MetricRegistry.name(getClass(), "updater", "active"), (Gauge<Integer>) databaseUpdaterExecutor::getActiveCount);
}
@Override
@@ -156,8 +160,8 @@ public class FeedRefreshEngine implements Managed {
}
private List<Feed> getNextUpdatableFeeds(int max) {
return UnitOfWork.call(sessionFactory, () -> {
List<Feed> feeds = feedDAO.findNextUpdatable(max, getLastLoginThreshold());
return unitOfWork.call(() -> {
List<Feed> feeds = feedDAO.findNextUpdatable(max);
// update disabledUntil to prevent feeds from being returned again by feedDAO.findNextUpdatable()
Date nextUpdateDate = DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes());
feedDAO.setDisabledUntil(feeds.stream().map(AbstractModel::getId).collect(Collectors.toList()), nextUpdateDate);
@@ -169,10 +173,6 @@ public class FeedRefreshEngine implements Managed {
return Math.min(100, 3 * config.getApplicationSettings().getBackgroundThreads());
}
private Date getLastLoginThreshold() {
return Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad()) ? DateUtils.addDays(new Date(), -30) : null;
}
@Override
public void stop() {
this.feedProcessingLoopExecutor.shutdownNow();
@@ -185,7 +185,7 @@ public class FeedRefreshEngine implements Managed {
/**
* returns an ExecutorService with a single thread that discards tasks if a task is already running
*/
private ExecutorService newDiscardingSingleThreadExecutorService() {
private ThreadPoolExecutor newDiscardingSingleThreadExecutorService() {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
return pool;
@@ -194,7 +194,7 @@ public class FeedRefreshEngine implements Managed {
/**
* returns an ExecutorService that blocks submissions until a thread is available
*/
private ExecutorService newBlockingExecutorService(int threads) {
private ThreadPoolExecutor newBlockingExecutorService(int threads) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
pool.setRejectedExecutionHandler((r, e) -> {
if (e.isShutdown()) {

View File

@@ -13,8 +13,8 @@ import com.commafeed.backend.model.Feed;
@Singleton
public class FeedRefreshIntervalCalculator {
private boolean heavyLoad;
private int refreshIntervalMinutes;
private final boolean heavyLoad;
private final int refreshIntervalMinutes;
@Inject
public FeedRefreshIntervalCalculator(CommaFeedConfiguration config) {

View File

@@ -15,7 +15,6 @@ import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.SessionFactory;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
@@ -46,7 +45,7 @@ import lombok.extern.slf4j.Slf4j;
@Singleton
public class FeedRefreshUpdater implements Managed {
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
private final FeedService feedService;
private final FeedEntryService feedEntryService;
private final PubSubService pubSubService;
@@ -63,10 +62,10 @@ public class FeedRefreshUpdater implements Managed {
private final Meter entryInserted;
@Inject
public FeedRefreshUpdater(SessionFactory sessionFactory, FeedService feedService, FeedEntryService feedEntryService,
public FeedRefreshUpdater(UnitOfWork unitOfWork, FeedService feedService, FeedEntryService feedEntryService,
PubSubService pubSubService, CommaFeedConfiguration config, MetricRegistry metrics, FeedSubscriptionDAO feedSubscriptionDAO,
CacheService cache, WebSocketSessions webSocketSessions) {
this.sessionFactory = sessionFactory;
this.unitOfWork = unitOfWork;
this.feedService = feedService;
this.feedEntryService = feedEntryService;
this.pubSubService = pubSubService;
@@ -89,7 +88,7 @@ public class FeedRefreshUpdater implements Managed {
// lock on feed, make sure we are not updating the same feed twice at
// the same time
String key1 = StringUtils.trimToEmpty("" + feed.getId());
String key1 = StringUtils.trimToEmpty(String.valueOf(feed.getId()));
// lock on content, make sure we are not updating the same entry
// twice at the same time
@@ -107,7 +106,7 @@ public class FeedRefreshUpdater implements Managed {
locked2 = lock2.tryLock(1, TimeUnit.MINUTES);
if (locked1 && locked2) {
processed = true;
inserted = UnitOfWork.call(sessionFactory, () -> feedEntryService.addEntry(feed, entry, subscriptions));
inserted = unitOfWork.call(() -> feedEntryService.addEntry(feed, entry, subscriptions));
if (inserted) {
entryInserted.mark();
}
@@ -164,7 +163,7 @@ public class FeedRefreshUpdater implements Managed {
if (!lastEntries.contains(cacheKey)) {
log.debug("cache miss for {}", entry.getUrl());
if (subscriptions == null) {
subscriptions = UnitOfWork.call(sessionFactory, () -> feedSubscriptionDAO.findByFeed(feed));
subscriptions = unitOfWork.call(() -> feedSubscriptionDAO.findByFeed(feed));
}
AddEntryResult addEntryResult = addEntry(feed, entry, subscriptions);
processed &= addEntryResult.processed;
@@ -204,7 +203,7 @@ public class FeedRefreshUpdater implements Managed {
feedUpdated.mark();
}
UnitOfWork.run(sessionFactory, () -> feedService.save(feed));
unitOfWork.run(() -> feedService.save(feed));
return processed;
}

View File

@@ -154,7 +154,7 @@ public class FeedUtils {
for (Emit emit : emits) {
int matchIndex = emit.getStart();
sb.append(source.substring(prevIndex, matchIndex));
sb.append(source, prevIndex, matchIndex);
sb.append(HtmlEntities.HTML_TO_NUMERIC_MAP.get(emit.getKeyword()));
prevIndex = emit.getEnd() + 1;
}
@@ -228,7 +228,7 @@ public class FeedUtils {
if (index == -1) {
return null;
}
String encoding = pi.substring(index + 10, pi.length());
String encoding = pi.substring(index + 10);
encoding = encoding.substring(0, encoding.indexOf('"'));
return encoding;
}

View File

@@ -15,7 +15,6 @@ import com.commafeed.backend.feed.FeedUtils;
import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedSubscriptionService;
import com.commafeed.backend.service.FeedSubscriptionService.FeedSubscriptionException;
import com.rometools.opml.feed.opml.Opml;
import com.rometools.opml.feed.opml.Outline;
import com.rometools.rome.io.FeedException;
@@ -78,8 +77,6 @@ public class OPMLImporter {
// make sure we continue with the import process even if a feed failed
try {
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent, position);
} catch (FeedSubscriptionException e) {
throw e;
} catch (Exception e) {
log.error("error while importing {}: {}", outline.getXmlUrl(), e.getMessage());
}

View File

@@ -23,11 +23,7 @@ public class OPML11Parser extends OPML10Parser {
public boolean isMyType(Document document) {
Element e = document.getRootElement();
if (e.getName().equals("opml")) {
return true;
}
return false;
return e.getName().equals("opml");
}

View File

@@ -6,8 +6,6 @@ import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.FeedEntryContentDAO;
import com.commafeed.backend.dao.FeedEntryDAO;
@@ -30,7 +28,7 @@ public class DatabaseCleaningService {
private static final int BATCH_SIZE = 100;
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
private final FeedDAO feedDAO;
private final FeedEntryDAO feedEntryDAO;
private final FeedEntryContentDAO feedEntryContentDAO;
@@ -42,16 +40,16 @@ public class DatabaseCleaningService {
int deleted = 0;
long entriesTotal = 0;
do {
List<Feed> feeds = UnitOfWork.call(sessionFactory, () -> feedDAO.findWithoutSubscriptions(1));
List<Feed> feeds = unitOfWork.call(() -> feedDAO.findWithoutSubscriptions(1));
for (Feed feed : feeds) {
int entriesDeleted = 0;
do {
entriesDeleted = UnitOfWork.call(sessionFactory, () -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE));
entriesDeleted = unitOfWork.call(() -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE));
entriesTotal += entriesDeleted;
log.info("removed {} entries for feeds without subscriptions", entriesTotal);
} while (entriesDeleted > 0);
}
deleted = UnitOfWork.call(sessionFactory, () -> feedDAO.delete(feeds));
deleted = unitOfWork.call(() -> feedDAO.delete(feeds));
total += deleted;
log.info("removed {} feeds without subscriptions", total);
} while (deleted != 0);
@@ -64,7 +62,7 @@ public class DatabaseCleaningService {
long total = 0;
int deleted = 0;
do {
deleted = UnitOfWork.call(sessionFactory, () -> feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE));
deleted = unitOfWork.call(() -> feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE));
total += deleted;
log.info("removed {} contents without entries", total);
} while (deleted != 0);
@@ -75,8 +73,7 @@ public class DatabaseCleaningService {
public long cleanEntriesForFeedsExceedingCapacity(final int maxFeedCapacity) {
long total = 0;
while (true) {
List<FeedCapacity> feeds = UnitOfWork.call(sessionFactory,
() -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, BATCH_SIZE));
List<FeedCapacity> feeds = unitOfWork.call(() -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, BATCH_SIZE));
if (feeds.isEmpty()) {
break;
}
@@ -85,8 +82,7 @@ public class DatabaseCleaningService {
long remaining = feed.getCapacity() - maxFeedCapacity;
do {
final long rem = remaining;
int deleted = UnitOfWork.call(sessionFactory,
() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(BATCH_SIZE, rem)));
int deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(BATCH_SIZE, rem)));
total += deleted;
remaining -= deleted;
log.info("removed {} entries for feeds exceeding capacity", total);
@@ -102,8 +98,7 @@ public class DatabaseCleaningService {
long total = 0;
int deleted = 0;
do {
deleted = UnitOfWork.call(sessionFactory,
() -> feedEntryStatusDAO.delete(feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE)));
deleted = unitOfWork.call(() -> feedEntryStatusDAO.delete(feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE)));
total += deleted;
log.info("removed {} old read statuses", total);
} while (deleted != 0);

View File

@@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
@Singleton
public class DatabaseStartupService implements Managed {
private final UnitOfWork unitOfWork;
private final SessionFactory sessionFactory;
private final UserDAO userDAO;
private final UserService userService;
@@ -35,9 +36,9 @@ public class DatabaseStartupService implements Managed {
@Override
public void start() {
updateSchema();
long count = UnitOfWork.call(sessionFactory, userDAO::count);
long count = unitOfWork.call(userDAO::count);
if (count == 0) {
UnitOfWork.run(sessionFactory, this::initialData);
unitOfWork.run(this::initialData);
}
}

View File

@@ -22,7 +22,7 @@ public class FeedService {
private final FeedDAO feedDAO;
private final Set<AbstractFaviconFetcher> faviconFetchers;
private Favicon defaultFavicon;
private final Favicon defaultFavicon;
@Inject
public FeedService(FeedDAO feedDAO, Set<AbstractFaviconFetcher> faviconFetchers) {

View File

@@ -57,6 +57,13 @@ public class FeedSubscriptionService {
throw new FeedSubscriptionException("Could not subscribe to a feed from this CommaFeed instance");
}
Integer maxFeedsPerUser = config.getApplicationSettings().getMaxFeedsPerUser();
if (maxFeedsPerUser > 0 && feedSubscriptionDAO.count(user) >= maxFeedsPerUser) {
String message = String.format("You cannot subscribe to more feeds on this CommaFeed instance (max %s feeds per user)",
maxFeedsPerUser);
throw new FeedSubscriptionException(message);
}
Feed feed = feedService.findOrCreate(url);
// upgrade feed to https if it was using http

View File

@@ -41,9 +41,9 @@ public class MailService {
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "" + settings.isSmtpTls());
props.put("mail.smtp.starttls.enable", String.valueOf(settings.isSmtpTls()));
props.put("mail.smtp.host", settings.getSmtpHost());
props.put("mail.smtp.port", "" + settings.getSmtpPort());
props.put("mail.smtp.port", String.valueOf(settings.getSmtpPort()));
Session session = Session.getInstance(props, new Authenticator() {
@Override

View File

@@ -17,7 +17,6 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.hibernate.SessionFactory;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.HttpGetter;
@@ -40,7 +39,7 @@ public class PubSubService {
private final CommaFeedConfiguration config;
private final FeedService feedService;
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
public void subscribe(Feed feed) {
String hub = feed.getPushHub();
@@ -75,7 +74,7 @@ public class PubSubService {
if (code == 400 && StringUtils.contains(message, pushpressError)) {
String[] tokens = message.split(" ");
feed.setPushTopic(tokens[tokens.length - 1]);
UnitOfWork.run(sessionFactory, () -> feedService.save(feed));
unitOfWork.run(() -> feedService.save(feed));
log.debug("handled pushpress subfeed {} : {}", topic, feed.getPushTopic());
} else {
throw new Exception(

View File

@@ -2,6 +2,7 @@ package com.commafeed.backend.service;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Optional;
import java.util.Set;
@@ -123,7 +124,7 @@ public class UserService {
}
public void createDemoUser() {
register(CommaFeedApplication.USERNAME_DEMO, "demo", "demo@commafeed.com", Arrays.asList(Role.USER), true);
register(CommaFeedApplication.USERNAME_DEMO, "demo", "demo@commafeed.com", Collections.singletonList(Role.USER), true);
}
public void unregister(User user) {

View File

@@ -8,6 +8,7 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.time.DateUtils;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedSubscriptionService;
@@ -20,6 +21,7 @@ public class PostLoginActivities {
private final UserDAO userDAO;
private final FeedSubscriptionService feedSubscriptionService;
private final UnitOfWork unitOfWork;
private final CommaFeedConfiguration config;
public void executeFor(User user) {
@@ -27,19 +29,26 @@ public class PostLoginActivities {
Date now = new Date();
boolean saveUser = false;
// only update lastLogin field every hour in order to not
// invalidate the cache every time someone logs in
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
user.setLastLogin(now);
saveUser = true;
}
if (config.getApplicationSettings().getHeavyLoad() && user.shouldRefreshFeedsAt(now)) {
if (Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad()) && user.shouldRefreshFeedsAt(now)) {
feedSubscriptionService.refreshAll(user);
user.setLastFullRefresh(now);
saveUser = true;
}
if (saveUser) {
userDAO.saveOrUpdate(user);
// Post login activites are susceptible to run for any webservice call.
// We update the user in a new transaction to update the user immediately.
// If we didn't and the webservice call takes time, subsequent webservice calls would have to wait for the first call to
// finish even if they didn't use the same database tables, because they updated the user too.
unitOfWork.run(() -> userDAO.saveOrUpdate(user));
}
}

View File

@@ -5,8 +5,6 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.CommaFeedApplication;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.UnitOfWork;
@@ -23,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
public class DemoAccountCleanupTask extends ScheduledTask {
private final CommaFeedConfiguration config;
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
private final UserDAO userDAO;
private final UserService userService;
@@ -34,7 +32,7 @@ public class DemoAccountCleanupTask extends ScheduledTask {
}
log.info("recreating demo user account");
UnitOfWork.run(sessionFactory, () -> {
unitOfWork.run(() -> {
User demoUser = userDAO.findByName(CommaFeedApplication.USERNAME_DEMO);
if (demoUser == null) {
return;

View File

@@ -14,8 +14,13 @@ import org.passay.PasswordValidator;
import org.passay.RuleResult;
import org.passay.WhitespaceRule;
import lombok.Setter;
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
@Setter
private static boolean strict = true;
@Override
public void initialize(ValidPassword constraintAnnotation) {
// nothing to do
@@ -27,7 +32,7 @@ public class PasswordConstraintValidator implements ConstraintValidator<ValidPas
return true;
}
PasswordValidator validator = buildPasswordValidator();
PasswordValidator validator = strict ? buildStrictPasswordValidator() : buildLoosePasswordValidator();
RuleResult result = validator.validate(new PasswordData(value));
if (result.isValid()) {
@@ -40,10 +45,10 @@ public class PasswordConstraintValidator implements ConstraintValidator<ValidPas
return false;
}
private PasswordValidator buildPasswordValidator() {
private PasswordValidator buildStrictPasswordValidator() {
return new PasswordValidator(
// length
new LengthRule(8, 128),
new LengthRule(8, 256),
// 1 uppercase char
new CharacterRule(EnglishCharacterData.UpperCase, 1),
// 1 lowercase char
@@ -56,4 +61,12 @@ public class PasswordConstraintValidator implements ConstraintValidator<ValidPas
new WhitespaceRule());
}
private PasswordValidator buildLoosePasswordValidator() {
return new PasswordValidator(
// length
new LengthRule(6, 256),
// no whitespace
new WhitespaceRule());
}
}

View File

@@ -21,8 +21,8 @@ import lombok.RequiredArgsConstructor;
@Singleton
public class SecurityCheckFactoryProvider extends AbstractValueParamProvider {
private UserService userService;
private HttpServletRequest request;
private final UserService userService;
private final HttpServletRequest request;
@Inject
public SecurityCheckFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider, UserService userService,

View File

@@ -31,7 +31,7 @@ public class Category implements Serializable {
@ApiModelProperty(value = "category feeds", required = true)
private List<Subscription> feeds = new ArrayList<>();
@ApiModelProperty(value = "wether the category is expanded or collapsed", required = true)
@ApiModelProperty(value = "whether the category is expanded or collapsed", required = true)
private boolean expanded;
@ApiModelProperty(value = "position of the category in the list", required = true)

View File

@@ -1,7 +1,7 @@
package com.commafeed.frontend.model;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@@ -45,7 +45,7 @@ public class Entry implements Serializable {
@ApiModelProperty(value = "comma-separated list of categories")
private String categories;
@ApiModelProperty(value = "wether entry content and title are rtl", required = true)
@ApiModelProperty(value = "whether entry content and title are rtl", required = true)
private boolean rtl;
@ApiModelProperty(value = "entry author")
@@ -99,7 +99,7 @@ public class Entry implements Serializable {
@ApiModelProperty(value = "starred status", required = true)
private boolean starred;
@ApiModelProperty(value = "wether the entry is still markable (old entry statuses are discarded)", required = true)
@ApiModelProperty(value = "whether the entry is still markable (old entry statuses are discarded)", required = true)
private boolean markable;
@ApiModelProperty(value = "tags", required = true)
@@ -158,13 +158,13 @@ public class Entry implements Serializable {
SyndContentImpl content = new SyndContentImpl();
content.setValue(getContent());
entry.setContents(Arrays.<SyndContent> asList(content));
entry.setContents(Collections.<SyndContent> singletonList(content));
if (getEnclosureUrl() != null) {
SyndEnclosureImpl enclosure = new SyndEnclosureImpl();
enclosure.setType(getEnclosureType());
enclosure.setUrl(getEnclosureUrl());
entry.setEnclosures(Arrays.<SyndEnclosure> asList(enclosure));
entry.setEnclosures(Collections.<SyndEnclosure> singletonList(enclosure));
}
entry.setLink(getUrl());

View File

@@ -104,9 +104,7 @@ public class CategoryREST {
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
@ApiParam(
value = "ordering",
allowableValues = "asc,desc,abc,zyx") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(value = "ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,

View File

@@ -3,7 +3,7 @@ package com.commafeed.frontend.resource;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
@@ -141,9 +141,7 @@ public class FeedREST {
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
@ApiParam(
value = "ordering",
allowableValues = "asc,desc,abc,zyx") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(value = "ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds) {
@@ -173,7 +171,7 @@ public class FeedREST {
entries.setErrorCount(subscription.getFeed().getErrorCount());
entries.setFeedLink(subscription.getFeed().getLink());
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, Arrays.asList(subscription), unreadOnly,
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, Collections.singletonList(subscription), unreadOnly,
entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null);
for (FeedEntryStatus status : list) {
@@ -325,7 +323,7 @@ public class FeedREST {
FeedSubscription subscription = feedSubscriptionDAO.findById(user, Long.valueOf(req.getId()));
if (subscription != null) {
feedEntryService.markSubscriptionEntries(user, Arrays.asList(subscription), olderThan, entryKeywords);
feedEntryService.markSubscriptionEntries(user, Collections.singletonList(subscription), olderThan, entryKeywords);
}
return Response.ok().build();
}
@@ -524,7 +522,7 @@ public class FeedREST {
return Response.status(Status.FORBIDDEN).entity("Import is disabled for the demo account").build();
}
try {
String opml = IOUtils.toString(input, "UTF-8");
String opml = IOUtils.toString(input, StandardCharsets.UTF_8);
opmlImporter.importOpml(user, opml);
} catch (Exception e) {
log.error(e.getMessage(), e);

View File

@@ -1,6 +1,6 @@
package com.commafeed.frontend.resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;
@@ -227,7 +227,8 @@ public class UserREST {
public Response registerUser(@Valid @ApiParam(required = true) RegistrationRequest req,
@Context @ApiParam(hidden = true) SessionHelper sessionHelper) {
try {
User registeredUser = userService.register(req.getName(), req.getPassword(), req.getEmail(), Arrays.asList(Role.USER));
User registeredUser = userService.register(req.getName(), req.getPassword(), req.getEmail(),
Collections.singletonList(Role.USER));
userService.login(req.getName(), req.getPassword());
sessionHelper.setLoggedInUser(registeredUser);
return Response.ok().build();

View File

@@ -7,8 +7,6 @@ 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;
@@ -22,7 +20,7 @@ abstract class AbstractCustomCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
private final UserSettingsDAO userSettingsDAO;
@Override
@@ -34,7 +32,7 @@ abstract class AbstractCustomCodeServlet extends HttpServlet {
return;
}
UserSettings settings = UnitOfWork.call(sessionFactory, () -> userSettingsDAO.findByUser(user.get()));
UserSettings settings = unitOfWork.call(() -> userSettingsDAO.findByUser(user.get()));
if (settings == null) {
return;
}

View File

@@ -2,8 +2,7 @@ package com.commafeed.frontend.servlet;
import javax.inject.Inject;
import org.hibernate.SessionFactory;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.UserSettings;
@@ -12,8 +11,8 @@ public class CustomCssServlet extends AbstractCustomCodeServlet {
private static final long serialVersionUID = 1L;
@Inject
public CustomCssServlet(SessionFactory sessionFactory, UserSettingsDAO userSettingsDAO) {
super(sessionFactory, userSettingsDAO);
public CustomCssServlet(UnitOfWork unitOfWork, UserSettingsDAO userSettingsDAO) {
super(unitOfWork, userSettingsDAO);
}
@Override

View File

@@ -3,8 +3,7 @@ package com.commafeed.frontend.servlet;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.UserSettings;
@@ -14,8 +13,8 @@ public class CustomJsServlet extends AbstractCustomCodeServlet {
private static final long serialVersionUID = 1L;
@Inject
public CustomJsServlet(SessionFactory sessionFactory, UserSettingsDAO userSettingsDAO) {
super(sessionFactory, userSettingsDAO);
public CustomJsServlet(UnitOfWork unitOfWork, UserSettingsDAO userSettingsDAO) {
super(unitOfWork, userSettingsDAO);
}
@Override

View File

@@ -12,7 +12,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SessionFactory;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.FeedCategoryDAO;
@@ -24,6 +23,7 @@ import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.service.FeedEntryService;
import com.commafeed.backend.service.UserService;
import com.commafeed.frontend.resource.CategoryREST;
import com.commafeed.frontend.session.SessionHelper;
@@ -39,11 +39,12 @@ public class NextUnreadServlet extends HttpServlet {
private static final String PARAM_CATEGORYID = "category";
private static final String PARAM_READINGORDER = "order";
private final SessionFactory sessionFactory;
private final UnitOfWork unitOfWork;
private final FeedSubscriptionDAO feedSubscriptionDAO;
private final FeedEntryStatusDAO feedEntryStatusDAO;
private final FeedCategoryDAO feedCategoryDAO;
private final UserService userService;
private final FeedEntryService feedEntryService;
private final CommaFeedConfiguration config;
@Override
@@ -54,7 +55,7 @@ public class NextUnreadServlet extends HttpServlet {
SessionHelper sessionHelper = new SessionHelper(req);
Optional<User> user = sessionHelper.getLoggedInUser();
if (user.isPresent()) {
UnitOfWork.run(sessionFactory, () -> userService.performPostLoginActivities(user.get()));
unitOfWork.run(() -> userService.performPostLoginActivities(user.get()));
}
if (!user.isPresent()) {
resp.sendRedirect(resp.encodeRedirectURL(config.getApplicationSettings().getPublicUrl()));
@@ -63,7 +64,7 @@ public class NextUnreadServlet extends HttpServlet {
final ReadingOrder order = StringUtils.equals(orderParam, "asc") ? ReadingOrder.asc : ReadingOrder.desc;
FeedEntryStatus status = UnitOfWork.call(sessionFactory, () -> {
FeedEntryStatus status = unitOfWork.call(() -> {
FeedEntryStatus s = null;
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user.get());
@@ -81,8 +82,7 @@ public class NextUnreadServlet extends HttpServlet {
}
}
if (s != null) {
s.setRead(true);
feedEntryStatusDAO.saveOrUpdate(s);
feedEntryService.markEntry(user.get(), s.getEntry().getId(), true);
}
return s;
});

View File

@@ -17,7 +17,7 @@ import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
@Singleton
public class SessionHelperFactoryProvider extends AbstractValueParamProvider {
private HttpServletRequest request;
private final HttpServletRequest request;
@Inject
public SessionHelperFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider, HttpServletRequest request) {

View File

@@ -7,7 +7,7 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class WebSocketMessageBuilder {
public static final String newFeedEntries(FeedSubscription subscription) {
public static String newFeedEntries(FeedSubscription subscription) {
return String.format("%s:%s", "new-feed-entries", subscription.getId());
}

View File

@@ -0,0 +1,47 @@
<?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="new-hibernate-id-generator" author="athou">
<modifyDataType tableName="hibernate_sequences" columnName="sequence_next_hi_value" newDataType="BIGINT" />
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from FEEDCATEGORIES)
where sequence_name = 'FEEDCATEGORIES'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from FEEDENTRIES)
where sequence_name = 'FEEDENTRIES'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from FEEDENTRYCONTENTS)
where sequence_name = 'FEEDENTRYCONTENTS'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from FEEDENTRYSTATUSES)
where sequence_name = 'FEEDENTRYSTATUSES'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from FEEDS)
where sequence_name = 'FEEDS'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from FEEDSUBSCRIPTIONS)
where sequence_name = 'FEEDSUBSCRIPTIONS'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from USERROLES)
where sequence_name = 'USERROLES'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from USERS)
where sequence_name = 'USERS'</sql>
<sql>update hibernate_sequences
set sequence_next_hi_value=
(select coalesce(max(id), 0) + 1 from USERSETTINGS)
where sequence_name = 'USERSETTINGS'</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -3,20 +3,21 @@
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">
<property name="blob_type" value="bytea" dbms="postgresql"/>
<property name="blob_type" value="blob" dbms="h2"/>
<property name="blob_type" value="blob" dbms="mysql,mariadb"/>
<property name="blob_type" value="blob" dbms="mssql"/>
<property name="blob_type" value="bytea" dbms="postgresql" />
<property name="blob_type" value="blob" dbms="h2" />
<property name="blob_type" value="blob" dbms="mysql,mariadb" />
<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" />
<include file="changelogs/db.changelog-3.2.xml" />
<include file="changelogs/db.changelog-3.5.xml" />
</databaseChangeLog>

View File

@@ -1,7 +1,6 @@
package com.commafeed.backend.service;
import org.apache.http.HttpHeaders;
import org.hibernate.SessionFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -16,6 +15,7 @@ import org.mockserver.model.HttpResponse;
import org.mockserver.model.MediaType;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.model.Feed;
@ExtendWith(MockServerExtension.class)
@@ -28,7 +28,7 @@ class PubSubServiceTest {
private FeedService feedService;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SessionFactory sessionFactory;
private UnitOfWork unitOfWork;
@Mock
private Feed feed;
@@ -43,7 +43,7 @@ class PubSubServiceTest {
this.client = client;
this.client.reset();
this.underTest = new PubSubService(config, feedService, sessionFactory);
this.underTest = new PubSubService(config, feedService, unitOfWork);
Integer port = client.getPort();
String hubUrl = String.format("http://localhost:%s/hub", port);
@@ -72,7 +72,7 @@ class PubSubServiceTest {
.withMethod("POST")
.withPath("/hub"));
Mockito.verify(feed, Mockito.never()).setPushTopic(Mockito.anyString());
Mockito.verifyNoInteractions(feedService);
Mockito.verifyNoInteractions(unitOfWork);
}
@Test
@@ -86,7 +86,7 @@ class PubSubServiceTest {
// Assert
Mockito.verify(feed).setPushTopic(Mockito.anyString());
Mockito.verify(feedService).save(feed);
Mockito.verify(unitOfWork).run(Mockito.any());
}
@Test
@@ -99,7 +99,7 @@ class PubSubServiceTest {
// Assert
Mockito.verify(feed, Mockito.never()).setPushTopic(Mockito.anyString());
Mockito.verifyNoInteractions(feedService);
Mockito.verifyNoInteractions(unitOfWork);
}
}

View File

@@ -1,6 +1,6 @@
package com.commafeed.frontend.resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import org.junit.jupiter.api.Test;
@@ -76,7 +76,7 @@ class UserRestTest {
userREST.registerUser(req, sessionHelper);
inOrder.verify(service).register("user", "password", "test@test.com", Arrays.asList(Role.USER));
inOrder.verify(service).register("user", "password", "test@test.com", Collections.singletonList(Role.USER));
inOrder.verify(service).login("user", "password");
}

View File

@@ -4,8 +4,11 @@ app:
# url used to access commafeed
publicUrl: http://localhost:8082/
# wether to allow user registrations
# whether to allow user registrations
allowRegistrations: true
# whether to enable strict password validation (1 uppercase char, 1 lowercase char, 1 digit, 1 special char)
strictPasswordPolicy: true
# create a demo account the first time the app starts
createDemoAccount: false
@@ -37,14 +40,14 @@ app:
graphitePort: 2003
graphiteInterval: 60
# wether this commafeed instance has a lot of feeds to refresh
# whether this commafeed instance has a lot of feeds to refresh
# leave this to false in almost all cases
heavyLoad: false
# minimum amount of time commafeed will wait before refreshing the same feed
refreshIntervalMinutes: 5
# wether to enable pubsub
# whether to enable pubsub
# probably not needed if refreshIntervalMinutes is low
pubsubhubbub: false
@@ -60,6 +63,9 @@ app:
# entries to keep per feed, old entries will be deleted, 0 to disable
maxFeedCapacity: 500
# limit the number of feeds a user can subscribe to, 0 to disable
maxFeedsPerUser: 0
# cache service to use, possible values are 'noop' and 'redis'
cache: noop
@@ -69,7 +75,7 @@ app:
# user-agent string that will be used by the http client, leave empty for the default one
userAgent:
# Database connection
# -------------------
# for MySQL
@@ -92,8 +98,8 @@ database:
properties:
charSet: UTF-8
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
logging:
level: INFO
loggers:

Some files were not shown because too many files have changed in this diff Show More