forked from Archives/Athou_commafeed
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f699d9675 | ||
|
|
a5aba6f7ae | ||
|
|
78c8711a79 | ||
|
|
8325236d0e | ||
|
|
437401e73f | ||
|
|
fa06d321d5 | ||
|
|
d1ddcb6ace | ||
|
|
6944d4dc0b | ||
|
|
c835d805b1 | ||
|
|
4a90e1f69d | ||
|
|
fcfeaa462e | ||
|
|
b16978d8fe | ||
|
|
68c62b4528 | ||
|
|
18f68aab31 | ||
|
|
8abb2770ec | ||
|
|
9156b8b6d0 | ||
|
|
2c32fa1e13 | ||
|
|
7e48afe36c | ||
|
|
cd94a3b56f | ||
|
|
22e0f1f382 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# 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]
|
||||
|
||||
- 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
|
||||
- reduce javascript bundle size by 30% by loading only the necessary translations
|
||||
- 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 burger button with the rest of the header on mobile
|
||||
|
||||
@@ -67,10 +80,10 @@
|
||||
## [3.0.1]
|
||||
|
||||
- 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
|
||||
its value
|
||||
- e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with
|
||||
its value
|
||||
- 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]
|
||||
|
||||
|
||||
10
commafeed-client/package-lock.json
generated
10
commafeed-client/package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"dayjs": "^1.11.7",
|
||||
"interweave": "^13.1.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"re-resizable": "^6.9.9",
|
||||
"react": "^18.2.0",
|
||||
"react-async-hook": "^4.0.0",
|
||||
"react-contexify": "^6.0.0",
|
||||
@@ -10035,6 +10036,15 @@
|
||||
"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": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"dayjs": "^1.11.7",
|
||||
"interweave": "^13.1.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"re-resizable": "^6.9.9",
|
||||
"react": "^18.2.0",
|
||||
"react-async-hook": "^4.0.0",
|
||||
"react-contexify": "^6.0.0",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.7.0</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<name>CommaFeed Client</name>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { categoryUnreadCount } from "app/utils"
|
||||
import { ErrorBoundary } from "components/ErrorBoundary"
|
||||
import { Header } from "components/header/Header"
|
||||
import { Tree } from "components/sidebar/Tree"
|
||||
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
||||
import { useI18n } from "i18n"
|
||||
import { AdminUsersPage } from "pages/admin/AdminUsersPage"
|
||||
import { MetricsPage } from "pages/admin/MetricsPage"
|
||||
@@ -37,7 +38,7 @@ import useLocalStorage from "use-local-storage"
|
||||
function Providers(props: { children: React.ReactNode }) {
|
||||
const preferredColorScheme = useColorScheme()
|
||||
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 (
|
||||
<I18nProvider i18n={i18n}>
|
||||
@@ -65,6 +66,9 @@ function Providers(props: { children: React.ReactNode }) {
|
||||
const ApiDocumentationPage = React.lazy(() => import("pages/app/ApiDocumentationPage"))
|
||||
|
||||
function AppRoutes() {
|
||||
const sidebarWidth = useAppSelector(state => state.tree.sidebarWidth)
|
||||
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<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="passwordRecovery" element={<PasswordRecoveryPage />} />
|
||||
<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=":id" element={<FeedEntriesPage sourceType="category" />} />
|
||||
<Route path=":id/details" element={<CategoryDetailsPage />} />
|
||||
@@ -134,13 +138,28 @@ function FaviconHandler() {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
useEffect(() => {
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
if (unreadCount === 0) Tinycon.reset()
|
||||
else Tinycon.setBubble(unreadCount)
|
||||
if (unreadCount === 0) {
|
||||
Tinycon.reset()
|
||||
} else {
|
||||
Tinycon.setBubble(unreadCount)
|
||||
}
|
||||
}, [root])
|
||||
|
||||
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() {
|
||||
useI18n()
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -153,6 +172,7 @@ export function App() {
|
||||
<Providers>
|
||||
<>
|
||||
<FaviconHandler />
|
||||
<BrowserExtensionBadgeUnreadCountHandler />
|
||||
<HashRouter>
|
||||
<GoogleAnalyticsHandler />
|
||||
<RedirectHandler />
|
||||
|
||||
@@ -88,7 +88,6 @@ export const Constants = {
|
||||
layout: {
|
||||
mobileBreakpoint: DEFAULT_THEME.breakpoints.md,
|
||||
headerHeight: 60,
|
||||
sidebarWidth: 350,
|
||||
entryMaxWidth: 650,
|
||||
isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight,
|
||||
isBottomVisible: (div: HTMLElement) => div.getBoundingClientRect().bottom <= window.innerHeight,
|
||||
@@ -97,5 +96,6 @@ export const Constants = {
|
||||
mainScrollAreaId: "main-scroll-area-id",
|
||||
entryId: (entry: Entry) => `entry-id-${entry.id}`,
|
||||
},
|
||||
browserExtensionUrl: "https://github.com/Athou/commafeed-browser-extension",
|
||||
bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e",
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ import { redirectTo } from "./redirect"
|
||||
interface TreeState {
|
||||
rootCategory?: Category
|
||||
mobileMenuOpen: boolean
|
||||
sidebarWidth: number
|
||||
sidebarVisible: boolean
|
||||
}
|
||||
|
||||
const initialState: TreeState = {
|
||||
mobileMenuOpen: false,
|
||||
sidebarWidth: 350,
|
||||
sidebarVisible: true,
|
||||
}
|
||||
|
||||
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>) => {
|
||||
state.mobileMenuOpen = action.payload
|
||||
},
|
||||
setSidebarWidth: (state, action: PayloadAction<number>) => {
|
||||
state.sidebarWidth = action.payload
|
||||
},
|
||||
toggleSidebar: state => {
|
||||
state.sidebarVisible = !state.sidebarVisible
|
||||
},
|
||||
},
|
||||
extraReducers: builder => {
|
||||
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
|
||||
|
||||
@@ -271,7 +271,7 @@ export interface Subscription {
|
||||
iconUrl: string
|
||||
unread: number
|
||||
categoryId?: string
|
||||
position?: number
|
||||
position: number
|
||||
newestItemTime?: number
|
||||
filter?: string
|
||||
}
|
||||
|
||||
@@ -52,17 +52,4 @@ export const scrollToWithCallback = ({
|
||||
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)
|
||||
|
||||
@@ -1,183 +1,200 @@
|
||||
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() {
|
||||
return (
|
||||
<Table striped highlightOnHover>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Refresh</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>R</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open next entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>J</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open previous entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>K</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Set focus on next entry without opening it</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>N</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Set focus on previous entry without opening it</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>P</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Move the page down</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Space</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Move the page up</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Shift</Trans>
|
||||
</Kbd>
|
||||
<span> + </span>
|
||||
<Kbd>
|
||||
<Trans>Space</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open/close current entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>O</Kbd>
|
||||
<span>, </span>
|
||||
<Kbd>
|
||||
<Trans>Enter</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open current entry in a new tab</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>V</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open current entry in a new tab in the background</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>B</Kbd>
|
||||
<span>, </span>
|
||||
<Kbd>
|
||||
<Trans>Middle click</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Toggle read status of current entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>M</Kbd>
|
||||
<span>, </span>
|
||||
<Trans>Swipe header to the right</Trans>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Mark all entries as read</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Shift</Trans>
|
||||
</Kbd>
|
||||
<span> + </span>
|
||||
<Kbd>A</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Go to the All view</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>G</Kbd>
|
||||
<span> </span>
|
||||
<Kbd>A</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Navigate to a subscription by entering its name</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Ctrl</Trans>
|
||||
</Kbd>
|
||||
<span> + </span>
|
||||
<Kbd>K</Kbd>
|
||||
<span>, </span>
|
||||
<Kbd>G</Kbd>
|
||||
<span> </span>
|
||||
<Kbd>U</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Show entry menu (desktop)</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Right click</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Show entry menu (mobile)</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Long press</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Show keyboard shortcut help</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>?</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Stack spacing="xs">
|
||||
<Table striped highlightOnHover>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Refresh</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>R</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open next entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>J</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open previous entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>K</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Set focus on next entry without opening it</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>N</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Set focus on previous entry without opening it</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>P</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Move the page down</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Space</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Move the page up</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Shift</Trans>
|
||||
</Kbd>
|
||||
<span> + </span>
|
||||
<Kbd>
|
||||
<Trans>Space</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open/close current entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>O</Kbd>
|
||||
<span>, </span>
|
||||
<Kbd>
|
||||
<Trans>Enter</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open current entry in a new tab</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>V</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Open current entry in a new tab in the background</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>B</Kbd>
|
||||
<span>*, </span>
|
||||
<Kbd>
|
||||
<Trans>Middle click</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Toggle read status of current entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>M</Kbd>
|
||||
<span>, </span>
|
||||
<Trans>Swipe header to the right</Trans>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Mark all entries as read</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Shift</Trans>
|
||||
</Kbd>
|
||||
<span> + </span>
|
||||
<Kbd>A</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Go to the All view</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>G</Kbd>
|
||||
<span> </span>
|
||||
<Kbd>A</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Navigate to a subscription by entering its name</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Ctrl</Trans>
|
||||
</Kbd>
|
||||
<span> + </span>
|
||||
<Kbd>K</Kbd>
|
||||
<span>, </span>
|
||||
<Kbd>G</Kbd>
|
||||
<span> </span>
|
||||
<Kbd>U</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Show entry menu (desktop)</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Right click</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Show entry menu (mobile)</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>
|
||||
<Trans>Long press</Trans>
|
||||
</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Toggle sidebar</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>F</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Trans } from "@lingui/macro"
|
||||
import { Tooltip } from "@mantine/core"
|
||||
import dayjs from "dayjs"
|
||||
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>
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,10 +12,11 @@ import {
|
||||
selectPreviousEntry,
|
||||
} from "app/slices/entries"
|
||||
import { redirectToRootCategory } from "app/slices/redirect"
|
||||
import { toggleSidebar } from "app/slices/tree"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { openLinkInBackgroundTab } from "app/utils"
|
||||
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
||||
import { Loader } from "components/Loader"
|
||||
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
||||
import { useMousetrap } from "hooks/useMousetrap"
|
||||
import { useViewMode } from "hooks/useViewMode"
|
||||
import { useEffect } from "react"
|
||||
@@ -29,10 +30,12 @@ export function FeedEntries() {
|
||||
const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
|
||||
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
|
||||
const hasMore = useAppSelector(state => state.entries.hasMore)
|
||||
const { viewMode } = useViewMode()
|
||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
|
||||
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
|
||||
const { viewMode } = useViewMode()
|
||||
const dispatch = useAppDispatch()
|
||||
const { openLinkInBackgroundTab } = useBrowserExtension()
|
||||
|
||||
const selectedEntry = entries.find(e => e.id === selectedEntryId)
|
||||
|
||||
@@ -211,7 +214,6 @@ export function FeedEntries() {
|
||||
window.open(selectedEntry.url, "_blank", "noreferrer")
|
||||
})
|
||||
useMousetrap("b", () => {
|
||||
// simulate ctrl+click to open tab in background
|
||||
if (!selectedEntry) return
|
||||
openLinkInBackgroundTab(selectedEntry.url)
|
||||
})
|
||||
@@ -234,6 +236,7 @@ export function FeedEntries() {
|
||||
)
|
||||
})
|
||||
useMousetrap("g a", () => dispatch(redirectToRootCategory()))
|
||||
useMousetrap("f", () => dispatch(toggleSidebar()))
|
||||
useMousetrap("?", () =>
|
||||
openModal({
|
||||
title: <Trans>Keyboard shortcuts</Trans>,
|
||||
@@ -265,6 +268,7 @@ export function FeedEntries() {
|
||||
expanded={!!entry.expanded || viewMode === "expanded"}
|
||||
selected={entry.id === selectedEntryId}
|
||||
showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")}
|
||||
maxWidth={sidebarVisible ? Constants.layout.entryMaxWidth : undefined}
|
||||
onHeaderClick={event => headerClicked(entry, event)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,21 +18,31 @@ interface FeedEntryProps {
|
||||
expanded: boolean
|
||||
selected: boolean
|
||||
showSelectionIndicator: boolean
|
||||
maxWidth?: number
|
||||
onHeaderClick: (e: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: ViewMode }) => {
|
||||
let backgroundColor
|
||||
if (theme.colorScheme === "dark") backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5]
|
||||
else backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit"
|
||||
if (theme.colorScheme === "dark") {
|
||||
backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5]
|
||||
} else {
|
||||
backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit"
|
||||
}
|
||||
|
||||
let marginY = 10
|
||||
if (props.viewMode === "title") marginY = 2
|
||||
else if (props.viewMode === "cozy") marginY = 6
|
||||
if (props.viewMode === "title") {
|
||||
marginY = 2
|
||||
} else if (props.viewMode === "cozy") {
|
||||
marginY = 6
|
||||
}
|
||||
|
||||
let mobileMarginY = 6
|
||||
if (props.viewMode === "title") mobileMarginY = 2
|
||||
else if (props.viewMode === "cozy") mobileMarginY = 4
|
||||
if (props.viewMode === "title") {
|
||||
mobileMarginY = 2
|
||||
} else if (props.viewMode === "cozy") {
|
||||
mobileMarginY = 4
|
||||
}
|
||||
|
||||
let backgroundHoverColor = backgroundColor
|
||||
if (!props.expanded && !props.entry.read) {
|
||||
@@ -59,7 +69,7 @@ const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: View
|
||||
textDecoration: "none",
|
||||
},
|
||||
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
|
||||
|
||||
let paddingY: MantineNumberSize = "xs"
|
||||
if (viewMode === "title") paddingY = 4
|
||||
else if (viewMode === "cozy") paddingY = 8
|
||||
if (viewMode === "title") {
|
||||
paddingY = 4
|
||||
} else if (viewMode === "cozy") {
|
||||
paddingY = 8
|
||||
}
|
||||
|
||||
let borderRadius: MantineNumberSize = "sm"
|
||||
if (viewMode === "title") borderRadius = 0
|
||||
else if (viewMode === "cozy") borderRadius = "xs"
|
||||
if (viewMode === "title") {
|
||||
borderRadius = 0
|
||||
} else if (viewMode === "cozy") {
|
||||
borderRadius = "xs"
|
||||
}
|
||||
|
||||
const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy")
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,8 @@ import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
|
||||
import { redirectToFeed } from "app/slices/redirect"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
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 { Item, Menu, Separator, useContextMenu } from "react-contexify"
|
||||
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 sourceType = useAppSelector(state => state.entries.source.type)
|
||||
const dispatch = useAppDispatch()
|
||||
const { openLinkInBackgroundTab } = useBrowserExtension()
|
||||
|
||||
return (
|
||||
<Menu id={menuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function Header() {
|
||||
const settings = useAppSelector(state => state.user.settings)
|
||||
const profile = useAppSelector(state => state.user.profile)
|
||||
const searchFromStore = useAppSelector(state => state.entries.search)
|
||||
const { isBrowserExtension, openSettingsPage, openAppInNewTab } = useBrowserExtension()
|
||||
const { isBrowserExtensionPopup, openSettingsPage, openAppInNewTab } = useBrowserExtension()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const searchForm = useForm<{ search: string }>({
|
||||
@@ -90,7 +90,7 @@ export function Header() {
|
||||
|
||||
<ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} />
|
||||
|
||||
{isBrowserExtension && (
|
||||
{isBrowserExtensionPopup && (
|
||||
<>
|
||||
<HeaderDivider />
|
||||
|
||||
|
||||
@@ -1,9 +1,64 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
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
|
||||
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 openAppInNewTab = () => window.parent.postMessage("open-app-in-new-tab", "*")
|
||||
const w = isBrowserExtensionPopup ? window.parent : window
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "العودة"
|
||||
msgid "Back to log in"
|
||||
msgstr "العودة لتسجيل الدخول"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "ملحقات المستعرض"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
@@ -163,13 +167,17 @@ msgstr "سيؤدي تغيير كلمة المرور إلى إنشاء مفتاح
|
||||
msgid "Check that the feed is working"
|
||||
msgstr "تأكد من عمل الخلاصة"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed التالي العنصر غير المقروء"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "إصدار CommaFeed {الإصدار} ({مراجعة})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "الموضوع"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "تبديل قراءة حالة الإدخال الحالي"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Enrere"
|
||||
msgid "Back to log in"
|
||||
msgstr "Tornar a iniciar sessió"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Extensions del navegador"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Comproveu que el canal funciona"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed següent element no llegit"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Versió CommaFeed {versió} ({revisió})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Canvia l'estat de lectura de l'entrada actual"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Zpět"
|
||||
msgid "Back to log in"
|
||||
msgstr "Zpět k přihlášení"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Rozšíření prohlížeče"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Zkontrolujte, zda zdroj funguje"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed další nepřečtená položka"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed verze {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Téma"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Přepne stav čtení aktuálního záznamu"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Yn ôl"
|
||||
msgid "Back to log in"
|
||||
msgstr "Yn ôl i fewngofnodi"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Estyniadau porwr"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Gwiriwch fod y porthiant yn gweithio"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed eitem nesaf heb ei darllen"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Fersiwn ComaFeed {fersiwn} ({ adolygu})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Thema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Toglo statws darllen y cofnod cyfredol"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Tilbage"
|
||||
msgid "Back to log in"
|
||||
msgstr "Tilbage for at logge ind"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Browserudvidelser"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Tjek, at foderet virker"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed næste ulæste element"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Skift læsestatus for den aktuelle post"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Zurück"
|
||||
msgid "Back to log in"
|
||||
msgstr "Zurück zum Anmelden"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Browsererweiterungen"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
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
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed nächstes ungelesenes Element"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed-Version {Version} ({Revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Thema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Lesestatus des aktuellen Eintrags umschalten"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Back"
|
||||
msgid "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
|
||||
msgid "Browser extentions"
|
||||
msgstr "Browser extentions"
|
||||
msgid "Browser extention"
|
||||
msgstr "Browser extention"
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
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
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed next unread item"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed version {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr "CommaFeed version {version} ({revision})."
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Theme"
|
||||
msgid "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
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Try out CommaFeed with the demo account: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Atrás"
|
||||
msgid "Back to log in"
|
||||
msgstr "Volver a iniciar sesión"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Extensiones del navegador"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Compruebe que el feed funciona"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed siguiente elemento no leído"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "versión de CommaFeed {versión} ({revisión})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Alternar estado de lectura de la entrada actual"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "برگشت"
|
||||
msgid "Back to log in"
|
||||
msgstr "بازگشت برای ورود به سیستم"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "گسترش مرورگر"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
@@ -163,13 +167,17 @@ msgstr "تغییر رمز عبور یک کلید API جدید ایجاد می ک
|
||||
msgid "Check that the feed is working"
|
||||
msgstr "بررسی کنید که خوراک کار می کند"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "مورد خوانده نشده بعدی CommaFeed"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "نسخه {نسخه} CommaFeed ({نسخه})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "تم"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Takaisin"
|
||||
msgid "Back to log in"
|
||||
msgstr "Takaisin sisäänkirjautumiseen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Selaimen laajennukset"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Tarkista, että syöttö toimii"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed seuraava lukematon kohde"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed-versio {version} ({versio})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Teema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Vaihda nykyisen merkinnän lukutila"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Kokeile CommaFeediä demotilillä: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Retour"
|
||||
msgid "Back to log in"
|
||||
msgstr "Retour à la connexion"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Extensions pour navigateurs"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Vérifie que le flux fonctionne"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed prochain article non lu"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed version {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Thème"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Marquer l'entrée actuelle comme lue/non lue"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Atrás"
|
||||
msgid "Back to log in"
|
||||
msgstr "Volver para iniciar sesión"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Extensións do navegador"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Comproba que a fonte funciona"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed seguinte elemento non lido"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Versión de CommaFeed {versión} ({revisión})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "alternar o estado de lectura da entrada actual"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Proba CommaFeed coa conta de demostración: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Vissza"
|
||||
msgid "Back to log in"
|
||||
msgstr "Vissza a bejelentkezéshez"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Böngészőbővítések"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
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
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed következő olvasatlan elem"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed verzió {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Téma"
|
||||
msgid "Toggle read status of current entry"
|
||||
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
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Kembali"
|
||||
msgid "Back to log in"
|
||||
msgstr "Kembali untuk masuk"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Ekstensi peramban"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Periksa apakah umpannya berfungsi"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed item yang belum dibaca berikutnya"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed versi {versi} ({revisi})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Beralih status baca entri saat ini"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Cobalah CommaFeed dengan akun demo: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Indietro"
|
||||
msgid "Back to log in"
|
||||
msgstr "Torna per accedere"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Estensioni del browser"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Verifica che il feed funzioni"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed successivo elemento non letto"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Versione CommaFeed {versione} ({revisione})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Commuta lo stato di lettura della voce corrente"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prova CommaFeed con il conto demo: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "裏"
|
||||
msgid "Back to log in"
|
||||
msgstr "ログインに戻る"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "ブラウザ拡張機能"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
@@ -163,13 +167,17 @@ msgstr "パスワードを変更すると、新しい API キーが生成され
|
||||
msgid "Check that the feed is working"
|
||||
msgstr "フィードが動作していることを確認してください"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "次の未読アイテムをカンマフィード"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "コンマフィードのバージョン {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "テーマ"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "現在のエントリの読み取りステータスを切り替えます"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "デモアカウントで CommaFeed を試す: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "뒤로"
|
||||
msgid "Back to log in"
|
||||
msgstr "로그인으로 돌아가기"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "브라우저 확장"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
@@ -163,13 +167,17 @@ msgstr "비밀번호를 변경하면 새 API 키가 생성됩니다."
|
||||
msgid "Check that the feed is working"
|
||||
msgstr "피드가 작동하는지 확인"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "다음 읽지 않은 항목을 쉼표로 피드"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "쉼표 피드 버전 {버전}({개정})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "테마"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "현재 항목의 읽기 상태 전환"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Kembali"
|
||||
msgid "Back to log in"
|
||||
msgstr "Kembali untuk log masuk"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Peluasan penyemak imbas"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Semak sama ada suapan berfungsi"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed item belum dibaca seterusnya"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Versi CommaFeed {versi} ({semakan})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Togol status bacaan entri semasa"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Cuba CommaFeed dengan akaun demo: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Tilbake"
|
||||
msgid "Back to log in"
|
||||
msgstr "Tilbake for å logge inn"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Nettleserutvidelser"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Sjekk at feeden fungerer"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed neste uleste element"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed versjon {versjon} ({revisjon})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Veksle lesestatus for gjeldende oppføring"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Terug"
|
||||
msgid "Back to log in"
|
||||
msgstr "Terug naar inloggen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Browserextensies"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Controleer of de feed werkt"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed volgende ongelezen item"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed-versie {versie} ({revisie})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Thema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Toggle leesstatus van huidige invoer"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Probeer CommaFeed uit met het demo-account: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Tilbake"
|
||||
msgid "Back to log in"
|
||||
msgstr "Tilbake for å logge inn"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Nettleserutvidelser"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Sjekk at feeden fungerer"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed neste uleste element"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed versjon {versjon} ({revisjon})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Veksle lesestatus for gjeldende oppføring"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Powrót"
|
||||
msgid "Back to log in"
|
||||
msgstr "Powrót do logowania"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Rozszerzenia przeglądarki"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Sprawdź, czy kanał działa"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "Przecinek następny nieprzeczytany element"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Wersja CommaFeed {wersja} ({wersja})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Motyw"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Przełącz stan odczytu bieżącego wpisu"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Voltar"
|
||||
msgid "Back to log in"
|
||||
msgstr "Voltar para logar"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Extensões do navegador"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Verifique se o feed está funcionando"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed próximo item não lido"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "Versão do CommaFeed {versão} ({revisão})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Alternar o status de leitura da entrada atual"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Experimente o CommaFeed com a conta demo: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Назад"
|
||||
msgid "Back to log in"
|
||||
msgstr "Вернуться к входу"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Расширения браузера"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
@@ -163,13 +167,17 @@ msgstr "При изменении пароля будет сгенерирова
|
||||
msgid "Check that the feed is working"
|
||||
msgstr "Проверьте, работает ли лента."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed следующий непрочитанный элемент"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed версия {версия} ({редакция})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Тема"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Переключить статус чтения текущей записи"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Попробуйте CommaFeed на демо-счете: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Späť"
|
||||
msgid "Back to log in"
|
||||
msgstr "Späť na prihlásenie"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Rozšírenia prehliadača"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Skontrolujte, či feed funguje"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed ďalšia neprečítaná položka"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed verzia {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Téma"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Prepne stav čítania aktuálneho záznamu"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Tillbaka"
|
||||
msgid "Back to log in"
|
||||
msgstr "Tillbaka för att logga in"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Webbläsartillägg"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
msgstr "Kontrollera att matningen fungerar"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed nästa olästa objekt"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Växla lässtatus för aktuell post"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prova CommaFeed med demokontot: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "Geri"
|
||||
msgid "Back to log in"
|
||||
msgstr "Giriş yapmak için geri dön"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "Tarayıcı uzantıları"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.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"
|
||||
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
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed sonraki okunmamış öğe"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed sürümü {sürüm} ({revizyon})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "Tema"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "Geçerli girişin okuma durumunu değiştir"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo"
|
||||
|
||||
@@ -127,9 +127,13 @@ msgstr "返回"
|
||||
msgid "Back to log in"
|
||||
msgstr "返回登录"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Browser extension required for Chrome"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "Browser extentions"
|
||||
msgstr "浏览器扩展"
|
||||
msgid "Browser extention"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
@@ -163,13 +167,17 @@ msgstr "更改密码将生成新的 API 密钥"
|
||||
msgid "Check that the feed is working"
|
||||
msgstr "检查提要是否正常工作"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed 下一个未读项目"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed version {version} ({revision})"
|
||||
msgstr "CommaFeed 版本 {version} ({revision})"
|
||||
msgid "CommaFeed version {version} ({revision})."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/header/ProfileMenu.tsx
|
||||
msgid "Compact"
|
||||
@@ -793,6 +801,10 @@ msgstr "主题"
|
||||
msgid "Toggle read status of current entry"
|
||||
msgstr "切换当前条目的读取状态"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "使用演示帐户试用 CommaFeed:demo/demo"
|
||||
|
||||
@@ -63,7 +63,7 @@ function Buttons() {
|
||||
const iconSize = 18
|
||||
const serverInfos = useAppSelector(state => state.server.serverInfos)
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme()
|
||||
const { isBrowserExtension, openSettingsPage } = useBrowserExtension()
|
||||
const { isBrowserExtensionPopup, openSettingsPage } = useBrowserExtension()
|
||||
const dispatch = useAppDispatch()
|
||||
const dark = colorScheme === "dark"
|
||||
|
||||
@@ -108,7 +108,7 @@ function Buttons() {
|
||||
hideLabelOnDesktop
|
||||
/>
|
||||
|
||||
{isBrowserExtension && (
|
||||
{isBrowserExtensionPopup && (
|
||||
<ActionButton
|
||||
label={<Trans>Extension options</Trans>}
|
||||
icon={<TbSettings size={iconSize} />}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { redirectToApiDocumentation } from "app/slices/redirect"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { CategorySelect } from "components/content/add/CategorySelect"
|
||||
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
||||
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
||||
import React, { useState } from "react"
|
||||
import { TbHelp, TbKeyboard, TbPuzzle, TbRocket } from "react-icons/tb"
|
||||
|
||||
@@ -60,16 +61,23 @@ function NextUnreadBookmarklet() {
|
||||
export function AboutPage() {
|
||||
const version = useAppSelector(state => state.server.serverInfos?.version)
|
||||
const revision = useAppSelector(state => state.server.serverInfos?.gitCommit)
|
||||
const { isBrowserExtensionInstalled, browserExtensionVersion, isBrowserExtensionInstallable } = useBrowserExtension()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return (
|
||||
<Container size="xl">
|
||||
<SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}>
|
||||
<Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}>
|
||||
<Box>
|
||||
<Trans>
|
||||
CommaFeed version {version} ({revision})
|
||||
CommaFeed version {version} ({revision}).
|
||||
</Trans>
|
||||
</Box>
|
||||
{isBrowserExtensionInstallable && isBrowserExtensionInstalled && (
|
||||
<Box>
|
||||
<Trans>CommaFeed browser extension version {browserExtensionVersion}.</Trans>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt="md">
|
||||
<Trans>
|
||||
<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} />}>
|
||||
<List>
|
||||
<List.Item>
|
||||
<Anchor href="https://github.com/Athou/commafeed-browser-extension" target="_blank" rel="noreferrer">
|
||||
<Trans>Browser extentions</Trans>
|
||||
<Anchor href={Constants.browserExtensionUrl} target="_blank" rel="noreferrer">
|
||||
<Trans>Browser extention</Trans>
|
||||
</Anchor>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Box } from "@mantine/core"
|
||||
import SwaggerUI from "swagger-ui-react"
|
||||
import "swagger-ui-react/swagger-ui.css"
|
||||
|
||||
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
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
Title,
|
||||
useMantineTheme,
|
||||
} from "@mantine/core"
|
||||
import { useViewportSize } from "@mantine/hooks"
|
||||
import { useMediaQuery, useViewportSize } from "@mantine/hooks"
|
||||
import { Constants } from "app/constants"
|
||||
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 { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { Loader } from "components/Loader"
|
||||
@@ -26,28 +26,39 @@ import { OnMobile } from "components/responsive/OnMobile"
|
||||
import { useAppLoading } from "hooks/useAppLoading"
|
||||
import { useWebSocket } from "hooks/useWebSocket"
|
||||
import { LoadingPage } from "pages/LoadingPage"
|
||||
import { Resizable } from "re-resizable"
|
||||
import { ReactNode, Suspense, useEffect } from "react"
|
||||
import { TbPlus } from "react-icons/tb"
|
||||
import { Outlet } from "react-router-dom"
|
||||
|
||||
interface LayoutProps {
|
||||
sidebar: ReactNode
|
||||
sidebarWidth: number
|
||||
header: ReactNode
|
||||
}
|
||||
|
||||
const sidebarPadding = DEFAULT_THEME.spacing.xs
|
||||
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: {
|
||||
maxWidth: `calc(${Constants.layout.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
|
||||
maxWidth: `calc(${props.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
|
||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
||||
maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
|
||||
},
|
||||
},
|
||||
mainContentWrapper: {
|
||||
paddingTop: Constants.layout.headerHeight,
|
||||
paddingLeft: Constants.layout.sidebarWidth,
|
||||
paddingLeft: props.sidebarWidth,
|
||||
paddingRight: 0,
|
||||
paddingBottom: 0,
|
||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
||||
@@ -55,7 +66,7 @@ const useStyles = createStyles(theme => ({
|
||||
},
|
||||
},
|
||||
mainContent: {
|
||||
maxWidth: `calc(100vw - ${Constants.layout.sidebarWidth}px)`,
|
||||
maxWidth: `calc(100vw - ${props.sidebarWidth}px)`,
|
||||
padding: theme.spacing.md,
|
||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
||||
maxWidth: "100vw",
|
||||
@@ -76,15 +87,19 @@ function LogoAndTitle() {
|
||||
)
|
||||
}
|
||||
|
||||
export default function Layout({ sidebar, header }: LayoutProps) {
|
||||
const { classes } = useStyles()
|
||||
export default function Layout(props: LayoutProps) {
|
||||
const { classes } = useStyles(props)
|
||||
const theme = useMantineTheme()
|
||||
const viewport = useViewportSize()
|
||||
const { loading } = useAppLoading()
|
||||
const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint})`)
|
||||
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
||||
const sidebarHidden = props.sidebarWidth === 0
|
||||
const dispatch = useAppDispatch()
|
||||
useWebSocket()
|
||||
|
||||
const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth))
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(reloadSettings())
|
||||
dispatch(reloadProfile())
|
||||
@@ -122,13 +137,29 @@ export default function Layout({ sidebar, header }: LayoutProps) {
|
||||
navbar={
|
||||
<Navbar
|
||||
id="sidebar"
|
||||
p={sidebarPadding}
|
||||
hiddenBreakpoint={Constants.layout.mobileBreakpoint}
|
||||
hidden={!mobileMenuOpen}
|
||||
width={{ md: Constants.layout.sidebarWidth }}
|
||||
hiddenBreakpoint={sidebarHidden ? 99999999 : Constants.layout.mobileBreakpoint}
|
||||
hidden={sidebarHidden || !mobileMenuOpen}
|
||||
width={{ md: props.sidebarWidth }}
|
||||
className={classes.sidebar}
|
||||
>
|
||||
<Navbar.Section grow component={ScrollArea} mx="-xs" px="xs">
|
||||
<Box className={classes.sidebarContent}>{sidebar}</Box>
|
||||
<Navbar.Section grow component={ScrollArea} mx={mobile ? 0 : "-sm"} px={mobile ? 0 : "sm"}>
|
||||
<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>
|
||||
}
|
||||
@@ -147,19 +178,19 @@ export default function Layout({ sidebar, header }: LayoutProps) {
|
||||
{!mobileMenuOpen && (
|
||||
<Group>
|
||||
<Box mr="sm">{burger}</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>{header}</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>{props.header}</Box>
|
||||
</Group>
|
||||
)}
|
||||
</OnMobile>
|
||||
<OnDesktop>
|
||||
<Group>
|
||||
<Group position="apart" sx={{ width: Constants.layout.sidebarWidth - 16 }}>
|
||||
<Group position="apart" sx={{ width: props.sidebarWidth - 16 }}>
|
||||
<Box>
|
||||
<LogoAndTitle />
|
||||
</Box>
|
||||
<Box>{addButton}</Box>
|
||||
</Group>
|
||||
<Box sx={{ flexGrow: 1 }}>{header}</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>{props.header}</Box>
|
||||
</Group>
|
||||
</OnDesktop>
|
||||
</Header>
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
app:
|
||||
# url used to access commafeed
|
||||
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
|
||||
allowRegistrations: true
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
app:
|
||||
# url used to access commafeed
|
||||
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
|
||||
allowRegistrations: false
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.7.0</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-server</artifactId>
|
||||
<name>CommaFeed Server</name>
|
||||
@@ -226,7 +226,7 @@
|
||||
<dependency>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.7.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -47,6 +47,7 @@ import com.commafeed.frontend.servlet.CustomCssServlet;
|
||||
import com.commafeed.frontend.servlet.CustomJsServlet;
|
||||
import com.commafeed.frontend.servlet.LogoutServlet;
|
||||
import com.commafeed.frontend.servlet.NextUnreadServlet;
|
||||
import com.commafeed.frontend.servlet.RobotsTxtDisallowAllServlet;
|
||||
import com.commafeed.frontend.session.SessionHelperFactoryProvider;
|
||||
import com.commafeed.frontend.ws.WebSocketConfigurator;
|
||||
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("customJs", injector.getInstance(CustomJsServlet.class)).addMapping("/custom_js.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
|
||||
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws")
|
||||
|
||||
@@ -65,6 +65,10 @@ public class CommaFeedConfiguration extends Configuration {
|
||||
@Valid
|
||||
private String publicUrl;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private Boolean hideFromWebCrawlers = true;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private Boolean allowRegistrations;
|
||||
|
||||
@@ -15,23 +15,30 @@ import com.commafeed.backend.model.QFeed;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedDAO extends GenericDAO<Feed> {
|
||||
|
||||
private final QFeed feed = QFeed.feed;
|
||||
private final QFeedSubscription subscription = QFeedSubscription.feedSubscription;
|
||||
|
||||
@Inject
|
||||
public FeedDAO(SessionFactory sessionFactory) {
|
||||
super(sessionFactory);
|
||||
}
|
||||
|
||||
public List<Feed> findNextUpdatable(int count) {
|
||||
return query().selectFrom(feed)
|
||||
.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date())))
|
||||
.orderBy(feed.disabledUntil.asc())
|
||||
.limit(count)
|
||||
.fetch();
|
||||
public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) {
|
||||
JPAQuery<Feed> query = query().selectFrom(feed).where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date())));
|
||||
if (lastLoginThreshold != null) {
|
||||
query.where(JPAExpressions.selectOne()
|
||||
.from(subscription)
|
||||
.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) {
|
||||
|
||||
@@ -86,10 +86,12 @@ public class FeedRefreshEngine implements Managed {
|
||||
Feed feed = queue.take();
|
||||
|
||||
// send the feed to be processed
|
||||
log.debug("got feed {} from the queue, send it for processing", feed.getId());
|
||||
processFeedAsync(feed);
|
||||
|
||||
// we removed a feed from the queue, try to refill it as it may now be empty
|
||||
if (queue.isEmpty()) {
|
||||
log.debug("took the last feed from the queue, try to refill");
|
||||
refillQueueAsync();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
@@ -108,9 +110,11 @@ public class FeedRefreshEngine implements Managed {
|
||||
while (!refillLoopExecutor.isShutdown()) {
|
||||
try {
|
||||
if (queue.isEmpty()) {
|
||||
log.debug("refilling queue");
|
||||
refillQueueAsync();
|
||||
}
|
||||
|
||||
log.debug("sleeping for 15s");
|
||||
TimeUnit.SECONDS.sleep(15);
|
||||
} catch (InterruptedException e) {
|
||||
log.debug("interrupted while sleeping");
|
||||
@@ -123,6 +127,7 @@ public class FeedRefreshEngine implements Managed {
|
||||
}
|
||||
|
||||
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
|
||||
queue.removeIf(f -> f.getId().equals(feed.getId()));
|
||||
queue.addFirst(feed);
|
||||
@@ -136,7 +141,9 @@ public class FeedRefreshEngine implements Managed {
|
||||
|
||||
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
|
||||
if (queue.stream().noneMatch(f -> f.getId().equals(feed.getId()))) {
|
||||
queue.addLast(feed);
|
||||
@@ -161,7 +168,10 @@ public class FeedRefreshEngine implements Managed {
|
||||
|
||||
private List<Feed> getNextUpdatableFeeds(int max) {
|
||||
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()
|
||||
Date nextUpdateDate = DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes());
|
||||
feedDAO.setDisabledUntil(feeds.stream().map(AbstractModel::getId).collect(Collectors.toList()), nextUpdateDate);
|
||||
|
||||
@@ -38,6 +38,6 @@ public class FeedCategory extends AbstractModel {
|
||||
|
||||
private boolean collapsed;
|
||||
|
||||
private Integer position;
|
||||
private int position;
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class FeedSubscription extends AbstractModel {
|
||||
@OneToMany(mappedBy = "subscription", cascade = CascadeType.REMOVE)
|
||||
private Set<FeedEntryStatus> statuses;
|
||||
|
||||
private Integer position;
|
||||
private int position;
|
||||
|
||||
@Column(name = "filtering_expression", length = 4096)
|
||||
private String filter;
|
||||
|
||||
@@ -8,8 +8,6 @@ import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -49,17 +47,4 @@ public class User extends AbstractModel {
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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) {
|
||||
return feedSubscriptionDAO.findAll(user).stream().collect(Collectors.toMap(FeedSubscription::getId, s -> getUnreadCount(user, s)));
|
||||
}
|
||||
|
||||
@@ -25,25 +25,20 @@ public class PostLoginActivities {
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
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();
|
||||
|
||||
boolean saveUser = false;
|
||||
|
||||
// only update lastLogin field every hour in order to not
|
||||
// invalidate the cache every time someone logs in
|
||||
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
|
||||
Date lastLogin = user.getLastLogin();
|
||||
if (lastLogin == null || lastLogin.before(DateUtils.addMinutes(now, -30))) {
|
||||
user.setLastLogin(now);
|
||||
saveUser = true;
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad()) && user.shouldRefreshFeedsAt(now)) {
|
||||
feedSubscriptionService.refreshAll(user);
|
||||
user.setLastFullRefresh(now);
|
||||
saveUser = true;
|
||||
}
|
||||
boolean heavyLoad = Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad());
|
||||
if (heavyLoad) {
|
||||
// the amount of feeds in the database that are up for refresh might be very large since we're in heavy load mode
|
||||
// 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.
|
||||
// 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
|
||||
@@ -51,5 +46,4 @@ public class PostLoginActivities {
|
||||
unitOfWork.run(() -> userDAO.saveOrUpdate(user));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@ public class Category implements Serializable {
|
||||
private boolean expanded;
|
||||
|
||||
@ApiModelProperty(value = "position of the category in the list", required = true)
|
||||
private Integer position;
|
||||
private int position;
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public class Subscription implements Serializable {
|
||||
private String categoryId;
|
||||
|
||||
@ApiModelProperty("position of the subscription's in the list")
|
||||
private Integer position;
|
||||
private int position;
|
||||
|
||||
@ApiModelProperty(value = "date of the newest item", dataType = "number")
|
||||
private Date newestItemTime;
|
||||
|
||||
@@ -458,7 +458,7 @@ public class CategoryREST {
|
||||
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) {
|
||||
if (id == null && subscription.getCategory() == null
|
||||
@@ -468,7 +468,7 @@ public class CategoryREST {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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: /");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -19,5 +19,6 @@
|
||||
<include file="changelogs/db.changelog-2.6.xml" />
|
||||
<include file="changelogs/db.changelog-3.2.xml" />
|
||||
<include file="changelogs/db.changelog-3.5.xml" />
|
||||
<include file="changelogs/db.changelog-3.6.xml" />
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -3,6 +3,9 @@
|
||||
app:
|
||||
# url used to access commafeed
|
||||
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
|
||||
allowRegistrations: true
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.7.0</version>
|
||||
<name>CommaFeed</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ if [[ "$BRANCH" != "master" ]]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# make sure README.md has been updated
|
||||
read -r -p "Has README.md been updated? (Y/n) " CONFIRM
|
||||
# make sure CHANGELOG.md has been updated
|
||||
read -r -p "Has CHANGELOG.md been updated? (Y/n) " CONFIRM
|
||||
case "$CONFIRM" in
|
||||
n | N) exit ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user