Compare commits

...

20 Commits
3.6.0 ... 3.7.0

Author SHA1 Message Date
Athou
4f699d9675 release 3.7.0 2023-06-20 09:18:18 +02:00
Athou
a5aba6f7ae content is no longer limited to 650px when sidebar is hidden (same as commafeed v2) (#1084) 2023-06-18 12:46:42 +02:00
Athou
78c8711a79 add tooltips to all relative dates with exact time 2023-06-17 22:53:07 +02:00
Athou
8325236d0e hide horizontal scrollbar (#1084) 2023-06-17 22:43:23 +02:00
Athou
437401e73f fix sidebar scrolling (#1084) 2023-06-17 08:37:41 +02:00
Athou
fa06d321d5 restore F shortcut to hide sidebar (#1084) 2023-06-16 21:49:08 +02:00
Athou
d1ddcb6ace resizeable tree (#1084) 2023-06-16 21:24:34 +02:00
Athou
6944d4dc0b fix unreadable api documentation page with dark theme (#1082) 2023-06-16 20:07:36 +02:00
Athou
c835d805b1 restore a version of findNextUpdatable that handles inactive users better than the one we removed a while ago 2023-06-16 15:27:39 +02:00
Athou
4a90e1f69d add some debugging 2023-06-16 13:14:37 +02:00
Athou
fcfeaa462e on user login and in heavy load mode, only force refresh feeds that are up for refresh 2023-06-16 13:14:37 +02:00
Athou
b16978d8fe position is now always set (#1076) 2023-06-15 21:12:10 +02:00
Athou
68c62b4528 no need to push the extension this much 2023-06-14 01:09:31 +02:00
Athou
18f68aab31 fallback to ctrl+click simulation if extension is not installed (#1074 #1075) 2023-06-14 01:03:59 +02:00
Athou
8abb2770ec fix release script, it's the CHANGELOG that needs to be updated 2023-06-13 11:15:29 +02:00
Athou
9156b8b6d0 add a setting to hide commafeed from search engines 2023-06-13 10:51:12 +02:00
Athou
2c32fa1e13 make "b" keyboard shortcut work in extension popup 2023-06-13 10:29:18 +02:00
Athou
7e48afe36c correctly detect the extension if the hook is not used on the initial page 2023-06-12 21:47:54 +02:00
Athou
cd94a3b56f update browser extension badge unread count 2023-06-12 20:54:40 +02:00
Athou
22e0f1f382 use browser extension to open tab in background (#1074) 2023-06-11 17:59:46 +02:00
69 changed files with 1002 additions and 407 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## [3.7.0]
- the sidebar is now resizable
- added the "f" keyboard shortcut to hide the sidebar
- added tooltips to relative dates with the exact date
- add a setting to hide commafeed from search engines (exposes a robots.txt file, enabled by default)
- the browser extension unread count now updates when articles are marked as read/unread in the app
- The "b" keyboard shortcut now works as expected on Chrome but requires the browser extension to be installed
- dark mode has been disabled on the api documentation page as it was unreadable
- improvement to the feed refresh queuing logic when "heavy load" mode is enabled
- fix a bug that could prevent feeds and categories from being edited
## [3.6.0] ## [3.6.0]
- add a button to open CommaFeed in a new tab and a button to open options when using the browser extension - add a button to open CommaFeed in a new tab and a button to open options when using the browser extension
@@ -26,7 +38,8 @@
- add divider to visually separate read-only information from form on the profile settings page - add divider to visually separate read-only information from form on the profile settings page
- reduce javascript bundle size by 30% by loading only the necessary translations - reduce javascript bundle size by 30% by loading only the necessary translations
- add a standalone donate page with all ways to support CommaFeed - add a standalone donate page with all ways to support CommaFeed
- fix an issue introduced in 3.1.0 that could make CommaFeed not refresh feeds as fast as before on instances with lots of feeds - fix an issue introduced in 3.1.0 that could make CommaFeed not refresh feeds as fast as before on instances with lots
of feeds
- fix alignment of icon with text for category tree nodes - fix alignment of icon with text for category tree nodes
- fix alignment of burger button with the rest of the header on mobile - fix alignment of burger button with the rest of the header on mobile
@@ -67,10 +80,10 @@
## [3.0.1] ## [3.0.1]
- allow env variable substitution in config.yml - allow env variable substitution in config.yml
- e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with - e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with
its value its value
- allow env variable prefixed with `CF_` to override config.yml properties - allow env variable prefixed with `CF_` to override config.yml properties
- e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true` - e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true`
## [3.0.0] ## [3.0.0]

View File

@@ -25,6 +25,7 @@
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"interweave": "^13.1.0", "interweave": "^13.1.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"re-resizable": "^6.9.9",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
@@ -10035,6 +10036,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/re-resizable": {
"version": "6.9.9",
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.9.tgz",
"integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==",
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",

View File

@@ -31,6 +31,7 @@
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"interweave": "^13.1.0", "interweave": "^13.1.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"re-resizable": "^6.9.9",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",

View File

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

View File

@@ -12,6 +12,7 @@ import { categoryUnreadCount } from "app/utils"
import { ErrorBoundary } from "components/ErrorBoundary" import { ErrorBoundary } from "components/ErrorBoundary"
import { Header } from "components/header/Header" import { Header } from "components/header/Header"
import { Tree } from "components/sidebar/Tree" import { Tree } from "components/sidebar/Tree"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useI18n } from "i18n" import { useI18n } from "i18n"
import { AdminUsersPage } from "pages/admin/AdminUsersPage" import { AdminUsersPage } from "pages/admin/AdminUsersPage"
import { MetricsPage } from "pages/admin/MetricsPage" import { MetricsPage } from "pages/admin/MetricsPage"
@@ -37,7 +38,7 @@ import useLocalStorage from "use-local-storage"
function Providers(props: { children: React.ReactNode }) { function Providers(props: { children: React.ReactNode }) {
const preferredColorScheme = useColorScheme() const preferredColorScheme = useColorScheme()
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>("color-scheme", preferredColorScheme) const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>("color-scheme", preferredColorScheme)
const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value || (colorScheme === "dark" ? "light" : "dark")) const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value ?? (colorScheme === "dark" ? "light" : "dark"))
return ( return (
<I18nProvider i18n={i18n}> <I18nProvider i18n={i18n}>
@@ -65,6 +66,9 @@ function Providers(props: { children: React.ReactNode }) {
const ApiDocumentationPage = React.lazy(() => import("pages/app/ApiDocumentationPage")) const ApiDocumentationPage = React.lazy(() => import("pages/app/ApiDocumentationPage"))
function AppRoutes() { function AppRoutes() {
const sidebarWidth = useAppSelector(state => state.tree.sidebarWidth)
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
return ( return (
<Routes> <Routes>
<Route path="/" element={<Navigate to={`/app/category/${Constants.categories.all.id}`} replace />} /> <Route path="/" element={<Navigate to={`/app/category/${Constants.categories.all.id}`} replace />} />
@@ -73,7 +77,7 @@ function AppRoutes() {
<Route path="register" element={<RegistrationPage />} /> <Route path="register" element={<RegistrationPage />} />
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} /> <Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
<Route path="api" element={<ApiDocumentationPage />} /> <Route path="api" element={<ApiDocumentationPage />} />
<Route path="app" element={<Layout header={<Header />} sidebar={<Tree />} />}> <Route path="app" element={<Layout header={<Header />} sidebar={<Tree />} sidebarWidth={sidebarVisible ? sidebarWidth : 0} />}>
<Route path="category"> <Route path="category">
<Route path=":id" element={<FeedEntriesPage sourceType="category" />} /> <Route path=":id" element={<FeedEntriesPage sourceType="category" />} />
<Route path=":id/details" element={<CategoryDetailsPage />} /> <Route path=":id/details" element={<CategoryDetailsPage />} />
@@ -134,13 +138,28 @@ function FaviconHandler() {
const root = useAppSelector(state => state.tree.rootCategory) const root = useAppSelector(state => state.tree.rootCategory)
useEffect(() => { useEffect(() => {
const unreadCount = categoryUnreadCount(root) const unreadCount = categoryUnreadCount(root)
if (unreadCount === 0) Tinycon.reset() if (unreadCount === 0) {
else Tinycon.setBubble(unreadCount) Tinycon.reset()
} else {
Tinycon.setBubble(unreadCount)
}
}, [root]) }, [root])
return null return null
} }
function BrowserExtensionBadgeUnreadCountHandler() {
const root = useAppSelector(state => state.tree.rootCategory)
const { setBadgeUnreadCount } = useBrowserExtension()
useEffect(() => {
if (!root) return
const unreadCount = categoryUnreadCount(root)
setBadgeUnreadCount(unreadCount)
}, [root, setBadgeUnreadCount])
return null
}
export function App() { export function App() {
useI18n() useI18n()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@@ -153,6 +172,7 @@ export function App() {
<Providers> <Providers>
<> <>
<FaviconHandler /> <FaviconHandler />
<BrowserExtensionBadgeUnreadCountHandler />
<HashRouter> <HashRouter>
<GoogleAnalyticsHandler /> <GoogleAnalyticsHandler />
<RedirectHandler /> <RedirectHandler />

View File

@@ -88,7 +88,6 @@ export const Constants = {
layout: { layout: {
mobileBreakpoint: DEFAULT_THEME.breakpoints.md, mobileBreakpoint: DEFAULT_THEME.breakpoints.md,
headerHeight: 60, headerHeight: 60,
sidebarWidth: 350,
entryMaxWidth: 650, entryMaxWidth: 650,
isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight, isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight,
isBottomVisible: (div: HTMLElement) => div.getBoundingClientRect().bottom <= window.innerHeight, isBottomVisible: (div: HTMLElement) => div.getBoundingClientRect().bottom <= window.innerHeight,
@@ -97,5 +96,6 @@ export const Constants = {
mainScrollAreaId: "main-scroll-area-id", mainScrollAreaId: "main-scroll-area-id",
entryId: (entry: Entry) => `entry-id-${entry.id}`, entryId: (entry: Entry) => `entry-id-${entry.id}`,
}, },
browserExtensionUrl: "https://github.com/Athou/commafeed-browser-extension",
bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e", bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e",
} }

View File

@@ -9,10 +9,14 @@ import { redirectTo } from "./redirect"
interface TreeState { interface TreeState {
rootCategory?: Category rootCategory?: Category
mobileMenuOpen: boolean mobileMenuOpen: boolean
sidebarWidth: number
sidebarVisible: boolean
} }
const initialState: TreeState = { const initialState: TreeState = {
mobileMenuOpen: false, mobileMenuOpen: false,
sidebarWidth: 350,
sidebarVisible: true,
} }
export const reloadTree = createAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data)) export const reloadTree = createAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data))
@@ -27,6 +31,12 @@ export const treeSlice = createSlice({
setMobileMenuOpen: (state, action: PayloadAction<boolean>) => { setMobileMenuOpen: (state, action: PayloadAction<boolean>) => {
state.mobileMenuOpen = action.payload state.mobileMenuOpen = action.payload
}, },
setSidebarWidth: (state, action: PayloadAction<number>) => {
state.sidebarWidth = action.payload
},
toggleSidebar: state => {
state.sidebarVisible = !state.sidebarVisible
},
}, },
extraReducers: builder => { extraReducers: builder => {
builder.addCase(reloadTree.fulfilled, (state, action) => { builder.addCase(reloadTree.fulfilled, (state, action) => {
@@ -54,5 +64,5 @@ export const treeSlice = createSlice({
}, },
}) })
export const { setMobileMenuOpen } = treeSlice.actions export const { setMobileMenuOpen, setSidebarWidth, toggleSidebar } = treeSlice.actions
export default treeSlice.reducer export default treeSlice.reducer

View File

@@ -271,7 +271,7 @@ export interface Subscription {
iconUrl: string iconUrl: string
unread: number unread: number
categoryId?: string categoryId?: string
position?: number position: number
newestItemTime?: number newestItemTime?: number
filter?: string filter?: string
} }

View File

@@ -52,17 +52,4 @@ export const scrollToWithCallback = ({
element.scrollTo(options) element.scrollTo(options)
} }
export const openLinkInBackgroundTab = (url: string) => {
// simulate ctrl+click to open tab in background
const a = document.createElement("a")
a.href = url
a.rel = "noreferrer"
a.dispatchEvent(
new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
})
)
}
export const truncate = (str: string, n: number) => (str.length > n ? `${str.slice(0, n - 1)}\u2026` : str) export const truncate = (str: string, n: number) => (str.length > n ? `${str.slice(0, n - 1)}\u2026` : str)

View File

@@ -1,183 +1,200 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Kbd, Table } from "@mantine/core" import { Anchor, Box, Kbd, Stack, Table } from "@mantine/core"
import { Constants } from "app/constants"
export function KeyboardShortcutsHelp() { export function KeyboardShortcutsHelp() {
return ( return (
<Table striped highlightOnHover> <Stack spacing="xs">
<tbody> <Table striped highlightOnHover>
<tr> <tbody>
<td> <tr>
<Trans>Refresh</Trans> <td>
</td> <Trans>Refresh</Trans>
<td> </td>
<Kbd>R</Kbd> <td>
</td> <Kbd>R</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Open next entry</Trans> <td>
</td> <Trans>Open next entry</Trans>
<td> </td>
<Kbd>J</Kbd> <td>
</td> <Kbd>J</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Open previous entry</Trans> <td>
</td> <Trans>Open previous entry</Trans>
<td> </td>
<Kbd>K</Kbd> <td>
</td> <Kbd>K</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Set focus on next entry without opening it</Trans> <td>
</td> <Trans>Set focus on next entry without opening it</Trans>
<td> </td>
<Kbd>N</Kbd> <td>
</td> <Kbd>N</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Set focus on previous entry without opening it</Trans> <td>
</td> <Trans>Set focus on previous entry without opening it</Trans>
<td> </td>
<Kbd>P</Kbd> <td>
</td> <Kbd>P</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Move the page down</Trans> <td>
</td> <Trans>Move the page down</Trans>
<td> </td>
<Kbd> <td>
<Trans>Space</Trans> <Kbd>
</Kbd> <Trans>Space</Trans>
</td> </Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Move the page up</Trans> <td>
</td> <Trans>Move the page up</Trans>
<td> </td>
<Kbd> <td>
<Trans>Shift</Trans> <Kbd>
</Kbd> <Trans>Shift</Trans>
<span> + </span> </Kbd>
<Kbd> <span> + </span>
<Trans>Space</Trans> <Kbd>
</Kbd> <Trans>Space</Trans>
</td> </Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Open/close current entry</Trans> <td>
</td> <Trans>Open/close current entry</Trans>
<td> </td>
<Kbd>O</Kbd> <td>
<span>, </span> <Kbd>O</Kbd>
<Kbd> <span>, </span>
<Trans>Enter</Trans> <Kbd>
</Kbd> <Trans>Enter</Trans>
</td> </Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Open current entry in a new tab</Trans> <td>
</td> <Trans>Open current entry in a new tab</Trans>
<td> </td>
<Kbd>V</Kbd> <td>
</td> <Kbd>V</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Open current entry in a new tab in the background</Trans> <td>
</td> <Trans>Open current entry in a new tab in the background</Trans>
<td> </td>
<Kbd>B</Kbd> <td>
<span>, </span> <Kbd>B</Kbd>
<Kbd> <span>*, </span>
<Trans>Middle click</Trans> <Kbd>
</Kbd> <Trans>Middle click</Trans>
</td> </Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Toggle read status of current entry</Trans> <td>
</td> <Trans>Toggle read status of current entry</Trans>
<td> </td>
<Kbd>M</Kbd> <td>
<span>, </span> <Kbd>M</Kbd>
<Trans>Swipe header to the right</Trans> <span>, </span>
</td> <Trans>Swipe header to the right</Trans>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Mark all entries as read</Trans> <td>
</td> <Trans>Mark all entries as read</Trans>
<td> </td>
<Kbd> <td>
<Trans>Shift</Trans> <Kbd>
</Kbd> <Trans>Shift</Trans>
<span> + </span> </Kbd>
<Kbd>A</Kbd> <span> + </span>
</td> <Kbd>A</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Go to the All view</Trans> <td>
</td> <Trans>Go to the All view</Trans>
<td> </td>
<Kbd>G</Kbd> <td>
<span> </span> <Kbd>G</Kbd>
<Kbd>A</Kbd> <span> </span>
</td> <Kbd>A</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Navigate to a subscription by entering its name</Trans> <td>
</td> <Trans>Navigate to a subscription by entering its name</Trans>
<td> </td>
<Kbd> <td>
<Trans>Ctrl</Trans> <Kbd>
</Kbd> <Trans>Ctrl</Trans>
<span> + </span> </Kbd>
<Kbd>K</Kbd> <span> + </span>
<span>, </span> <Kbd>K</Kbd>
<Kbd>G</Kbd> <span>, </span>
<span> </span> <Kbd>G</Kbd>
<Kbd>U</Kbd> <span> </span>
</td> <Kbd>U</Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Show entry menu (desktop)</Trans> <td>
</td> <Trans>Show entry menu (desktop)</Trans>
<td> </td>
<Kbd> <td>
<Trans>Right click</Trans> <Kbd>
</Kbd> <Trans>Right click</Trans>
</td> </Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Show entry menu (mobile)</Trans> <td>
</td> <Trans>Show entry menu (mobile)</Trans>
<td> </td>
<Kbd> <td>
<Trans>Long press</Trans> <Kbd>
</Kbd> <Trans>Long press</Trans>
</td> </Kbd>
</tr> </td>
<tr> </tr>
<td> <tr>
<Trans>Show keyboard shortcut help</Trans> <td>
</td> <Trans>Toggle sidebar</Trans>
<td> </td>
<Kbd>?</Kbd> <td>
</td> <Kbd>F</Kbd>
</tr> </td>
</tbody> </tr>
</Table> <tr>
<td>
<Trans>Show keyboard shortcut help</Trans>
</td>
<td>
<Kbd>?</Kbd>
</td>
</tr>
</tbody>
</Table>
<Box>
<span>* </span>
<Anchor href={Constants.browserExtensionUrl} target="_blank" rel="noreferrer">
<Trans>Browser extension required for Chrome</Trans>
</Anchor>
</Box>
</Stack>
) )
} }

View File

@@ -1,4 +1,5 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Tooltip } from "@mantine/core"
import dayjs from "dayjs" import dayjs from "dayjs"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
@@ -10,5 +11,10 @@ export function RelativeDate(props: { date: Date | number | undefined }) {
}, []) }, [])
if (!props.date) return <Trans>N/A</Trans> if (!props.date) return <Trans>N/A</Trans>
return <>{dayjs(props.date).from(dayjs(now))}</> const date = dayjs(props.date)
return (
<Tooltip label={date.toString()} openDelay={500}>
<span>{date.from(dayjs(now))}</span>
</Tooltip>
)
} }

View File

@@ -12,10 +12,11 @@ import {
selectPreviousEntry, selectPreviousEntry,
} from "app/slices/entries" } from "app/slices/entries"
import { redirectToRootCategory } from "app/slices/redirect" import { redirectToRootCategory } from "app/slices/redirect"
import { toggleSidebar } from "app/slices/tree"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { openLinkInBackgroundTab } from "app/utils"
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMousetrap } from "hooks/useMousetrap" import { useMousetrap } from "hooks/useMousetrap"
import { useViewMode } from "hooks/useViewMode" import { useViewMode } from "hooks/useViewMode"
import { useEffect } from "react" import { useEffect } from "react"
@@ -29,10 +30,12 @@ export function FeedEntries() {
const entriesTimestamp = useAppSelector(state => state.entries.timestamp) const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId) const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
const hasMore = useAppSelector(state => state.entries.hasMore) const hasMore = useAppSelector(state => state.entries.hasMore)
const { viewMode } = useViewMode()
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks) const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry) const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
const { viewMode } = useViewMode()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { openLinkInBackgroundTab } = useBrowserExtension()
const selectedEntry = entries.find(e => e.id === selectedEntryId) const selectedEntry = entries.find(e => e.id === selectedEntryId)
@@ -211,7 +214,6 @@ export function FeedEntries() {
window.open(selectedEntry.url, "_blank", "noreferrer") window.open(selectedEntry.url, "_blank", "noreferrer")
}) })
useMousetrap("b", () => { useMousetrap("b", () => {
// simulate ctrl+click to open tab in background
if (!selectedEntry) return if (!selectedEntry) return
openLinkInBackgroundTab(selectedEntry.url) openLinkInBackgroundTab(selectedEntry.url)
}) })
@@ -234,6 +236,7 @@ export function FeedEntries() {
) )
}) })
useMousetrap("g a", () => dispatch(redirectToRootCategory())) useMousetrap("g a", () => dispatch(redirectToRootCategory()))
useMousetrap("f", () => dispatch(toggleSidebar()))
useMousetrap("?", () => useMousetrap("?", () =>
openModal({ openModal({
title: <Trans>Keyboard shortcuts</Trans>, title: <Trans>Keyboard shortcuts</Trans>,
@@ -265,6 +268,7 @@ export function FeedEntries() {
expanded={!!entry.expanded || viewMode === "expanded"} expanded={!!entry.expanded || viewMode === "expanded"}
selected={entry.id === selectedEntryId} selected={entry.id === selectedEntryId}
showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")} showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")}
maxWidth={sidebarVisible ? Constants.layout.entryMaxWidth : undefined}
onHeaderClick={event => headerClicked(entry, event)} onHeaderClick={event => headerClicked(entry, event)}
/> />
</div> </div>

View File

@@ -18,21 +18,31 @@ interface FeedEntryProps {
expanded: boolean expanded: boolean
selected: boolean selected: boolean
showSelectionIndicator: boolean showSelectionIndicator: boolean
maxWidth?: number
onHeaderClick: (e: React.MouseEvent) => void onHeaderClick: (e: React.MouseEvent) => void
} }
const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: ViewMode }) => { const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: ViewMode }) => {
let backgroundColor let backgroundColor
if (theme.colorScheme === "dark") backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5] if (theme.colorScheme === "dark") {
else backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit" backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5]
} else {
backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit"
}
let marginY = 10 let marginY = 10
if (props.viewMode === "title") marginY = 2 if (props.viewMode === "title") {
else if (props.viewMode === "cozy") marginY = 6 marginY = 2
} else if (props.viewMode === "cozy") {
marginY = 6
}
let mobileMarginY = 6 let mobileMarginY = 6
if (props.viewMode === "title") mobileMarginY = 2 if (props.viewMode === "title") {
else if (props.viewMode === "cozy") mobileMarginY = 4 mobileMarginY = 2
} else if (props.viewMode === "cozy") {
mobileMarginY = 4
}
let backgroundHoverColor = backgroundColor let backgroundHoverColor = backgroundColor
if (!props.expanded && !props.entry.read) { if (!props.expanded && !props.entry.read) {
@@ -59,7 +69,7 @@ const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: View
textDecoration: "none", textDecoration: "none",
}, },
body: { body: {
maxWidth: Constants.layout.entryMaxWidth, maxWidth: props.maxWidth ?? "100%",
}, },
} }
@@ -87,12 +97,18 @@ export function FeedEntry(props: FeedEntryProps) {
if (viewMode === "title" || viewMode === "cozy") paddingX = 6 if (viewMode === "title" || viewMode === "cozy") paddingX = 6
let paddingY: MantineNumberSize = "xs" let paddingY: MantineNumberSize = "xs"
if (viewMode === "title") paddingY = 4 if (viewMode === "title") {
else if (viewMode === "cozy") paddingY = 8 paddingY = 4
} else if (viewMode === "cozy") {
paddingY = 8
}
let borderRadius: MantineNumberSize = "sm" let borderRadius: MantineNumberSize = "sm"
if (viewMode === "title") borderRadius = 0 if (viewMode === "title") {
else if (viewMode === "cozy") borderRadius = "xs" borderRadius = 0
} else if (viewMode === "cozy") {
borderRadius = "xs"
}
const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy") const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy")
return ( return (

View File

@@ -5,7 +5,8 @@ import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
import { redirectToFeed } from "app/slices/redirect" import { redirectToFeed } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types" import { Entry } from "app/types"
import { openLinkInBackgroundTab, truncate } from "app/utils" import { truncate } from "app/utils"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useEffect } from "react" import { useEffect } from "react"
import { Item, Menu, Separator, useContextMenu } from "react-contexify" import { Item, Menu, Separator, useContextMenu } from "react-contexify"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
@@ -34,6 +35,7 @@ export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const { classes, theme } = useStyles() const { classes, theme } = useStyles()
const sourceType = useAppSelector(state => state.entries.source.type) const sourceType = useAppSelector(state => state.entries.source.type)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { openLinkInBackgroundTab } = useBrowserExtension()
return ( return (
<Menu id={menuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}> <Menu id={menuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}>

View File

@@ -23,7 +23,7 @@ export function Header() {
const settings = useAppSelector(state => state.user.settings) const settings = useAppSelector(state => state.user.settings)
const profile = useAppSelector(state => state.user.profile) const profile = useAppSelector(state => state.user.profile)
const searchFromStore = useAppSelector(state => state.entries.search) const searchFromStore = useAppSelector(state => state.entries.search)
const { isBrowserExtension, openSettingsPage, openAppInNewTab } = useBrowserExtension() const { isBrowserExtensionPopup, openSettingsPage, openAppInNewTab } = useBrowserExtension()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const searchForm = useForm<{ search: string }>({ const searchForm = useForm<{ search: string }>({
@@ -90,7 +90,7 @@ export function Header() {
<ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} /> <ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} />
{isBrowserExtension && ( {isBrowserExtensionPopup && (
<> <>
<HeaderDivider /> <HeaderDivider />

View File

@@ -1,9 +1,64 @@
import { useEffect, useState } from "react"
export const useBrowserExtension = () => { export const useBrowserExtension = () => {
// the extension will set the "browser-extension-installed" attribute on the root element
const [browserExtensionVersion, setBrowserExtensionVersion] = useState(
document.documentElement.getAttribute("browser-extension-installed")
)
// monitor the attribute on the root element as it may change after the page was loaded
useEffect(() => {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === "attributes") {
const element = mutation.target as Element
const version = element.getAttribute("browser-extension-installed")
if (version) setBrowserExtensionVersion(version)
}
})
})
observer.observe(document.documentElement, {
attributes: true,
})
return () => observer.disconnect()
}, [])
// when not in an iframe, window.parent is a reference to window // when not in an iframe, window.parent is a reference to window
const isBrowserExtension = window.parent !== window const isBrowserExtensionPopup = window.parent !== window
const isBrowserExtensionInstalled = isBrowserExtensionPopup || !!browserExtensionVersion
const isBrowserExtensionInstallable = !isBrowserExtensionPopup
const openSettingsPage = () => window.parent.postMessage("open-settings-page", "*") const w = isBrowserExtensionPopup ? window.parent : window
const openAppInNewTab = () => window.parent.postMessage("open-app-in-new-tab", "*") const openSettingsPage = () => w.postMessage("open-settings-page", "*")
const openAppInNewTab = () => w.postMessage("open-app-in-new-tab", "*")
const openLinkInBackgroundTab = (url: string) => {
if (isBrowserExtensionInstalled) {
w.postMessage(`open-link-in-background-tab:${url}`, "*")
} else {
// fallback to ctrl+click simulation
const a = document.createElement("a")
a.href = url
a.rel = "noreferrer"
a.dispatchEvent(
new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
})
)
}
}
const setBadgeUnreadCount = (count: number) => w.postMessage(`set-badge-unread-count:${count}`, "*")
return { isBrowserExtension, openSettingsPage, openAppInNewTab } return {
browserExtensionVersion,
isBrowserExtensionInstallable,
isBrowserExtensionInstalled,
isBrowserExtensionPopup,
openSettingsPage,
openAppInNewTab,
openLinkInBackgroundTab,
setBadgeUnreadCount,
}
} }

View File

@@ -127,9 +127,13 @@ msgstr "العودة"
msgid "Back to log in" msgid "Back to log in"
msgstr "العودة لتسجيل الدخول" msgstr "العودة لتسجيل الدخول"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "ملحقات المستعرض" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "سيؤدي تغيير كلمة المرور إلى إنشاء مفتاح
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "تأكد من عمل الخلاصة" msgstr "تأكد من عمل الخلاصة"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed التالي العنصر غير المقروء" msgstr "CommaFeed التالي العنصر غير المقروء"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "إصدار CommaFeed {الإصدار} ({مراجعة})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "الموضوع"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "تبديل قراءة حالة الإدخال الحالي" msgstr "تبديل قراءة حالة الإدخال الحالي"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي" msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي"

View File

@@ -127,9 +127,13 @@ msgstr "Enrere"
msgid "Back to log in" msgid "Back to log in"
msgstr "Tornar a iniciar sessió" msgstr "Tornar a iniciar sessió"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Extensions del navegador" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Canviar la contrasenya generarà una nova clau d'API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Comproveu que el canal funciona" msgstr "Comproveu que el canal funciona"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed següent element no llegit" msgstr "CommaFeed següent element no llegit"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Versió CommaFeed {versió} ({revisió})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Canvia l'estat de lectura de l'entrada actual" msgstr "Canvia l'estat de lectura de l'entrada actual"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo" msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Zpět"
msgid "Back to log in" msgid "Back to log in"
msgstr "Zpět k přihlášení" msgstr "Zpět k přihlášení"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Rozšíření prohlížeče" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Změna hesla vygeneruje nový klíč API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Zkontrolujte, zda zdroj funguje" msgstr "Zkontrolujte, zda zdroj funguje"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed další nepřečtená položka" msgstr "CommaFeed další nepřečtená položka"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed verze {version} ({revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Téma"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Přepne stav čtení aktuálního záznamu" msgstr "Přepne stav čtení aktuálního záznamu"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo" msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Yn ôl"
msgid "Back to log in" msgid "Back to log in"
msgstr "Yn ôl i fewngofnodi" msgstr "Yn ôl i fewngofnodi"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Estyniadau porwr" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Bydd newid cyfrinair yn cynhyrchu allwedd API newydd"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Gwiriwch fod y porthiant yn gweithio" msgstr "Gwiriwch fod y porthiant yn gweithio"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed eitem nesaf heb ei darllen" msgstr "CommaFeed eitem nesaf heb ei darllen"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Fersiwn ComaFeed {fersiwn} ({ adolygu})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Thema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Toglo statws darllen y cofnod cyfredol" msgstr "Toglo statws darllen y cofnod cyfredol"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo" msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo"

View File

@@ -127,9 +127,13 @@ msgstr "Tilbage"
msgid "Back to log in" msgid "Back to log in"
msgstr "Tilbage for at logge ind" msgstr "Tilbage for at logge ind"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Browserudvidelser" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,12 +167,16 @@ msgstr "Ændring af adgangskode vil generere en ny API-nøgle"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Tjek, at foderet virker" msgstr "Tjek, at foderet virker"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed næste ulæste element" msgstr "CommaFeed næste ulæste element"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Skift læsestatus for den aktuelle post" msgstr "Skift læsestatus for den aktuelle post"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prøv CommaFeed med demokontoen: demo/demo" msgstr "Prøv CommaFeed med demokontoen: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Zurück"
msgid "Back to log in" msgid "Back to log in"
msgstr "Zurück zum Anmelden" msgstr "Zurück zum Anmelden"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Browsererweiterungen" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Das Ändern des Passworts generiert einen neuen API-Schlüssel"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Überprüfen Sie, ob der Feed funktioniert" msgstr "Überprüfen Sie, ob der Feed funktioniert"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed nächstes ungelesenes Element" msgstr "CommaFeed nächstes ungelesenes Element"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed-Version {Version} ({Revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Thema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Lesestatus des aktuellen Eintrags umschalten" msgstr "Lesestatus des aktuellen Eintrags umschalten"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo" msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Back"
msgid "Back to log in" msgid "Back to log in"
msgstr "Back to log in" msgstr "Back to log in"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr "Browser extension required for Chrome"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Browser extentions" msgstr "Browser extention"
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Changing password will generate a new API key"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Check that the feed is working" msgstr "Check that the feed is working"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "CommaFeed browser extension version {browserExtensionVersion}."
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed next unread item" msgstr "CommaFeed next unread item"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed version {version} ({revision})" msgstr "CommaFeed version {version} ({revision})."
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Theme"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Toggle read status of current entry" msgstr "Toggle read status of current entry"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr "Toggle sidebar"
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Try out CommaFeed with the demo account: demo/demo" msgstr "Try out CommaFeed with the demo account: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Atrás"
msgid "Back to log in" msgid "Back to log in"
msgstr "Volver a iniciar sesión" msgstr "Volver a iniciar sesión"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Extensiones del navegador" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Cambiar la contraseña generará una nueva clave API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Compruebe que el feed funciona" msgstr "Compruebe que el feed funciona"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed siguiente elemento no leído" msgstr "CommaFeed siguiente elemento no leído"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "versión de CommaFeed {versión} ({revisión})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Alternar estado de lectura de la entrada actual" msgstr "Alternar estado de lectura de la entrada actual"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo" msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "برگشت"
msgid "Back to log in" msgid "Back to log in"
msgstr "بازگشت برای ورود به سیستم" msgstr "بازگشت برای ورود به سیستم"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "گسترش مرورگر" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "تغییر رمز عبور یک کلید API جدید ایجاد می ک
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "بررسی کنید که خوراک کار می کند" msgstr "بررسی کنید که خوراک کار می کند"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "مورد خوانده نشده بعدی CommaFeed" msgstr "مورد خوانده نشده بعدی CommaFeed"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "نسخه {نسخه} CommaFeed ({نسخه})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "تم"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید" msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو" msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو"

View File

@@ -127,9 +127,13 @@ msgstr "Takaisin"
msgid "Back to log in" msgid "Back to log in"
msgstr "Takaisin sisäänkirjautumiseen" msgstr "Takaisin sisäänkirjautumiseen"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Selaimen laajennukset" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Salasanan vaihtaminen luo uuden API-avaimen"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Tarkista, että syöttö toimii" msgstr "Tarkista, että syöttö toimii"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed seuraava lukematon kohde" msgstr "CommaFeed seuraava lukematon kohde"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed-versio {version} ({versio})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Teema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Vaihda nykyisen merkinnän lukutila" msgstr "Vaihda nykyisen merkinnän lukutila"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Kokeile CommaFeediä demotilillä: demo/demo" msgstr "Kokeile CommaFeediä demotilillä: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Retour"
msgid "Back to log in" msgid "Back to log in"
msgstr "Retour à la connexion" msgstr "Retour à la connexion"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Extensions pour navigateurs" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Changer de mot de passe générera une nouvelle clé API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Vérifie que le flux fonctionne" msgstr "Vérifie que le flux fonctionne"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed prochain article non lu" msgstr "CommaFeed prochain article non lu"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed version {version} ({revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Thème"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Marquer l'entrée actuelle comme lue/non lue" msgstr "Marquer l'entrée actuelle comme lue/non lue"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo" msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Atrás"
msgid "Back to log in" msgid "Back to log in"
msgstr "Volver para iniciar sesión" msgstr "Volver para iniciar sesión"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Extensións do navegador" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "O cambio de contrasinal xerará unha nova clave de API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Comproba que a fonte funciona" msgstr "Comproba que a fonte funciona"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed seguinte elemento non lido" msgstr "CommaFeed seguinte elemento non lido"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Versión de CommaFeed {versión} ({revisión})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "alternar o estado de lectura da entrada actual" msgstr "alternar o estado de lectura da entrada actual"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Proba CommaFeed coa conta de demostración: demo/demo" msgstr "Proba CommaFeed coa conta de demostración: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Vissza"
msgid "Back to log in" msgid "Back to log in"
msgstr "Vissza a bejelentkezéshez" msgstr "Vissza a bejelentkezéshez"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Böngészőbővítések" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "A jelszó megváltoztatása új API-kulcsot generál"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Ellenőrizze, hogy a feed működik-e" msgstr "Ellenőrizze, hogy a feed működik-e"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed következő olvasatlan elem" msgstr "CommaFeed következő olvasatlan elem"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed verzió {version} ({revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Téma"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Az aktuális bejegyzés olvasási állapotának váltása" msgstr "Az aktuális bejegyzés olvasási állapotának váltása"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo" msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Kembali"
msgid "Back to log in" msgid "Back to log in"
msgstr "Kembali untuk masuk" msgstr "Kembali untuk masuk"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Ekstensi peramban" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Mengubah kata sandi akan menghasilkan kunci API baru"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Periksa apakah umpannya berfungsi" msgstr "Periksa apakah umpannya berfungsi"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed item yang belum dibaca berikutnya" msgstr "CommaFeed item yang belum dibaca berikutnya"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed versi {versi} ({revisi})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Beralih status baca entri saat ini" msgstr "Beralih status baca entri saat ini"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Cobalah CommaFeed dengan akun demo: demo/demo" msgstr "Cobalah CommaFeed dengan akun demo: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Indietro"
msgid "Back to log in" msgid "Back to log in"
msgstr "Torna per accedere" msgstr "Torna per accedere"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Estensioni del browser" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "La modifica della password genererà una nuova chiave API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Verifica che il feed funzioni" msgstr "Verifica che il feed funzioni"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed successivo elemento non letto" msgstr "CommaFeed successivo elemento non letto"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Versione CommaFeed {versione} ({revisione})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Commuta lo stato di lettura della voce corrente" msgstr "Commuta lo stato di lettura della voce corrente"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prova CommaFeed con il conto demo: demo/demo" msgstr "Prova CommaFeed con il conto demo: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "裏"
msgid "Back to log in" msgid "Back to log in"
msgstr "ログインに戻る" msgstr "ログインに戻る"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "ブラウザ拡張機能" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "パスワードを変更すると、新しい API キーが生成され
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "フィードが動作していることを確認してください" msgstr "フィードが動作していることを確認してください"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "次の未読アイテムをカンマフィード" msgstr "次の未読アイテムをカンマフィード"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "コンマフィードのバージョン {version} ({revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "テーマ"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "現在のエントリの読み取りステータスを切り替えます" msgstr "現在のエントリの読み取りステータスを切り替えます"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "デモアカウントで CommaFeed を試す: demo/demo" msgstr "デモアカウントで CommaFeed を試す: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "뒤로"
msgid "Back to log in" msgid "Back to log in"
msgstr "로그인으로 돌아가기" msgstr "로그인으로 돌아가기"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "브라우저 확장" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "비밀번호를 변경하면 새 API 키가 생성됩니다."
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "피드가 작동하는지 확인" msgstr "피드가 작동하는지 확인"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "다음 읽지 않은 항목을 쉼표로 피드" msgstr "다음 읽지 않은 항목을 쉼표로 피드"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "쉼표 피드 버전 {버전}({개정})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "테마"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "현재 항목의 읽기 상태 전환" msgstr "현재 항목의 읽기 상태 전환"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo" msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Kembali"
msgid "Back to log in" msgid "Back to log in"
msgstr "Kembali untuk log masuk" msgstr "Kembali untuk log masuk"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Peluasan penyemak imbas" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Menukar kata laluan akan menjana kunci API baharu"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Semak sama ada suapan berfungsi" msgstr "Semak sama ada suapan berfungsi"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed item belum dibaca seterusnya" msgstr "CommaFeed item belum dibaca seterusnya"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Versi CommaFeed {versi} ({semakan})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Togol status bacaan entri semasa" msgstr "Togol status bacaan entri semasa"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Cuba CommaFeed dengan akaun demo: demo/demo" msgstr "Cuba CommaFeed dengan akaun demo: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Tilbake"
msgid "Back to log in" msgid "Back to log in"
msgstr "Tilbake for å logge inn" msgstr "Tilbake for å logge inn"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Nettleserutvidelser" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Endring av passord vil generere en ny API-nøkkel"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Sjekk at feeden fungerer" msgstr "Sjekk at feeden fungerer"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed neste uleste element" msgstr "CommaFeed neste uleste element"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed versjon {versjon} ({revisjon})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Veksle lesestatus for gjeldende oppføring" msgstr "Veksle lesestatus for gjeldende oppføring"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prøv CommaFeed med demokontoen: demo/demo" msgstr "Prøv CommaFeed med demokontoen: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Terug"
msgid "Back to log in" msgid "Back to log in"
msgstr "Terug naar inloggen" msgstr "Terug naar inloggen"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Browserextensies" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Het wijzigen van het wachtwoord genereert een nieuwe API-sleutel"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Controleer of de feed werkt" msgstr "Controleer of de feed werkt"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed volgende ongelezen item" msgstr "CommaFeed volgende ongelezen item"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed-versie {versie} ({revisie})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Thema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Toggle leesstatus van huidige invoer" msgstr "Toggle leesstatus van huidige invoer"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Probeer CommaFeed uit met het demo-account: demo/demo" msgstr "Probeer CommaFeed uit met het demo-account: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Tilbake"
msgid "Back to log in" msgid "Back to log in"
msgstr "Tilbake for å logge inn" msgstr "Tilbake for å logge inn"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Nettleserutvidelser" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Endring av passord vil generere en ny API-nøkkel"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Sjekk at feeden fungerer" msgstr "Sjekk at feeden fungerer"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed neste uleste element" msgstr "CommaFeed neste uleste element"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed versjon {versjon} ({revisjon})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Veksle lesestatus for gjeldende oppføring" msgstr "Veksle lesestatus for gjeldende oppføring"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prøv CommaFeed med demokontoen: demo/demo" msgstr "Prøv CommaFeed med demokontoen: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Powrót"
msgid "Back to log in" msgid "Back to log in"
msgstr "Powrót do logowania" msgstr "Powrót do logowania"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Rozszerzenia przeglądarki" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Zmiana hasła spowoduje wygenerowanie nowego klucza API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Sprawdź, czy kanał działa" msgstr "Sprawdź, czy kanał działa"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "Przecinek następny nieprzeczytany element" msgstr "Przecinek następny nieprzeczytany element"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Wersja CommaFeed {wersja} ({wersja})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Motyw"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Przełącz stan odczytu bieżącego wpisu" msgstr "Przełącz stan odczytu bieżącego wpisu"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo" msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Voltar"
msgid "Back to log in" msgid "Back to log in"
msgstr "Voltar para logar" msgstr "Voltar para logar"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Extensões do navegador" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "A alteração da senha gerará uma nova chave de API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Verifique se o feed está funcionando" msgstr "Verifique se o feed está funcionando"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed próximo item não lido" msgstr "CommaFeed próximo item não lido"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "Versão do CommaFeed {versão} ({revisão})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Alternar o status de leitura da entrada atual" msgstr "Alternar o status de leitura da entrada atual"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Experimente o CommaFeed com a conta demo: demo/demo" msgstr "Experimente o CommaFeed com a conta demo: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Назад"
msgid "Back to log in" msgid "Back to log in"
msgstr "Вернуться к входу" msgstr "Вернуться к входу"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Расширения браузера" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "При изменении пароля будет сгенерирова
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Проверьте, работает ли лента." msgstr "Проверьте, работает ли лента."
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed следующий непрочитанный элемент" msgstr "CommaFeed следующий непрочитанный элемент"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed версия {версия} ({редакция})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Тема"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Переключить статус чтения текущей записи" msgstr "Переключить статус чтения текущей записи"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Попробуйте CommaFeed на демо-счете: demo/demo" msgstr "Попробуйте CommaFeed на демо-счете: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Späť"
msgid "Back to log in" msgid "Back to log in"
msgstr "Späť na prihlásenie" msgstr "Späť na prihlásenie"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Rozšírenia prehliadača" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Zmena hesla vygeneruje nový kľúč API"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Skontrolujte, či feed funguje" msgstr "Skontrolujte, či feed funguje"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed ďalšia neprečítaná položka" msgstr "CommaFeed ďalšia neprečítaná položka"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed verzia {version} ({revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Téma"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Prepne stav čítania aktuálneho záznamu" msgstr "Prepne stav čítania aktuálneho záznamu"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo" msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Tillbaka"
msgid "Back to log in" msgid "Back to log in"
msgstr "Tillbaka för att logga in" msgstr "Tillbaka för att logga in"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Webbläsartillägg" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,12 +167,16 @@ msgstr "Ändra lösenord kommer att generera en ny API-nyckel"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Kontrollera att matningen fungerar" msgstr "Kontrollera att matningen fungerar"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed nästa olästa objekt" msgstr "CommaFeed nästa olästa objekt"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Växla lässtatus för aktuell post" msgstr "Växla lässtatus för aktuell post"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prova CommaFeed med demokontot: demo/demo" msgstr "Prova CommaFeed med demokontot: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "Geri"
msgid "Back to log in" msgid "Back to log in"
msgstr "Giriş yapmak için geri dön" msgstr "Giriş yapmak için geri dön"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "Tarayıcı uzantıları" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "Şifreyi değiştirmek yeni bir API anahtarı oluşturacak"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Feed'in çalışıp çalışmadığını kontrol edin" msgstr "Feed'in çalışıp çalışmadığını kontrol edin"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed sonraki okunmamış öğe" msgstr "CommaFeed sonraki okunmamış öğe"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed sürümü {sürüm} ({revizyon})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "Tema"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Geçerli girişin okuma durumunu değiştir" msgstr "Geçerli girişin okuma durumunu değiştir"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo" msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo"

View File

@@ -127,9 +127,13 @@ msgstr "返回"
msgid "Back to log in" msgid "Back to log in"
msgstr "返回登录" msgstr "返回登录"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extentions" msgid "Browser extention"
msgstr "浏览器扩展" msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -163,13 +167,17 @@ msgstr "更改密码将生成新的 API 密钥"
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "检查提要是否正常工作" msgstr "检查提要是否正常工作"
#: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
msgstr "CommaFeed 下一个未读项目" msgstr "CommaFeed 下一个未读项目"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})" msgid "CommaFeed version {version} ({revision})."
msgstr "CommaFeed 版本 {version} ({revision})" msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -793,6 +801,10 @@ msgstr "主题"
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "切换当前条目的读取状态" msgstr "切换当前条目的读取状态"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "使用演示帐户试用 CommaFeeddemo/demo" msgstr "使用演示帐户试用 CommaFeeddemo/demo"

View File

@@ -63,7 +63,7 @@ function Buttons() {
const iconSize = 18 const iconSize = 18
const serverInfos = useAppSelector(state => state.server.serverInfos) const serverInfos = useAppSelector(state => state.server.serverInfos)
const { colorScheme, toggleColorScheme } = useMantineColorScheme() const { colorScheme, toggleColorScheme } = useMantineColorScheme()
const { isBrowserExtension, openSettingsPage } = useBrowserExtension() const { isBrowserExtensionPopup, openSettingsPage } = useBrowserExtension()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const dark = colorScheme === "dark" const dark = colorScheme === "dark"
@@ -108,7 +108,7 @@ function Buttons() {
hideLabelOnDesktop hideLabelOnDesktop
/> />
{isBrowserExtension && ( {isBrowserExtensionPopup && (
<ActionButton <ActionButton
label={<Trans>Extension options</Trans>} label={<Trans>Extension options</Trans>}
icon={<TbSettings size={iconSize} />} icon={<TbSettings size={iconSize} />}

View File

@@ -5,6 +5,7 @@ import { redirectToApiDocumentation } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { CategorySelect } from "components/content/add/CategorySelect" import { CategorySelect } from "components/content/add/CategorySelect"
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import React, { useState } from "react" import React, { useState } from "react"
import { TbHelp, TbKeyboard, TbPuzzle, TbRocket } from "react-icons/tb" import { TbHelp, TbKeyboard, TbPuzzle, TbRocket } from "react-icons/tb"
@@ -60,16 +61,23 @@ function NextUnreadBookmarklet() {
export function AboutPage() { export function AboutPage() {
const version = useAppSelector(state => state.server.serverInfos?.version) const version = useAppSelector(state => state.server.serverInfos?.version)
const revision = useAppSelector(state => state.server.serverInfos?.gitCommit) const revision = useAppSelector(state => state.server.serverInfos?.gitCommit)
const { isBrowserExtensionInstalled, browserExtensionVersion, isBrowserExtensionInstallable } = useBrowserExtension()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return ( return (
<Container size="xl"> <Container size="xl">
<SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}> <SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}>
<Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}> <Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}>
<Box> <Box>
<Trans> <Trans>
CommaFeed version {version} ({revision}) CommaFeed version {version} ({revision}).
</Trans> </Trans>
</Box> </Box>
{isBrowserExtensionInstallable && isBrowserExtensionInstalled && (
<Box>
<Trans>CommaFeed browser extension version {browserExtensionVersion}.</Trans>
</Box>
)}
<Box mt="md"> <Box mt="md">
<Trans> <Trans>
<span>CommaFeed is an open-source project. Sources are hosted on </span> <span>CommaFeed is an open-source project. Sources are hosted on </span>
@@ -86,8 +94,8 @@ export function AboutPage() {
<Section title={<Trans>Goodies</Trans>} icon={<TbPuzzle size={24} />}> <Section title={<Trans>Goodies</Trans>} icon={<TbPuzzle size={24} />}>
<List> <List>
<List.Item> <List.Item>
<Anchor href="https://github.com/Athou/commafeed-browser-extension" target="_blank" rel="noreferrer"> <Anchor href={Constants.browserExtensionUrl} target="_blank" rel="noreferrer">
<Trans>Browser extentions</Trans> <Trans>Browser extention</Trans>
</Anchor> </Anchor>
</List.Item> </List.Item>
<List.Item> <List.Item>

View File

@@ -1,8 +1,14 @@
import { Box } from "@mantine/core"
import SwaggerUI from "swagger-ui-react" import SwaggerUI from "swagger-ui-react"
import "swagger-ui-react/swagger-ui.css" import "swagger-ui-react/swagger-ui.css"
function ApiDocumentationPage() { function ApiDocumentationPage() {
return <SwaggerUI url="swagger/swagger.json" /> return (
// force white background because swagger is unreadable with dark theme
<Box style={{ backgroundColor: "#fff" }}>
<SwaggerUI url="swagger/swagger.json" />
</Box>
)
} }
export default ApiDocumentationPage export default ApiDocumentationPage

View File

@@ -13,10 +13,10 @@ import {
Title, Title,
useMantineTheme, useMantineTheme,
} from "@mantine/core" } from "@mantine/core"
import { useViewportSize } from "@mantine/hooks" import { useMediaQuery, useViewportSize } from "@mantine/hooks"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectToAdd, redirectToRootCategory } from "app/slices/redirect" import { redirectToAdd, redirectToRootCategory } from "app/slices/redirect"
import { reloadTree, setMobileMenuOpen } from "app/slices/tree" import { reloadTree, setMobileMenuOpen, setSidebarWidth } from "app/slices/tree"
import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user" import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
@@ -26,28 +26,39 @@ import { OnMobile } from "components/responsive/OnMobile"
import { useAppLoading } from "hooks/useAppLoading" import { useAppLoading } from "hooks/useAppLoading"
import { useWebSocket } from "hooks/useWebSocket" import { useWebSocket } from "hooks/useWebSocket"
import { LoadingPage } from "pages/LoadingPage" import { LoadingPage } from "pages/LoadingPage"
import { Resizable } from "re-resizable"
import { ReactNode, Suspense, useEffect } from "react" import { ReactNode, Suspense, useEffect } from "react"
import { TbPlus } from "react-icons/tb" import { TbPlus } from "react-icons/tb"
import { Outlet } from "react-router-dom" import { Outlet } from "react-router-dom"
interface LayoutProps { interface LayoutProps {
sidebar: ReactNode sidebar: ReactNode
sidebarWidth: number
header: ReactNode header: ReactNode
} }
const sidebarPadding = DEFAULT_THEME.spacing.xs const sidebarPadding = DEFAULT_THEME.spacing.xs
const sidebarRightBorderWidth = "1px" const sidebarRightBorderWidth = "1px"
const useStyles = createStyles(theme => ({ const useStyles = createStyles((theme, props: LayoutProps) => ({
sidebar: {
"& .mantine-ScrollArea-scrollbar[data-orientation='horizontal']": {
display: "none",
},
},
sidebarContentResizeWrapper: {
padding: sidebarPadding,
minHeight: `calc(100vh - ${Constants.layout.headerHeight}px)`,
},
sidebarContent: { sidebarContent: {
maxWidth: `calc(${Constants.layout.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, maxWidth: `calc(${props.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
}, },
}, },
mainContentWrapper: { mainContentWrapper: {
paddingTop: Constants.layout.headerHeight, paddingTop: Constants.layout.headerHeight,
paddingLeft: Constants.layout.sidebarWidth, paddingLeft: props.sidebarWidth,
paddingRight: 0, paddingRight: 0,
paddingBottom: 0, paddingBottom: 0,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
@@ -55,7 +66,7 @@ const useStyles = createStyles(theme => ({
}, },
}, },
mainContent: { mainContent: {
maxWidth: `calc(100vw - ${Constants.layout.sidebarWidth}px)`, maxWidth: `calc(100vw - ${props.sidebarWidth}px)`,
padding: theme.spacing.md, padding: theme.spacing.md,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
maxWidth: "100vw", maxWidth: "100vw",
@@ -76,15 +87,19 @@ function LogoAndTitle() {
) )
} }
export default function Layout({ sidebar, header }: LayoutProps) { export default function Layout(props: LayoutProps) {
const { classes } = useStyles() const { classes } = useStyles(props)
const theme = useMantineTheme() const theme = useMantineTheme()
const viewport = useViewportSize() const viewport = useViewportSize()
const { loading } = useAppLoading() const { loading } = useAppLoading()
const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint})`)
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
const sidebarHidden = props.sidebarWidth === 0
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useWebSocket() useWebSocket()
const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth))
useEffect(() => { useEffect(() => {
dispatch(reloadSettings()) dispatch(reloadSettings())
dispatch(reloadProfile()) dispatch(reloadProfile())
@@ -122,13 +137,29 @@ export default function Layout({ sidebar, header }: LayoutProps) {
navbar={ navbar={
<Navbar <Navbar
id="sidebar" id="sidebar"
p={sidebarPadding} hiddenBreakpoint={sidebarHidden ? 99999999 : Constants.layout.mobileBreakpoint}
hiddenBreakpoint={Constants.layout.mobileBreakpoint} hidden={sidebarHidden || !mobileMenuOpen}
hidden={!mobileMenuOpen} width={{ md: props.sidebarWidth }}
width={{ md: Constants.layout.sidebarWidth }} className={classes.sidebar}
> >
<Navbar.Section grow component={ScrollArea} mx="-xs" px="xs"> <Navbar.Section grow component={ScrollArea} mx={mobile ? 0 : "-sm"} px={mobile ? 0 : "sm"}>
<Box className={classes.sidebarContent}>{sidebar}</Box> <Resizable
enable={{
top: false,
right: !mobile,
bottom: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
}}
onResize={(e, dir, el) => handleResize(el)}
minWidth={120}
className={classes.sidebarContentResizeWrapper}
>
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
</Resizable>
</Navbar.Section> </Navbar.Section>
</Navbar> </Navbar>
} }
@@ -147,19 +178,19 @@ export default function Layout({ sidebar, header }: LayoutProps) {
{!mobileMenuOpen && ( {!mobileMenuOpen && (
<Group> <Group>
<Box mr="sm">{burger}</Box> <Box mr="sm">{burger}</Box>
<Box sx={{ flexGrow: 1 }}>{header}</Box> <Box sx={{ flexGrow: 1 }}>{props.header}</Box>
</Group> </Group>
)} )}
</OnMobile> </OnMobile>
<OnDesktop> <OnDesktop>
<Group> <Group>
<Group position="apart" sx={{ width: Constants.layout.sidebarWidth - 16 }}> <Group position="apart" sx={{ width: props.sidebarWidth - 16 }}>
<Box> <Box>
<LogoAndTitle /> <LogoAndTitle />
</Box> </Box>
<Box>{addButton}</Box> <Box>{addButton}</Box>
</Group> </Group>
<Box sx={{ flexGrow: 1 }}>{header}</Box> <Box sx={{ flexGrow: 1 }}>{props.header}</Box>
</Group> </Group>
</OnDesktop> </OnDesktop>
</Header> </Header>

View File

@@ -3,6 +3,9 @@
app: app:
# url used to access commafeed # url used to access commafeed
publicUrl: http://localhost:8082/ publicUrl: http://localhost:8082/
# whether to expose a robots.txt file that disallows web crawlers and search engine indexers
hideFromWebCrawlers: true
# whether to allow user registrations # whether to allow user registrations
allowRegistrations: true allowRegistrations: true

View File

@@ -3,6 +3,9 @@
app: app:
# url used to access commafeed # url used to access commafeed
publicUrl: http://localhost:8082/ publicUrl: http://localhost:8082/
# whether to expose a robots.txt file that disallows web crawlers and search engine indexers
hideFromWebCrawlers: true
# whether to allow user registrations # whether to allow user registrations
allowRegistrations: false allowRegistrations: false

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.6.0</version> <version>3.7.0</version>
</parent> </parent>
<artifactId>commafeed-server</artifactId> <artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name> <name>CommaFeed Server</name>
@@ -226,7 +226,7 @@
<dependency> <dependency>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<version>3.6.0</version> <version>3.7.0</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -47,6 +47,7 @@ import com.commafeed.frontend.servlet.CustomCssServlet;
import com.commafeed.frontend.servlet.CustomJsServlet; import com.commafeed.frontend.servlet.CustomJsServlet;
import com.commafeed.frontend.servlet.LogoutServlet; import com.commafeed.frontend.servlet.LogoutServlet;
import com.commafeed.frontend.servlet.NextUnreadServlet; import com.commafeed.frontend.servlet.NextUnreadServlet;
import com.commafeed.frontend.servlet.RobotsTxtDisallowAllServlet;
import com.commafeed.frontend.session.SessionHelperFactoryProvider; import com.commafeed.frontend.session.SessionHelperFactoryProvider;
import com.commafeed.frontend.ws.WebSocketConfigurator; import com.commafeed.frontend.ws.WebSocketConfigurator;
import com.commafeed.frontend.ws.WebSocketEndpoint; import com.commafeed.frontend.ws.WebSocketEndpoint;
@@ -169,6 +170,11 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css"); environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css");
environment.servlets().addServlet("customJs", injector.getInstance(CustomJsServlet.class)).addMapping("/custom_js.js"); environment.servlets().addServlet("customJs", injector.getInstance(CustomJsServlet.class)).addMapping("/custom_js.js");
environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js"); environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js");
if (Boolean.TRUE.equals(config.getApplicationSettings().getHideFromWebCrawlers())) {
environment.servlets()
.addServlet("robots.txt", injector.getInstance(RobotsTxtDisallowAllServlet.class))
.addMapping("/robots.txt");
}
// WebSocket endpoint // WebSocket endpoint
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws") ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws")

View File

@@ -65,6 +65,10 @@ public class CommaFeedConfiguration extends Configuration {
@Valid @Valid
private String publicUrl; private String publicUrl;
@NotNull
@Valid
private Boolean hideFromWebCrawlers = true;
@NotNull @NotNull
@Valid @Valid
private Boolean allowRegistrations; private Boolean allowRegistrations;

View File

@@ -15,23 +15,30 @@ import com.commafeed.backend.model.QFeed;
import com.commafeed.backend.model.QFeedSubscription; import com.commafeed.backend.model.QFeedSubscription;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQuery;
@Singleton @Singleton
public class FeedDAO extends GenericDAO<Feed> { public class FeedDAO extends GenericDAO<Feed> {
private final QFeed feed = QFeed.feed; private final QFeed feed = QFeed.feed;
private final QFeedSubscription subscription = QFeedSubscription.feedSubscription;
@Inject @Inject
public FeedDAO(SessionFactory sessionFactory) { public FeedDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
public List<Feed> findNextUpdatable(int count) { public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) {
return query().selectFrom(feed) JPAQuery<Feed> query = query().selectFrom(feed).where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date())));
.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date()))) if (lastLoginThreshold != null) {
.orderBy(feed.disabledUntil.asc()) query.where(JPAExpressions.selectOne()
.limit(count) .from(subscription)
.fetch(); .join(subscription.user)
.where(subscription.feed.id.eq(feed.id), subscription.user.lastLogin.gt(lastLoginThreshold))
.exists());
}
return query.orderBy(feed.disabledUntil.asc()).limit(count).fetch();
} }
public void setDisabledUntil(List<Long> feedIds, Date date) { public void setDisabledUntil(List<Long> feedIds, Date date) {

View File

@@ -86,10 +86,12 @@ public class FeedRefreshEngine implements Managed {
Feed feed = queue.take(); Feed feed = queue.take();
// send the feed to be processed // send the feed to be processed
log.debug("got feed {} from the queue, send it for processing", feed.getId());
processFeedAsync(feed); processFeedAsync(feed);
// we removed a feed from the queue, try to refill it as it may now be empty // we removed a feed from the queue, try to refill it as it may now be empty
if (queue.isEmpty()) { if (queue.isEmpty()) {
log.debug("took the last feed from the queue, try to refill");
refillQueueAsync(); refillQueueAsync();
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -108,9 +110,11 @@ public class FeedRefreshEngine implements Managed {
while (!refillLoopExecutor.isShutdown()) { while (!refillLoopExecutor.isShutdown()) {
try { try {
if (queue.isEmpty()) { if (queue.isEmpty()) {
log.debug("refilling queue");
refillQueueAsync(); refillQueueAsync();
} }
log.debug("sleeping for 15s");
TimeUnit.SECONDS.sleep(15); TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.debug("interrupted while sleeping"); log.debug("interrupted while sleeping");
@@ -123,6 +127,7 @@ public class FeedRefreshEngine implements Managed {
} }
public void refreshImmediately(Feed feed) { public void refreshImmediately(Feed feed) {
log.debug("add feed {} at the start of the queue", feed.getId());
// remove the feed from the queue if it was already queued to avoid refreshing it twice // remove the feed from the queue if it was already queued to avoid refreshing it twice
queue.removeIf(f -> f.getId().equals(feed.getId())); queue.removeIf(f -> f.getId().equals(feed.getId()));
queue.addFirst(feed); queue.addFirst(feed);
@@ -136,7 +141,9 @@ public class FeedRefreshEngine implements Managed {
refill.mark(); refill.mark();
for (Feed feed : getNextUpdatableFeeds(getBatchSize())) { List<Feed> nextUpdatableFeeds = getNextUpdatableFeeds(getBatchSize());
log.debug("found {} feeds that are up for refresh", nextUpdatableFeeds.size());
for (Feed feed : nextUpdatableFeeds) {
// add the feed only if it was not already queued // add the feed only if it was not already queued
if (queue.stream().noneMatch(f -> f.getId().equals(feed.getId()))) { if (queue.stream().noneMatch(f -> f.getId().equals(feed.getId()))) {
queue.addLast(feed); queue.addLast(feed);
@@ -161,7 +168,10 @@ public class FeedRefreshEngine implements Managed {
private List<Feed> getNextUpdatableFeeds(int max) { private List<Feed> getNextUpdatableFeeds(int max) {
return unitOfWork.call(() -> { return unitOfWork.call(() -> {
List<Feed> feeds = feedDAO.findNextUpdatable(max); Date lastLoginThreshold = Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad())
? DateUtils.addDays(new Date(), -30)
: null;
List<Feed> feeds = feedDAO.findNextUpdatable(max, lastLoginThreshold);
// update disabledUntil to prevent feeds from being returned again by feedDAO.findNextUpdatable() // update disabledUntil to prevent feeds from being returned again by feedDAO.findNextUpdatable()
Date nextUpdateDate = DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes()); Date nextUpdateDate = DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes());
feedDAO.setDisabledUntil(feeds.stream().map(AbstractModel::getId).collect(Collectors.toList()), nextUpdateDate); feedDAO.setDisabledUntil(feeds.stream().map(AbstractModel::getId).collect(Collectors.toList()), nextUpdateDate);

View File

@@ -38,6 +38,6 @@ public class FeedCategory extends AbstractModel {
private boolean collapsed; private boolean collapsed;
private Integer position; private int position;
} }

View File

@@ -38,7 +38,7 @@ public class FeedSubscription extends AbstractModel {
@OneToMany(mappedBy = "subscription", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "subscription", cascade = CascadeType.REMOVE)
private Set<FeedEntryStatus> statuses; private Set<FeedEntryStatus> statuses;
private Integer position; private int position;
@Column(name = "filtering_expression", length = 4096) @Column(name = "filtering_expression", length = 4096)
private String filter; private String filter;

View File

@@ -8,8 +8,6 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.apache.commons.lang3.time.DateUtils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -49,17 +47,4 @@ public class User extends AbstractModel {
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date recoverPasswordTokenDate; private Date recoverPasswordTokenDate;
@Column(name = "last_full_refresh")
@Temporal(TemporalType.TIMESTAMP)
private Date lastFullRefresh;
public boolean shouldRefreshFeedsAt(Date when) {
return lastFullRefresh == null || lastFullRefreshMoreThan30MinutesBefore(when);
}
private boolean lastFullRefreshMoreThan30MinutesBefore(Date when) {
return lastFullRefresh.before(DateUtils.addMinutes(when, -30));
}
} }

View File

@@ -1,5 +1,6 @@
package com.commafeed.backend.service; package com.commafeed.backend.service;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -107,6 +108,17 @@ public class FeedSubscriptionService {
} }
} }
public void refreshAllUpForRefresh(User user) {
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
for (FeedSubscription sub : subs) {
Date disabledUntil = sub.getFeed().getDisabledUntil();
if (disabledUntil == null || disabledUntil.before(new Date())) {
Feed feed = sub.getFeed();
feedRefreshEngine.refreshImmediately(feed);
}
}
}
public Map<Long, UnreadCount> getUnreadCount(User user) { public Map<Long, UnreadCount> getUnreadCount(User user) {
return feedSubscriptionDAO.findAll(user).stream().collect(Collectors.toMap(FeedSubscription::getId, s -> getUnreadCount(user, s))); return feedSubscriptionDAO.findAll(user).stream().collect(Collectors.toMap(FeedSubscription::getId, s -> getUnreadCount(user, s)));
} }

View File

@@ -25,25 +25,20 @@ public class PostLoginActivities {
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;
public void executeFor(User user) { public void executeFor(User user) {
Date lastLogin = user.getLastLogin(); // only update lastLogin every once in a while in order to avoid invalidating the cache every time someone logs in
Date now = new Date(); Date now = new Date();
Date lastLogin = user.getLastLogin();
boolean saveUser = false; if (lastLogin == null || lastLogin.before(DateUtils.addMinutes(now, -30))) {
// 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); user.setLastLogin(now);
saveUser = true;
}
if (Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad()) && user.shouldRefreshFeedsAt(now)) { boolean heavyLoad = Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad());
feedSubscriptionService.refreshAll(user); if (heavyLoad) {
user.setLastFullRefresh(now); // the amount of feeds in the database that are up for refresh might be very large since we're in heavy load mode
saveUser = true; // the feed refresh engine might not be able to catch up quickly enough
} // put feeds from online users that are up for refresh at the top of the queue
feedSubscriptionService.refreshAllUpForRefresh(user);
}
if (saveUser) {
// Post login activites are susceptible to run for any webservice call. // Post login activites are susceptible to run for any webservice call.
// We update the user in a new transaction to update the user immediately. // 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 // If we didn't and the webservice call takes time, subsequent webservice calls would have to wait for the first call to
@@ -51,5 +46,4 @@ public class PostLoginActivities {
unitOfWork.run(() -> userDAO.saveOrUpdate(user)); unitOfWork.run(() -> userDAO.saveOrUpdate(user));
} }
} }
} }

View File

@@ -35,5 +35,5 @@ public class Category implements Serializable {
private boolean expanded; private boolean expanded;
@ApiModelProperty(value = "position of the category in the list", required = true) @ApiModelProperty(value = "position of the category in the list", required = true)
private Integer position; private int position;
} }

View File

@@ -54,7 +54,7 @@ public class Subscription implements Serializable {
private String categoryId; private String categoryId;
@ApiModelProperty("position of the subscription's in the list") @ApiModelProperty("position of the subscription's in the list")
private Integer position; private int position;
@ApiModelProperty(value = "date of the newest item", dataType = "number") @ApiModelProperty(value = "date of the newest item", dataType = "number")
private Date newestItemTime; private Date newestItemTime;

View File

@@ -458,7 +458,7 @@ public class CategoryREST {
category.getChildren().add(child); category.getChildren().add(child);
} }
} }
Collections.sort(category.getChildren(), (o1, o2) -> ObjectUtils.compare(o1.getPosition(), o2.getPosition())); category.getChildren().sort(Comparator.comparing(Category::getPosition).thenComparing(Category::getName));
for (FeedSubscription subscription : subscriptions) { for (FeedSubscription subscription : subscriptions) {
if (id == null && subscription.getCategory() == null if (id == null && subscription.getCategory() == null
@@ -468,7 +468,7 @@ public class CategoryREST {
category.getFeeds().add(sub); category.getFeeds().add(sub);
} }
} }
Collections.sort(category.getFeeds(), (o1, o2) -> ObjectUtils.compare(o1.getPosition(), o2.getPosition())); category.getFeeds().sort(Comparator.comparing(Subscription::getPosition).thenComparing(Subscription::getName));
return category; return category;
} }

View File

@@ -0,0 +1,20 @@
package com.commafeed.frontend.servlet;
import java.io.IOException;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
public class RobotsTxtDisallowAllServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain");
resp.getWriter().write("User-agent: *");
resp.getWriter().write("\n");
resp.getWriter().write("Disallow: /");
}
}

View File

@@ -0,0 +1,25 @@
<?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="position-required" author="athou">
<update tableName="FEEDCATEGORIES">
<column name="position" valueNumeric="0" />
<where>position is null</where>
</update>
<addNotNullConstraint tableName="FEEDCATEGORIES" columnName="position" columnDataType="int" />
<update tableName="FEEDSUBSCRIPTIONS">
<column name="position" valueNumeric="0" />
<where>position is null</where>
</update>
<addNotNullConstraint tableName="FEEDSUBSCRIPTIONS" columnName="position" columnDataType="int" />
</changeSet>
<changeSet id="drop-unused-last-full-refresh" author="athou">
<dropColumn tableName="USERS" columnName="last_full_refresh" />
</changeSet>
</databaseChangeLog>

View File

@@ -19,5 +19,6 @@
<include file="changelogs/db.changelog-2.6.xml" /> <include file="changelogs/db.changelog-2.6.xml" />
<include file="changelogs/db.changelog-3.2.xml" /> <include file="changelogs/db.changelog-3.2.xml" />
<include file="changelogs/db.changelog-3.5.xml" /> <include file="changelogs/db.changelog-3.5.xml" />
<include file="changelogs/db.changelog-3.6.xml" />
</databaseChangeLog> </databaseChangeLog>

View File

@@ -3,6 +3,9 @@
app: app:
# url used to access commafeed # url used to access commafeed
publicUrl: http://localhost:8082/ publicUrl: http://localhost:8082/
# whether to expose a robots.txt file that disallows web crawlers and search engine indexers
hideFromWebCrawlers: true
# whether to allow user registrations # whether to allow user registrations
allowRegistrations: true allowRegistrations: true

View File

@@ -5,7 +5,7 @@
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.6.0</version> <version>3.7.0</version>
<name>CommaFeed</name> <name>CommaFeed</name>
<packaging>pom</packaging> <packaging>pom</packaging>

View File

@@ -13,8 +13,8 @@ if [[ "$BRANCH" != "master" ]]; then
exit exit
fi fi
# make sure README.md has been updated # make sure CHANGELOG.md has been updated
read -r -p "Has README.md been updated? (Y/n) " CONFIRM read -r -p "Has CHANGELOG.md been updated? (Y/n) " CONFIRM
case "$CONFIRM" in case "$CONFIRM" in
n | N) exit ;; n | N) exit ;;
esac esac