forked from Archives/Athou_commafeed
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b51de8e5b | ||
|
|
0ba70d29bd | ||
|
|
197b3b258b | ||
|
|
850f66999c | ||
|
|
d7d3574e36 | ||
|
|
435d612cbf | ||
|
|
3d3a7c6496 | ||
|
|
fba57fe0a7 | ||
|
|
ce7933f320 | ||
|
|
8ac452afc9 | ||
|
|
a11cb3ac7a | ||
|
|
39808bbafc | ||
|
|
aee56e3dbe | ||
|
|
40f451c762 | ||
|
|
d633803ab5 | ||
|
|
d7a3b75687 | ||
|
|
df8c4056b6 | ||
|
|
06319c1eb0 | ||
|
|
b7ede8eba2 | ||
|
|
1a4517d6a3 | ||
|
|
a402c5d7d8 | ||
|
|
408809787e | ||
|
|
d7b0d572c1 | ||
|
|
b356be3e6f | ||
|
|
998385334b | ||
|
|
c6d613d81a | ||
|
|
9981d8763d | ||
|
|
b37680333c | ||
|
|
66d1eb3f1f | ||
|
|
6fe1c2a3c0 | ||
|
|
c2e453027c | ||
|
|
f16bac9b59 | ||
|
|
8cca826e70 | ||
|
|
b0165bb26a | ||
|
|
366294ab46 | ||
|
|
2988938440 | ||
|
|
e865769e30 | ||
|
|
f87be2fc03 | ||
|
|
466846d268 | ||
|
|
61b6be4090 | ||
|
|
cb779ec494 | ||
|
|
da6f2050f9 | ||
|
|
4304f84a55 | ||
|
|
8a175d8221 | ||
|
|
f1896d34e2 | ||
|
|
45d0e0ec98 | ||
|
|
38c5beec2f | ||
|
|
c4715dc3f7 | ||
|
|
6ce6b5ef0e | ||
|
|
1af3dd452c | ||
|
|
1f4ec41222 | ||
|
|
512c4cc507 | ||
|
|
d391c8f1c9 | ||
|
|
46d3e67aec | ||
|
|
d9505c4d87 | ||
|
|
42491f5778 | ||
|
|
9c897c9fb2 | ||
|
|
21b500a96e | ||
|
|
04c74b5daa | ||
|
|
3edb8a3ee2 | ||
|
|
922346bef6 |
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -7,6 +7,7 @@ exemptLabels:
|
|||||||
- pinned
|
- pinned
|
||||||
- security
|
- security
|
||||||
- enhancement
|
- enhancement
|
||||||
|
- bug
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
staleLabel: wontfix
|
staleLabel: wontfix
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [ "8", "11", "17" ]
|
java: [ "8", "11", "17", "21" ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,5 +1,41 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [3.10.1]
|
||||||
|
|
||||||
|
- swap next and previous buttons (#1159)
|
||||||
|
- unread count for subscriptions will now be shortened starting at 10k instead of 1k
|
||||||
|
- increased websocket ping interval to just under a minute to reduce data and battery usage on mobile
|
||||||
|
- only refresh subscription tree on a timer if websocket connection is unavailable
|
||||||
|
- the Docker image now uses less memory by returning unused memory to the OS
|
||||||
|
- add support for Java 21
|
||||||
|
|
||||||
|
## [3.10.0]
|
||||||
|
|
||||||
|
- added a Fever-compatible API that is usable with mobile clients that support the Fever API (see instructions in Settings -> Profile)
|
||||||
|
- long entry titles are no longer shortened in the detailed view
|
||||||
|
- added the "s" keyboard shortcut to star/unstar entries
|
||||||
|
- http sessions are now stored in the database (they were stored on disk before)
|
||||||
|
- fixed an issue that made it impossible to override the database url in a config.yml mounted in the Docker image
|
||||||
|
|
||||||
|
## [3.9.0]
|
||||||
|
|
||||||
|
- improve performance by disabling the loader when nothing is loading (most noticeable on mobile)
|
||||||
|
- added a setting to disable the 'mark all as read' confirmation
|
||||||
|
- added a setting to disable the custom context menu
|
||||||
|
- if the custom context is enabled, it can still be disabled by pressing the shift key
|
||||||
|
- the announcement feature is now working again and supports html ('announcement' configuration element in config.yml)
|
||||||
|
- add support for MariaDB 11+
|
||||||
|
- fix entry header shortly rendered as mobile on desktop, causing a small visual glitch
|
||||||
|
- fix an issue that could cause a feed to not refresh correctly if the url was very long
|
||||||
|
- database cleanup batch size is now configurable
|
||||||
|
- css parsing errors are no longer logged to the standard output
|
||||||
|
- fix small errors in the api documentation
|
||||||
|
|
||||||
|
## [3.8.1]
|
||||||
|
|
||||||
|
- in expanded mode, don't scroll when clicking on the body of the current entry
|
||||||
|
- improve content cleanup task performance for instances with a very large number of feeds
|
||||||
|
|
||||||
## [3.8.0]
|
## [3.8.0]
|
||||||
|
|
||||||
- add previous and next buttons in the toolbar
|
- add previous and next buttons in the toolbar
|
||||||
@@ -90,10 +126,9 @@
|
|||||||
## [3.0.1]
|
## [3.0.1]
|
||||||
|
|
||||||
- allow env variable substitution in config.yml
|
- allow env variable substitution in config.yml
|
||||||
- e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with
|
- e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with its value
|
||||||
its value
|
|
||||||
- allow env variable prefixed with `CF_` to override config.yml properties
|
- allow env variable prefixed with `CF_` to override config.yml properties
|
||||||
- e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true`
|
- e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true`
|
||||||
|
|
||||||
## [3.0.0]
|
## [3.0.0]
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ EXPOSE 8082
|
|||||||
|
|
||||||
RUN mkdir -p /commafeed/data
|
RUN mkdir -p /commafeed/data
|
||||||
VOLUME /commafeed/data
|
VOLUME /commafeed/data
|
||||||
ENV CF_SESSION_PATH=/commafeed/data/sessions
|
|
||||||
ENV CF_DATABASE_URL=jdbc:h2:/commafeed/data/db
|
|
||||||
|
|
||||||
COPY commafeed-server/config.yml.example config.yml
|
COPY commafeed-server/config.yml.example config.yml
|
||||||
COPY commafeed-server/target/commafeed.jar .
|
COPY commafeed-server/target/commafeed.jar .
|
||||||
|
|
||||||
CMD ["java", "-Djava.net.preferIPv4Stack=true", "-jar", "commafeed.jar", "server", "config.yml"]
|
ENV JAVA_TOOL_OPTIONS -Djava.net.preferIPv4Stack=true -Xms20m -XX:+UseG1GC -XX:-ShrinkHeapInSteps -XX:G1PeriodicGCInterval=10000 -XX:-G1PeriodicGCInvokesConcurrent -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10
|
||||||
|
CMD ["java", "-jar", "commafeed.jar", "server", "config.yml"]
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -7,22 +7,32 @@ Google Reader inspired self-hosted RSS reader, based on Dropwizard and React/Typ
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 4 different layouts
|
- 4 different layouts
|
||||||
- Dark theme
|
- Light/Dark theme
|
||||||
- Fully responsive
|
- Fully responsive
|
||||||
- Keyboard shortcuts for almost everything
|
- Keyboard shortcuts for almost everything
|
||||||
- Support for right-to-left feeds
|
- Support for right-to-left feeds
|
||||||
- Translated in 25+ languages
|
- Translated in 25+ languages
|
||||||
- Supports thousands of users and millions of feeds
|
- Supports thousands of users and millions of feeds
|
||||||
- OPML import/export
|
- OPML import/export
|
||||||
- REST API
|
- REST API and a Fever-compatible API for native mobile apps
|
||||||
- [Browser extension](https://github.com/Athou/commafeed-browser-extension)
|
- [Browser extension](https://github.com/Athou/commafeed-browser-extension)
|
||||||
|
|
||||||
## Deployment on your own server
|
## Deployment
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
|
Docker is the easiest way to get started with CommaFeed.
|
||||||
|
|
||||||
Docker images are built automatically and are available at https://hub.docker.com/r/athou/commafeed
|
Docker images are built automatically and are available at https://hub.docker.com/r/athou/commafeed
|
||||||
|
|
||||||
|
### Cloud hosting
|
||||||
|
|
||||||
|
[PikaPods](https://www.pikapods.com) offers 1-click cloud hosting solutions starting at $1/month with a free $5
|
||||||
|
welcome credit and officially supports CommaFeed.
|
||||||
|
PikaPods shares 20% of the revenue back to CommaFeed.
|
||||||
|
|
||||||
|
[](https://www.pikapods.com/pods?run=commafeed)
|
||||||
|
|
||||||
### Download precompiled package
|
### Download precompiled package
|
||||||
|
|
||||||
mkdir commafeed && cd commafeed
|
mkdir commafeed && cd commafeed
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commafeed-client</artifactId>
|
<artifactId>commafeed-client</artifactId>
|
||||||
<name>CommaFeed Client</name>
|
<name>CommaFeed Client</name>
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ describe("entries", () => {
|
|||||||
sourceWebsiteUrl: "",
|
sourceWebsiteUrl: "",
|
||||||
entries: [{ id: "3" } as Entry],
|
entries: [{ id: "3" } as Entry],
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
|
loading: false,
|
||||||
scrollingToEntry: false,
|
scrollingToEntry: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -102,6 +103,7 @@ describe("entries", () => {
|
|||||||
sourceWebsiteUrl: "",
|
sourceWebsiteUrl: "",
|
||||||
entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry],
|
entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry],
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
|
loading: false,
|
||||||
scrollingToEntry: false,
|
scrollingToEntry: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -128,6 +130,7 @@ describe("entries", () => {
|
|||||||
sourceWebsiteUrl: "",
|
sourceWebsiteUrl: "",
|
||||||
entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry],
|
entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry],
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
|
loading: false,
|
||||||
scrollingToEntry: false,
|
scrollingToEntry: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface EntriesState {
|
|||||||
timestamp?: number
|
timestamp?: number
|
||||||
selectedEntryId?: string
|
selectedEntryId?: string
|
||||||
hasMore: boolean
|
hasMore: boolean
|
||||||
|
loading: boolean
|
||||||
search?: string
|
search?: string
|
||||||
scrollingToEntry: boolean
|
scrollingToEntry: boolean
|
||||||
}
|
}
|
||||||
@@ -40,6 +41,7 @@ const initialState: EntriesState = {
|
|||||||
sourceWebsiteUrl: "",
|
sourceWebsiteUrl: "",
|
||||||
entries: [],
|
entries: [],
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
|
loading: false,
|
||||||
scrollingToEntry: false,
|
scrollingToEntry: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +331,10 @@ export const entriesSlice = createSlice({
|
|||||||
state.sourceWebsiteUrl = ""
|
state.sourceWebsiteUrl = ""
|
||||||
state.hasMore = true
|
state.hasMore = true
|
||||||
state.selectedEntryId = undefined
|
state.selectedEntryId = undefined
|
||||||
|
state.loading = true
|
||||||
|
})
|
||||||
|
builder.addCase(loadMoreEntries.pending, state => {
|
||||||
|
state.loading = true
|
||||||
})
|
})
|
||||||
builder.addCase(loadEntries.fulfilled, (state, action) => {
|
builder.addCase(loadEntries.fulfilled, (state, action) => {
|
||||||
state.entries = action.payload.entries
|
state.entries = action.payload.entries
|
||||||
@@ -336,12 +342,14 @@ export const entriesSlice = createSlice({
|
|||||||
state.sourceLabel = action.payload.name
|
state.sourceLabel = action.payload.name
|
||||||
state.sourceWebsiteUrl = action.payload.feedLink
|
state.sourceWebsiteUrl = action.payload.feedLink
|
||||||
state.hasMore = action.payload.hasMore
|
state.hasMore = action.payload.hasMore
|
||||||
|
state.loading = false
|
||||||
})
|
})
|
||||||
builder.addCase(loadMoreEntries.fulfilled, (state, action) => {
|
builder.addCase(loadMoreEntries.fulfilled, (state, action) => {
|
||||||
// remove already existing entries
|
// remove already existing entries
|
||||||
const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id))
|
const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id))
|
||||||
state.entries = [...state.entries, ...entriesToAdd]
|
state.entries = [...state.entries, ...entriesToAdd]
|
||||||
state.hasMore = action.payload.hasMore
|
state.hasMore = action.payload.hasMore
|
||||||
|
state.loading = false
|
||||||
})
|
})
|
||||||
builder.addCase(tagEntry.pending, (state, action) => {
|
builder.addCase(tagEntry.pending, (state, action) => {
|
||||||
state.entries
|
state.entries
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"
|
import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { ServerInfo } from "app/types"
|
import { ServerInfo } from "app/types"
|
||||||
|
|
||||||
interface ServerState {
|
interface ServerState {
|
||||||
serverInfos?: ServerInfo
|
serverInfos?: ServerInfo
|
||||||
|
webSocketConnected: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ServerState = {}
|
const initialState: ServerState = {
|
||||||
|
webSocketConnected: false,
|
||||||
|
}
|
||||||
|
|
||||||
export const reloadServerInfos = createAsyncThunk("server/infos", () => client.server.getServerInfos().then(r => r.data))
|
export const reloadServerInfos = createAsyncThunk("server/infos", () => client.server.getServerInfos().then(r => r.data))
|
||||||
export const serverSlice = createSlice({
|
export const serverSlice = createSlice({
|
||||||
name: "server",
|
name: "server",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {},
|
reducers: {
|
||||||
|
setWebSocketConnected: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.webSocketConnected = action.payload
|
||||||
|
},
|
||||||
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
builder.addCase(reloadServerInfos.fulfilled, (state, action) => {
|
builder.addCase(reloadServerInfos.fulfilled, (state, action) => {
|
||||||
state.serverInfos = action.payload
|
state.serverInfos = action.payload
|
||||||
@@ -20,4 +27,5 @@ export const serverSlice = createSlice({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const { setWebSocketConnected } = serverSlice.actions
|
||||||
export default serverSlice.reducer
|
export default serverSlice.reducer
|
||||||
|
|||||||
@@ -91,6 +91,28 @@ export const changeAlwaysScrollToEntry = createAsyncThunk<
|
|||||||
if (!settings) return
|
if (!settings) return
|
||||||
client.user.saveSettings({ ...settings, alwaysScrollToEntry })
|
client.user.saveSettings({ ...settings, alwaysScrollToEntry })
|
||||||
})
|
})
|
||||||
|
export const changeMarkAllAsReadConfirmation = createAsyncThunk<
|
||||||
|
void,
|
||||||
|
boolean,
|
||||||
|
{
|
||||||
|
state: RootState
|
||||||
|
}
|
||||||
|
>("settings/markAllAsReadConfirmation", (markAllAsReadConfirmation, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
|
||||||
|
})
|
||||||
|
export const changeCustomContextMenu = createAsyncThunk<
|
||||||
|
void,
|
||||||
|
boolean,
|
||||||
|
{
|
||||||
|
state: RootState
|
||||||
|
}
|
||||||
|
>("settings/customContextMenu", (customContextMenu, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, customContextMenu })
|
||||||
|
})
|
||||||
export const changeSharingSetting = createAsyncThunk<
|
export const changeSharingSetting = createAsyncThunk<
|
||||||
void,
|
void,
|
||||||
{ site: keyof SharingSettings; value: boolean },
|
{ site: keyof SharingSettings; value: boolean },
|
||||||
@@ -151,6 +173,14 @@ export const userSlice = createSlice({
|
|||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.alwaysScrollToEntry = action.meta.arg
|
state.settings.alwaysScrollToEntry = action.meta.arg
|
||||||
})
|
})
|
||||||
|
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.markAllAsReadConfirmation = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeCustomContextMenu.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.customContextMenu = action.meta.arg
|
||||||
|
})
|
||||||
builder.addCase(changeSharingSetting.pending, (state, action) => {
|
builder.addCase(changeSharingSetting.pending, (state, action) => {
|
||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
||||||
@@ -162,6 +192,8 @@ export const userSlice = createSlice({
|
|||||||
changeShowRead.fulfilled,
|
changeShowRead.fulfilled,
|
||||||
changeScrollMarks.fulfilled,
|
changeScrollMarks.fulfilled,
|
||||||
changeAlwaysScrollToEntry.fulfilled,
|
changeAlwaysScrollToEntry.fulfilled,
|
||||||
|
changeMarkAllAsReadConfirmation.fulfilled,
|
||||||
|
changeCustomContextMenu.fulfilled,
|
||||||
changeSharingSetting.fulfilled
|
changeSharingSetting.fulfilled
|
||||||
),
|
),
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -3,38 +3,6 @@ export interface AddCategoryRequest {
|
|||||||
parentId?: string
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationSettings {
|
|
||||||
publicUrl: string
|
|
||||||
allowRegistrations: boolean
|
|
||||||
createDemoAccount: boolean
|
|
||||||
googleAnalyticsTrackingCode?: string
|
|
||||||
googleAuthKey?: string
|
|
||||||
backgroundThreads: number
|
|
||||||
databaseUpdateThreads: number
|
|
||||||
smtpHost?: string
|
|
||||||
smtpPort?: number
|
|
||||||
smtpTls?: boolean
|
|
||||||
smtpUserName?: string
|
|
||||||
smtpPassword?: string
|
|
||||||
smtpFromAddress?: string
|
|
||||||
graphiteEnabled?: boolean
|
|
||||||
graphitePrefix?: string
|
|
||||||
graphiteHost?: string
|
|
||||||
graphitePort?: number
|
|
||||||
graphiteInterval?: number
|
|
||||||
heavyLoad: boolean
|
|
||||||
pubsubhubbub: boolean
|
|
||||||
imageProxyEnabled: boolean
|
|
||||||
queryTimeout: number
|
|
||||||
keepStatusDays: number
|
|
||||||
maxFeedCapacity: number
|
|
||||||
refreshIntervalMinutes: number
|
|
||||||
cache: ApplicationSettingsCache
|
|
||||||
announcement?: string
|
|
||||||
userAgent?: string
|
|
||||||
unreadThreshold?: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
id: string
|
id: string
|
||||||
parentId?: string
|
parentId?: string
|
||||||
@@ -234,6 +202,8 @@ export interface Settings {
|
|||||||
customJs?: string
|
customJs?: string
|
||||||
scrollSpeed: number
|
scrollSpeed: number
|
||||||
alwaysScrollToEntry: boolean
|
alwaysScrollToEntry: boolean
|
||||||
|
markAllAsReadConfirmation: boolean
|
||||||
|
customContextMenu: boolean
|
||||||
sharingSettings: SharingSettings
|
sharingSettings: SharingSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,8 +270,6 @@ export interface UserModel {
|
|||||||
admin: boolean
|
admin: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApplicationSettingsCache = "NOOP" | "REDIS"
|
|
||||||
|
|
||||||
export type ReadingMode = "all" | "unread"
|
export type ReadingMode = "all" | "unread"
|
||||||
|
|
||||||
export type ReadingOrder = "asc" | "desc"
|
export type ReadingOrder = "asc" | "desc"
|
||||||
|
|||||||
36
commafeed-client/src/components/AnnouncementDialog.tsx
Normal file
36
commafeed-client/src/components/AnnouncementDialog.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Trans } from "@lingui/macro"
|
||||||
|
import { Box, Dialog, Text } from "@mantine/core"
|
||||||
|
import { useAppSelector } from "app/store"
|
||||||
|
import { Content } from "components/content/Content"
|
||||||
|
import { useAsync } from "react-async-hook"
|
||||||
|
import useLocalStorage from "use-local-storage"
|
||||||
|
|
||||||
|
const sha256Hex = async (input: string | undefined) => {
|
||||||
|
const data = new TextEncoder().encode(input)
|
||||||
|
const buffer = await crypto.subtle.digest("SHA-256", data)
|
||||||
|
const array = Array.from(new Uint8Array(buffer))
|
||||||
|
return array.map(b => b.toString(16).padStart(2, "0")).join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AnnouncementDialog() {
|
||||||
|
const announcement = useAppSelector(state => state.server.serverInfos?.announcement)
|
||||||
|
const announcementHash = useAsync(sha256Hex, [announcement]).result
|
||||||
|
const [localStorageHash, setLocalStorageHash] = useLocalStorage("announcement-hash", "no-hash")
|
||||||
|
|
||||||
|
const opened = !!announcementHash && announcementHash !== localStorageHash
|
||||||
|
const onClosed = () => setLocalStorageHash(announcementHash)
|
||||||
|
|
||||||
|
if (!announcement) return null
|
||||||
|
return (
|
||||||
|
<Dialog opened={opened} withCloseButton onClose={onClosed} size="xl" radius="md">
|
||||||
|
<Box>
|
||||||
|
<Text weight="bold">
|
||||||
|
<Trans>Announcement</Trans>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Content content={announcement} />
|
||||||
|
</Box>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -113,6 +113,14 @@ export function KeyboardShortcutsHelp() {
|
|||||||
<Trans>Swipe header to the right</Trans>
|
<Trans>Swipe header to the right</Trans>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Trans>Toggle starred status of current entry</Trans>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Kbd>S</Kbd>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Trans>Mark all entries as read</Trans>
|
<Trans>Mark all entries as read</Trans>
|
||||||
@@ -161,6 +169,20 @@ export function KeyboardShortcutsHelp() {
|
|||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Trans>Show native menu (desktop)</Trans>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Kbd>
|
||||||
|
<Trans>Shift</Trans>
|
||||||
|
</Kbd>
|
||||||
|
<span> + </span>
|
||||||
|
<Kbd>
|
||||||
|
<Trans>Right click</Trans>
|
||||||
|
</Kbd>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Trans>Show entry menu (mobile)</Trans>
|
<Trans>Show entry menu (mobile)</Trans>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
|
import { Box } from "@mantine/core"
|
||||||
import { openModal } from "@mantine/modals"
|
import { openModal } from "@mantine/modals"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +11,7 @@ import {
|
|||||||
selectEntry,
|
selectEntry,
|
||||||
selectNextEntry,
|
selectNextEntry,
|
||||||
selectPreviousEntry,
|
selectPreviousEntry,
|
||||||
|
starEntry,
|
||||||
} from "app/slices/entries"
|
} from "app/slices/entries"
|
||||||
import { redirectToRootCategory } from "app/slices/redirect"
|
import { redirectToRootCategory } from "app/slices/redirect"
|
||||||
import { toggleSidebar } from "app/slices/tree"
|
import { toggleSidebar } from "app/slices/tree"
|
||||||
@@ -31,9 +33,11 @@ export function FeedEntries() {
|
|||||||
const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
|
const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
|
||||||
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
|
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
|
||||||
const hasMore = useAppSelector(state => state.entries.hasMore)
|
const hasMore = useAppSelector(state => state.entries.hasMore)
|
||||||
|
const loading = useAppSelector(state => state.entries.loading)
|
||||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||||
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
|
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
|
||||||
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
|
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
|
||||||
|
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
|
||||||
const { viewMode } = useViewMode()
|
const { viewMode } = useViewMode()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { openLinkInBackgroundTab } = useBrowserExtension()
|
const { openLinkInBackgroundTab } = useBrowserExtension()
|
||||||
@@ -62,6 +66,8 @@ export function FeedEntries() {
|
|||||||
|
|
||||||
const contextMenu = useContextMenu()
|
const contextMenu = useContextMenu()
|
||||||
const headerRightClicked = (entry: ExpendableEntry, event: React.MouseEvent) => {
|
const headerRightClicked = (entry: ExpendableEntry, event: React.MouseEvent) => {
|
||||||
|
if (event.shiftKey || !customContextMenu) return
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
id: Constants.dom.entryContextMenuId(entry),
|
id: Constants.dom.entryContextMenuId(entry),
|
||||||
@@ -71,6 +77,10 @@ export function FeedEntries() {
|
|||||||
|
|
||||||
const bodyClicked = (entry: ExpendableEntry) => {
|
const bodyClicked = (entry: ExpendableEntry) => {
|
||||||
if (viewMode !== "expanded") return
|
if (viewMode !== "expanded") return
|
||||||
|
|
||||||
|
// entry is already selected
|
||||||
|
if (entry.id === selectedEntryId) return
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
selectEntry({
|
selectEntry({
|
||||||
entry,
|
entry,
|
||||||
@@ -248,6 +258,11 @@ export function FeedEntries() {
|
|||||||
if (!selectedEntry) return
|
if (!selectedEntry) return
|
||||||
dispatch(markEntry({ entry: selectedEntry, read: !selectedEntry.read }))
|
dispatch(markEntry({ entry: selectedEntry, read: !selectedEntry.read }))
|
||||||
})
|
})
|
||||||
|
useMousetrap("s", () => {
|
||||||
|
// toggle starred status
|
||||||
|
if (!selectedEntry) return
|
||||||
|
dispatch(starEntry({ entry: selectedEntry, starred: !selectedEntry.starred }))
|
||||||
|
})
|
||||||
useMousetrap("shift+a", () => {
|
useMousetrap("shift+a", () => {
|
||||||
// mark all entries as read
|
// mark all entries as read
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -276,9 +291,9 @@ export function FeedEntries() {
|
|||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
id="entries"
|
id="entries"
|
||||||
initialLoad={false}
|
initialLoad={false}
|
||||||
loadMore={() => dispatch(loadMoreEntries())}
|
loadMore={() => !loading && dispatch(loadMoreEntries())}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
loader={<Loader key={0} />}
|
loader={<Box key={0}>{loading && <Loader />}</Box>}
|
||||||
>
|
>
|
||||||
{entries.map(entry => (
|
{entries.map(entry => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Box, createStyles, Text } from "@mantine/core"
|
import { Box, createStyles, Space, Text } from "@mantine/core"
|
||||||
import { Entry } from "app/types"
|
import { Entry } from "app/types"
|
||||||
import { RelativeDate } from "components/RelativeDate"
|
import { RelativeDate } from "components/RelativeDate"
|
||||||
import { FeedEntryTitle } from "./FeedEntryTitle"
|
import { FeedEntryTitle } from "./FeedEntryTitle"
|
||||||
@@ -12,17 +12,11 @@ export interface FeedEntryHeaderProps {
|
|||||||
const useStyles = createStyles((theme, props: FeedEntryHeaderProps) => ({
|
const useStyles = createStyles((theme, props: FeedEntryHeaderProps) => ({
|
||||||
headerText: {
|
headerText: {
|
||||||
fontWeight: theme.colorScheme === "light" && !props.entry.read ? "bold" : "inherit",
|
fontWeight: theme.colorScheme === "light" && !props.entry.read ? "bold" : "inherit",
|
||||||
whiteSpace: props.expanded ? "inherit" : "nowrap",
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
},
|
},
|
||||||
headerSubtext: {
|
headerSubtext: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
fontSize: "90%",
|
fontSize: "90%",
|
||||||
whiteSpace: props.expanded ? "inherit" : "nowrap",
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
||||||
@@ -33,18 +27,13 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
|||||||
<FeedEntryTitle entry={props.entry} />
|
<FeedEntryTitle entry={props.entry} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={classes.headerSubtext}>
|
<Box className={classes.headerSubtext}>
|
||||||
<Box mr={6}>
|
<FeedFavicon url={props.entry.iconUrl} />
|
||||||
<FeedFavicon url={props.entry.iconUrl} />
|
<Space w={6} />
|
||||||
</Box>
|
<Text color="dimmed">
|
||||||
<Box>
|
{props.entry.feedName}
|
||||||
<Text color="dimmed">{props.entry.feedName}</Text>
|
<span> · </span>
|
||||||
</Box>
|
<RelativeDate date={props.entry.date} />
|
||||||
<Box>
|
</Text>
|
||||||
<Text color="dimmed">
|
|
||||||
<span> · </span>
|
|
||||||
<RelativeDate date={props.entry.date} />
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
{props.expanded && (
|
{props.expanded && (
|
||||||
<Box className={classes.headerSubtext}>
|
<Box className={classes.headerSubtext}>
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ export function Header() {
|
|||||||
<Center>
|
<Center>
|
||||||
<HeaderToolbar>
|
<HeaderToolbar>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbArrowDown size={iconSize} />}
|
icon={<TbArrowUp size={iconSize} />}
|
||||||
label={<Trans>Next</Trans>}
|
label={<Trans>Previous</Trans>}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch(
|
dispatch(
|
||||||
selectNextEntry({
|
selectPreviousEntry({
|
||||||
expand: true,
|
expand: true,
|
||||||
markAsRead: true,
|
markAsRead: true,
|
||||||
scrollToEntry: true,
|
scrollToEntry: true,
|
||||||
@@ -90,11 +90,11 @@ export function Header() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbArrowUp size={iconSize} />}
|
icon={<TbArrowDown size={iconSize} />}
|
||||||
label={<Trans>Previous</Trans>}
|
label={<Trans>Next</Trans>}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch(
|
dispatch(
|
||||||
selectPreviousEntry({
|
selectNextEntry({
|
||||||
expand: true,
|
expand: true,
|
||||||
markAsRead: true,
|
markAsRead: true,
|
||||||
scrollToEntry: true,
|
scrollToEntry: true,
|
||||||
|
|||||||
@@ -13,8 +13,27 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
|
|||||||
const source = useAppSelector(state => state.entries.source)
|
const source = useAppSelector(state => state.entries.source)
|
||||||
const sourceLabel = useAppSelector(state => state.entries.sourceLabel)
|
const sourceLabel = useAppSelector(state => state.entries.sourceLabel)
|
||||||
const entriesTimestamp = useAppSelector(state => state.entries.timestamp) ?? Date.now()
|
const entriesTimestamp = useAppSelector(state => state.entries.timestamp) ?? Date.now()
|
||||||
|
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const buttonClicked = () => {
|
||||||
|
if (markAllAsReadConfirmation) {
|
||||||
|
setThreshold(0)
|
||||||
|
setOpened(true)
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
markAllEntries({
|
||||||
|
sourceType: source.type,
|
||||||
|
req: {
|
||||||
|
id: source.id,
|
||||||
|
read: true,
|
||||||
|
olderThan: entriesTimestamp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal opened={opened} onClose={() => setOpened(false)} title={<Trans>Mark all entries as read</Trans>}>
|
<Modal opened={opened} onClose={() => setOpened(false)} title={<Trans>Mark all entries as read</Trans>}>
|
||||||
@@ -70,14 +89,7 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
|
|||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
<ActionButton
|
<ActionButton icon={<TbChecks size={props.iconSize} />} label={<Trans>Mark all as read</Trans>} onClick={buttonClicked} />
|
||||||
icon={<TbChecks size={props.iconSize} />}
|
|
||||||
label={<Trans>Mark all as read</Trans>}
|
|
||||||
onClick={() => {
|
|
||||||
setThreshold(0)
|
|
||||||
setOpened(true)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
|
|||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import {
|
import {
|
||||||
changeAlwaysScrollToEntry,
|
changeAlwaysScrollToEntry,
|
||||||
|
changeCustomContextMenu,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
|
changeMarkAllAsReadConfirmation,
|
||||||
changeScrollMarks,
|
changeScrollMarks,
|
||||||
changeScrollSpeed,
|
changeScrollSpeed,
|
||||||
changeSharingSetting,
|
changeSharingSetting,
|
||||||
@@ -19,6 +21,8 @@ export function DisplaySettings() {
|
|||||||
const showRead = useAppSelector(state => state.user.settings?.showRead)
|
const showRead = useAppSelector(state => state.user.settings?.showRead)
|
||||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||||
const alwaysScrollToEntry = useAppSelector(state => state.user.settings?.alwaysScrollToEntry)
|
const alwaysScrollToEntry = useAppSelector(state => state.user.settings?.alwaysScrollToEntry)
|
||||||
|
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
|
||||||
|
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
|
||||||
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
|
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
@@ -58,6 +62,18 @@ export function DisplaySettings() {
|
|||||||
onChange={e => dispatch(changeScrollMarks(e.currentTarget.checked))}
|
onChange={e => dispatch(changeScrollMarks(e.currentTarget.checked))}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
label={<Trans>Show confirmation when marking all entries as read</Trans>}
|
||||||
|
checked={markAllAsReadConfirmation}
|
||||||
|
onChange={e => dispatch(changeMarkAllAsReadConfirmation(e.currentTarget.checked))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
label={<Trans>Show CommaFeed's own context menu on right click</Trans>}
|
||||||
|
checked={customContextMenu}
|
||||||
|
onChange={e => dispatch(changeCustomContextMenu(e.currentTarget.checked))}
|
||||||
|
/>
|
||||||
|
|
||||||
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
||||||
|
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
|
|||||||
@@ -77,10 +77,7 @@ export function ProfileSettings() {
|
|||||||
|
|
||||||
<form onSubmit={form.onSubmit(saveProfile.execute)}>
|
<form onSubmit={form.onSubmit(saveProfile.execute)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Input.Wrapper label={<Trans>User name</Trans>}>
|
<TextInput label={<Trans>User name</Trans>} readOnly value={profile?.name} />
|
||||||
<Box>{profile?.name}</Box>
|
|
||||||
</Input.Wrapper>
|
|
||||||
|
|
||||||
<TextInput label={<Trans>API key</Trans>} readOnly value={profile?.apiKey} />
|
<TextInput label={<Trans>API key</Trans>} readOnly value={profile?.apiKey} />
|
||||||
|
|
||||||
<Input.Wrapper
|
<Input.Wrapper
|
||||||
@@ -98,6 +95,22 @@ export function ProfileSettings() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Input.Wrapper>
|
</Input.Wrapper>
|
||||||
|
|
||||||
|
<Input.Wrapper
|
||||||
|
label={<Trans>Fever API</Trans>}
|
||||||
|
description={
|
||||||
|
<Trans>
|
||||||
|
CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client.
|
||||||
|
The username is your user name and the password is your API key.
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Anchor href={`rest/fever/user/${profile?.id}`} target="_blank">
|
||||||
|
<Trans>Fever API URL</Trans>
|
||||||
|
</Anchor>
|
||||||
|
</Box>
|
||||||
|
</Input.Wrapper>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Badge, createStyles } from "@mantine/core"
|
import { Badge, createStyles, Tooltip } from "@mantine/core"
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({
|
const useStyles = createStyles(() => ({
|
||||||
badge: {
|
badge: {
|
||||||
@@ -13,6 +13,10 @@ export function UnreadCount(props: { unreadCount: number }) {
|
|||||||
|
|
||||||
if (props.unreadCount <= 0) return null
|
if (props.unreadCount <= 0) return null
|
||||||
|
|
||||||
const count = props.unreadCount >= 1000 ? "999+" : props.unreadCount
|
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
||||||
return <Badge className={classes.badge}>{count}</Badge>
|
return (
|
||||||
|
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count}>
|
||||||
|
<Badge className={classes.badge}>{count}</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { useMediaQuery } from "@mantine/hooks"
|
import { useMediaQuery } from "@mantine/hooks"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
|
|
||||||
export const useMobile = (breakpoint: string = Constants.layout.mobileBreakpoint) => !useMediaQuery(`(min-width: ${breakpoint})`)
|
export const useMobile = (breakpoint: string = Constants.layout.mobileBreakpoint) =>
|
||||||
|
!useMediaQuery(`(min-width: ${breakpoint})`, undefined, {
|
||||||
|
getInitialValueInEffect: false,
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { setWebSocketConnected } from "app/slices/server"
|
||||||
import { reloadTree } from "app/slices/tree"
|
import { reloadTree } from "app/slices/tree"
|
||||||
import { useAppDispatch } from "app/store"
|
import { useAppDispatch } from "app/store"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
@@ -11,7 +12,14 @@ export const useWebSocket = () => {
|
|||||||
const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss"
|
const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss"
|
||||||
const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}/ws`
|
const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}/ws`
|
||||||
|
|
||||||
const ws = new WebsocketHeartbeatJs({ url: wsUrl, pingMsg: "ping" })
|
const ws = new WebsocketHeartbeatJs({
|
||||||
|
url: wsUrl,
|
||||||
|
pingMsg: "ping",
|
||||||
|
// ping interval, just under a minute to prevent firewalls from closing idle connections
|
||||||
|
pingTimeout: 55000,
|
||||||
|
})
|
||||||
|
ws.onopen = () => dispatch(setWebSocketConnected(true))
|
||||||
|
ws.onclose = () => dispatch(setWebSocketConnected(false))
|
||||||
ws.onmessage = event => {
|
ws.onmessage = event => {
|
||||||
const { data } = event
|
const { data } = event
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "ملف opml هو ملف XML يحتوي على عناوين URL للتغ
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "تحليل التغذية"
|
msgstr "تحليل التغذية"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "مفتاح API"
|
msgstr "مفتاح API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "تأكد من عمل الخلاصة"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed التالي العنصر غير المقروء"
|
msgstr "CommaFeed التالي العنصر غير المقروء"
|
||||||
@@ -339,6 +347,14 @@ msgstr "موجز URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "الملف مطلوب"
|
msgstr "الملف مطلوب"
|
||||||
@@ -665,6 +681,7 @@ msgstr "تم إغلاق التسجيلات في مثيل CommaFeed هذا"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "مشاركة"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "مشاركة المواقع"
|
msgstr "مشاركة المواقع"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "الحلقة"
|
msgstr "الحلقة"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "إظهار موجز ويب والفئات التي لا تحتوي عل
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "إظهار تعليمات اختصار لوحة المفاتيح"
|
msgstr "إظهار تعليمات اختصار لوحة المفاتيح"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "تبديل قراءة حالة الإدخال الحالي"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي"
|
msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Un fitxer opml és un fitxer XML que conté URL i categories de canals.
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analitzar el feed"
|
msgstr "Analitzar el feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "clau API"
|
msgstr "clau API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Comproveu que el canal funciona"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed següent element no llegit"
|
msgstr "CommaFeed següent element no llegit"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL del canal"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "el fitxer és necessari"
|
msgstr "el fitxer és necessari"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Els registres estan tancats en aquesta instància de CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "API REST"
|
msgstr "API REST"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Comparteix"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Compartir llocs"
|
msgstr "Compartir llocs"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "canvi"
|
msgstr "canvi"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Mostra feeds i categories sense entrades no llegides"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Mostra l'ajuda de la drecera del teclat"
|
msgstr "Mostra l'ajuda de la drecera del teclat"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Canvia l'estat de lectura de l'entrada actual"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"
|
msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Soubor opml je soubor XML obsahující adresy URL a kategorie zdrojů. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyzujte krmivo"
|
msgstr "Analyzujte krmivo"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Klíč API"
|
msgstr "Klíč API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Zkontrolujte, zda zdroj funguje"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed další nepřečtená položka"
|
msgstr "CommaFeed další nepřečtená položka"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL zdroje"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -665,6 +681,7 @@ msgstr "V této instanci CommaFeed jsou registrace uzavřeny"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Sdílejte"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Stránky pro sdílení"
|
msgstr "Stránky pro sdílení"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Směna"
|
msgstr "Směna"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Zobrazit kanály a kategorie bez nepřečtených položek"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Zobrazit nápovědu ke klávesovým zkratkám"
|
msgstr "Zobrazit nápovědu ke klávesovým zkratkám"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Přepne stav čtení aktuálního záznamu"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo"
|
msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Mae ffeil opml yn ffeil XML sy'n cynnwys URLs porthiant a chategorïau.
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Dadansoddi porthiant"
|
msgstr "Dadansoddi porthiant"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Allwedd API"
|
msgstr "Allwedd API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Gwiriwch fod y porthiant yn gweithio"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed eitem nesaf heb ei darllen"
|
msgstr "CommaFeed eitem nesaf heb ei darllen"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL porthiant"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "mae angen y ffeil"
|
msgstr "mae angen y ffeil"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Mae cofrestriadau ar gau ar yr achos CommaFeed hwn"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Rhannu"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Rhannu gwefannau"
|
msgstr "Rhannu gwefannau"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "shifft"
|
msgstr "shifft"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Dangos ffrydiau a chategorïau heb unrhyw gofnodion heb eu darllen"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Dangos cymorth llwybr byr bysellfwrdd"
|
msgstr "Dangos cymorth llwybr byr bysellfwrdd"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Toglo statws darllen y cofnod cyfredol"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo"
|
msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "En opml-fil er en XML-fil, der indeholder feed-URL'er og kategorier. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyser foder"
|
msgstr "Analyser foder"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nøgle"
|
msgstr "API-nøgle"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Tjek, at foderet virker"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed næste ulæste element"
|
msgstr "CommaFeed næste ulæste element"
|
||||||
@@ -339,6 +347,14 @@ msgstr ""
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fil er påkrævet"
|
msgstr "fil er påkrævet"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Registreringer er lukket på denne CommaFeed-instans"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Del"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Delingssider"
|
msgstr "Delingssider"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Skift"
|
msgstr "Skift"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Vis feeds og kategorier uden ulæste poster"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Vis hjælp til tastaturgenveje"
|
msgstr "Vis hjælp til tastaturgenveje"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Skift læsestatus for den aktuelle post"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
|
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
|
||||||
msgstr ""
|
msgstr "CommaFeed ist ein Open Source Projekt. Der Quellcode wird auf auf </0><1>GitHub</1> gehostet."
|
||||||
|
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "<0>Complete syntax is available </0><1>here</1>."
|
msgid "<0>Complete syntax is available </0><1>here</1>."
|
||||||
msgstr ""
|
msgstr "Vollständiger Syntax ist </0><1>hier</1> verfügbar."
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
msgid "<0>Have an account?</0><1>Log in!</1>"
|
msgid "<0>Have an account?</0><1>Log in!</1>"
|
||||||
@@ -35,12 +35,12 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "<0>Need an account?</0><1>Sign up!</1>"
|
msgid "<0>Need an account?</0><1>Sign up!</1>"
|
||||||
msgstr "<0>Benötigen Sie ein Konto?</0><1>Melden Sie sich an!</1>"
|
msgstr "<0>Benötigen Sie ein Konto?</0><1>Hier geht's zur Registrierung!</1>"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Ungefähr"
|
msgstr "Über"
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
@@ -77,16 +77,20 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
msgid "An email has been sent if this address was registered. Check your inbox."
|
msgid "An email has been sent if this address was registered. Check your inbox."
|
||||||
msgstr "Eine E-Mail wurde gesendet, wenn diese Adresse registriert wurde. "
|
msgstr "Eine E-Mail wurde gesendet, wenn diese Adresse registriert wurde. Bitte den Posteingang prüfen."
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your data from other feed reading services."
|
msgid "An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your data from other feed reading services."
|
||||||
msgstr "Eine opml-Datei ist eine XML-Datei, die Feed-URLs und Kategorien enthält. "
|
msgstr "Eine opml-Datei ist eine XML-Datei, die Feed-URLs und Kategorien enthält."
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/content/add/Subscribe.tsx
|
||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Feed analysieren"
|
msgstr "Feed analysieren"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr "Ankündigung"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-Schlüssel"
|
msgstr "API-Schlüssel"
|
||||||
@@ -101,7 +105,7 @@ msgstr "Sind Sie sicher, dass Sie Benutzer <0>{userName}</0> löschen möchten?"
|
|||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "Are you sure you want to delete your account? There's no turning back!"
|
msgid "Are you sure you want to delete your account? There's no turning back!"
|
||||||
msgstr "Sind Sie sicher, dass Sie Ihr Konto löschen möchten? "
|
msgstr "Sind Sie sicher, dass Sie Ihr Konto löschen möchten?"
|
||||||
|
|
||||||
#: src/components/header/MarkAllAsReadButton.tsx
|
#: src/components/header/MarkAllAsReadButton.tsx
|
||||||
msgid "Are you sure you want to mark all entries of <0>{sourceLabel}</0> as read?"
|
msgid "Are you sure you want to mark all entries of <0>{sourceLabel}</0> as read?"
|
||||||
@@ -117,7 +121,7 @@ msgstr "Sind Sie sicher, dass Sie <0>{feedName}</0> abbestellen möchten?"
|
|||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
msgid "Asc"
|
msgid "Asc"
|
||||||
msgstr "Asz"
|
msgstr "Aufsteigend"
|
||||||
|
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Available variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison."
|
msgid "Available variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison."
|
||||||
@@ -129,15 +133,15 @@ msgstr "Zurück"
|
|||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
msgid "Back to log in"
|
msgid "Back to log in"
|
||||||
msgstr "Zurück zum Anmelden"
|
msgstr "Zurück zum Login"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Browser extension required for Chrome"
|
msgid "Browser extension required for Chrome"
|
||||||
msgstr ""
|
msgstr "Browser-Erweiterung für Chrome benötigt"
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Browser extention"
|
msgid "Browser extention"
|
||||||
msgstr ""
|
msgstr "Browser-Erweiterung"
|
||||||
|
|
||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/content/add/AddCategory.tsx
|
#: src/components/content/add/AddCategory.tsx
|
||||||
@@ -175,6 +179,10 @@ msgstr "Überprüfen Sie, ob der Feed funktioniert"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr "CommaFeed ist kompatibel zur Fever API. Benutzen Sie folgende URL in Ihrem Fever-kompatiblen Mobilclient. Der Benutzername ist Ihr User Name, das Passwort ist der API-Schlüssel."
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed nächstes ungelesenes Element"
|
msgstr "CommaFeed nächstes ungelesenes Element"
|
||||||
@@ -264,7 +272,7 @@ msgstr "Anzeige"
|
|||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/app/DonatePage.tsx
|
#: src/pages/app/DonatePage.tsx
|
||||||
msgid "Donate"
|
msgid "Donate"
|
||||||
msgstr ""
|
msgstr "Spenden"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
@@ -323,7 +331,7 @@ msgstr "Exportieren Sie Ihre Abonnements und Kategorien als OPML-Datei, die in a
|
|||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Extension options"
|
msgid "Extension options"
|
||||||
msgstr ""
|
msgstr "Erweiterungsoptionen"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/content/add/Subscribe.tsx
|
||||||
msgid "Feed name"
|
msgid "Feed name"
|
||||||
@@ -337,6 +345,14 @@ msgstr "Feed-URL"
|
|||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
|
msgstr "Alle Feeds jetzt abrufen"
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
@@ -369,7 +385,7 @@ msgstr "Generierte Feed-URL"
|
|||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
msgid "Go to {0}"
|
msgid "Go to {0}"
|
||||||
msgstr ""
|
msgstr "Gehe zu {0}"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Go to the All view"
|
msgid "Go to the All view"
|
||||||
@@ -381,7 +397,7 @@ msgstr "Gehen Sie zur API-Dokumentation."
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Goodies"
|
msgid "Goodies"
|
||||||
msgstr "Gutes"
|
msgstr "Goodies"
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
@@ -411,7 +427,7 @@ msgstr "Ungelesen lassen"
|
|||||||
#: src/components/content/FeedEntries.tsx
|
#: src/components/content/FeedEntries.tsx
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Tastenkürzel"
|
msgstr "Tastaturkürzel"
|
||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
@@ -463,7 +479,7 @@ msgstr "Abmelden"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Long press"
|
msgid "Long press"
|
||||||
msgstr ""
|
msgstr "Langer Tastendruck"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -495,7 +511,7 @@ msgstr "Metriken"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Middle click"
|
msgid "Middle click"
|
||||||
msgstr ""
|
msgstr "Mittelklick"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Move the page down"
|
msgid "Move the page down"
|
||||||
@@ -508,7 +524,7 @@ msgstr "Bewege die Seite nach oben"
|
|||||||
#: src/components/RelativeDate.tsx
|
#: src/components/RelativeDate.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "N/A"
|
msgid "N/A"
|
||||||
msgstr "n. z"
|
msgstr "n.v."
|
||||||
|
|
||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -576,11 +592,11 @@ msgstr "Link öffnen"
|
|||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
msgid "Open link in new background tab"
|
msgid "Open link in new background tab"
|
||||||
msgstr ""
|
msgstr "Link in neuem Tab im Hintergrund öffnen"
|
||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
msgid "Open link in new tab"
|
msgid "Open link in new tab"
|
||||||
msgstr ""
|
msgstr "Link in neuem Tab öffnen"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Open next entry"
|
msgid "Open next entry"
|
||||||
@@ -613,11 +629,11 @@ msgstr "Bestellung"
|
|||||||
|
|
||||||
#: src/components/content/add/AddCategory.tsx
|
#: src/components/content/add/AddCategory.tsx
|
||||||
msgid "Parent"
|
msgid "Parent"
|
||||||
msgstr "Elternteil"
|
msgstr "Übergeordnet"
|
||||||
|
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
msgid "Parent Category"
|
msgid "Parent Category"
|
||||||
msgstr "Elternkategorie"
|
msgstr "Übergeordnete Kategorie"
|
||||||
|
|
||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
@@ -638,11 +654,11 @@ msgstr "Passwörter stimmen nicht überein"
|
|||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Position"
|
msgid "Position"
|
||||||
msgstr "Stellung"
|
msgstr "Position"
|
||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
msgstr ""
|
msgstr "Vorheriges"
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
@@ -665,9 +681,10 @@ msgstr "Registrierungen sind für diese CommaFeed-Instanz geschlossen"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "REST-API"
|
msgstr "REST-API"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr "Rechtsklick"
|
||||||
|
|
||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
@@ -679,7 +696,7 @@ msgstr "Speichern"
|
|||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "Scroll smoothly when navigating between entries"
|
msgid "Scroll smoothly when navigating between entries"
|
||||||
msgstr "Geschwindes Scrollen beim Navigieren zwischen Einträgen"
|
msgstr "Schnelles Scrollen beim Navigieren zwischen Einträgen"
|
||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
@@ -716,11 +733,20 @@ msgstr "Teilen"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Seiten teilen"
|
msgstr "Seiten teilen"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Verschiebung"
|
msgstr "Verschiebung"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr "CommaFeed-Kontextmenü anzeigen bei Rechtsklick"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr "Bestätigung beim Markieren von allen Einträgen als gelesen"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Feeds und Kategorien ohne ungelesene Einträge anzeigen"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Tastenkürzel-Hilfe anzeigen"
|
msgstr "Tastenkürzel-Hilfe anzeigen"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -787,12 +817,12 @@ msgstr ""
|
|||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Switch to dark theme"
|
msgid "Switch to dark theme"
|
||||||
msgstr "Zum dunklen Design wechseln"
|
msgstr "Zum Darkmode wechseln"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Switch to light theme"
|
msgid "Switch to light theme"
|
||||||
msgstr "Wechseln Sie zum Lichtdesign"
|
msgstr "Zum Lightmode wechseln"
|
||||||
|
|
||||||
#: src/components/content/FeedEntryFooter.tsx
|
#: src/components/content/FeedEntryFooter.tsx
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
@@ -812,7 +842,11 @@ msgstr "Lesestatus des aktuellen Eintrags umschalten"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr "Sidebar an- und ausschalten"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr "Markierungsstatus des aktuellen Eintrags ändern"
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
@@ -820,7 +854,7 @@ msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo"
|
|||||||
|
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Try the demo!"
|
msgid "Try the demo!"
|
||||||
msgstr ""
|
msgstr "Testen Sie die Demo!"
|
||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
msgid "Unread"
|
msgid "Unread"
|
||||||
@@ -859,4 +893,4 @@ msgstr "Sie haben noch keine Abonnements. "
|
|||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
msgid "Your feeds have been queued for refresh."
|
msgid "Your feeds have been queued for refresh."
|
||||||
msgstr ""
|
msgstr "Ihr Feed wurde für die Aktualisierung eingereiht."
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "An opml file is an XML file containing feed URLs and categories. You can
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyze feed"
|
msgstr "Analyze feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr "Announcement"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API key"
|
msgstr "API key"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Check that the feed is working"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr "CommaFeed browser extension version {browserExtensionVersion}."
|
msgstr "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed next unread item"
|
msgstr "CommaFeed next unread item"
|
||||||
@@ -339,6 +347,14 @@ msgstr "Feed URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr "Fetch all my feeds now"
|
msgstr "Fetch all my feeds now"
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr "Fever API"
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr "Fever API URL"
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "file is required"
|
msgstr "file is required"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Registrations are closed on this CommaFeed instance"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "REST API"
|
msgstr "REST API"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr "Right click"
|
msgstr "Right click"
|
||||||
@@ -716,11 +733,20 @@ msgstr "Share"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Sharing sites"
|
msgstr "Sharing sites"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Shift"
|
msgstr "Shift"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr "Show CommaFeed's own context menu on right click"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr "Show confirmation when marking all entries as read"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr "Show entry menu (desktop)"
|
msgstr "Show entry menu (desktop)"
|
||||||
@@ -737,6 +763,10 @@ msgstr "Show feeds and categories with no unread entries"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Show keyboard shortcut help"
|
msgstr "Show keyboard shortcut help"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr "Show native menu (desktop)"
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Toggle read status of current entry"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr "Toggle sidebar"
|
msgstr "Toggle sidebar"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr "Toggle starred status of current entry"
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Try out CommaFeed with the demo account: demo/demo"
|
msgstr "Try out CommaFeed with the demo account: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Un archivo opml es un archivo XML que contiene categorías y direcciones
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analizar alimentación"
|
msgstr "Analizar alimentación"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "clave API"
|
msgstr "clave API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Compruebe que el feed funciona"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed siguiente elemento no leído"
|
msgstr "CommaFeed siguiente elemento no leído"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL de fuente"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "archivo requerido"
|
msgstr "archivo requerido"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Los registros están cerrados en esta instancia de CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "API REST"
|
msgstr "API REST"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Compartir"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Compartir sitios"
|
msgstr "Compartir sitios"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Cambio"
|
msgstr "Cambio"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Mostrar feeds y categorías sin entradas no leídas"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Mostrar ayuda de atajo de teclado"
|
msgstr "Mostrar ayuda de atajo de teclado"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Alternar estado de lectura de la entrada actual"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo"
|
msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "یک فایل opml یک فایل XML است که حاوی آدرسه
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "خوراک را تجزیه و تحلیل کنید"
|
msgstr "خوراک را تجزیه و تحلیل کنید"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "کلید API"
|
msgstr "کلید API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "بررسی کنید که خوراک کار می کند"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "مورد خوانده نشده بعدی CommaFeed"
|
msgstr "مورد خوانده نشده بعدی CommaFeed"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL فید"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "فایل مورد نیاز است"
|
msgstr "فایل مورد نیاز است"
|
||||||
@@ -665,6 +681,7 @@ msgstr "ثبت نام در این نمونه CommaFeed بسته شده است"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "به اشتراک بگذارید"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "اشتراک گذاری سایت ها"
|
msgstr "اشتراک گذاری سایت ها"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "شیفت"
|
msgstr "شیفت"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "فیدها و دسته ها را بدون ورودی خوانده نشد
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "نمایش راهنمایی میانبر صفحه کلید"
|
msgstr "نمایش راهنمایی میانبر صفحه کلید"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو"
|
msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Opml-tiedosto on XML-tiedosto, joka sisältää syötteen URL-osoitteet
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analysoi syöte"
|
msgstr "Analysoi syöte"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-avain"
|
msgstr "API-avain"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Tarkista, että syöttö toimii"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed seuraava lukematon kohde"
|
msgstr "CommaFeed seuraava lukematon kohde"
|
||||||
@@ -339,6 +347,14 @@ msgstr "Syötteen URL-osoite"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "tiedosto vaaditaan"
|
msgstr "tiedosto vaaditaan"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Tämän CommaFeed-esiintymän rekisteröinnit on suljettu"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Jaa"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Sivustojen jakaminen"
|
msgstr "Sivustojen jakaminen"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Vaihto"
|
msgstr "Vaihto"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Näytä syötteet ja luokat ilman lukemattomia merkintöjä"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Näytä pikanäppäimen ohje"
|
msgstr "Näytä pikanäppäimen ohje"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Vaihda nykyisen merkinnän lukutila"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Kokeile CommaFeediä demotilillä: demo/demo"
|
msgstr "Kokeile CommaFeediä demotilillä: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Un fichier OPML est un fichier XML contenant des URL de flux et des cat
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyser le flux"
|
msgstr "Analyser le flux"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr "Annonces"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Clé API"
|
msgstr "Clé API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Vérifie que le flux fonctionne"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr "Extension CommaFeed pour navigateur version {browserExtensionVersion}."
|
msgstr "Extension CommaFeed pour navigateur version {browserExtensionVersion}."
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr "Commafeed est compatible avec l'API Fever, en inscrivant l'URL suivante dans votre client mobile compatible. Entrez votre nom d'utilisateur habituel, et votre clef API comme mot de passe."
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed prochain article non lu"
|
msgstr "CommaFeed prochain article non lu"
|
||||||
@@ -264,7 +272,7 @@ msgstr "Affichage"
|
|||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/app/DonatePage.tsx
|
#: src/pages/app/DonatePage.tsx
|
||||||
msgid "Donate"
|
msgid "Donate"
|
||||||
msgstr "Faites un don"
|
msgstr "Faire un don"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL du flux"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr "Rafraîchir tous mes flux"
|
msgstr "Rafraîchir tous mes flux"
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr "API Fever"
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr "URL API Fever"
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fichier requis"
|
msgstr "fichier requis"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Les inscriptions sont fermées sur cette instance de CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "API REST"
|
msgstr "API REST"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr "Clic droit"
|
msgstr "Clic droit"
|
||||||
@@ -716,11 +733,20 @@ msgstr "Partager"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Sites de partage"
|
msgstr "Sites de partage"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Maj"
|
msgstr "Maj"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr "Afficher le menu contextuel de Commafeed lors d'un clic droit"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr "Demander une confirmation avant de tout marquer comme lu"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr "Afficher les options de l'entrée (ordinateur)"
|
msgstr "Afficher les options de l'entrée (ordinateur)"
|
||||||
@@ -737,6 +763,10 @@ msgstr "Afficher les flux et les catégories pour lesquels tout est déjà lu"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Montrer les raccourcis clavier"
|
msgstr "Montrer les raccourcis clavier"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr "Afficher les options du navigateur (ordinateur)"
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Marquer l'entrée actuelle comme lue/non lue"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr "Montrer/cacher la barre latérale"
|
msgstr "Montrer/cacher la barre latérale"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr "Montrer/cacher le statut favori de l'entrée"
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"
|
msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Un ficheiro opml é un ficheiro XML que contén URL e categorías de fon
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analizar feed"
|
msgstr "Analizar feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "chave API"
|
msgstr "chave API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Comproba que a fonte funciona"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed seguinte elemento non lido"
|
msgstr "CommaFeed seguinte elemento non lido"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL da fonte"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "é necesario o ficheiro"
|
msgstr "é necesario o ficheiro"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Os rexistros están pechados nesta instancia de CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "API REST"
|
msgstr "API REST"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Compartir"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Compartir sitios"
|
msgstr "Compartir sitios"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "quendas"
|
msgstr "quendas"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Mostrar fontes e categorías sen entradas sen ler"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Mostrar axuda do atallo do teclado"
|
msgstr "Mostrar axuda do atallo do teclado"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "alternar o estado de lectura da entrada actual"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Proba CommaFeed coa conta de demostración: demo/demo"
|
msgstr "Proba CommaFeed coa conta de demostración: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Az opml-fájl olyan XML-fájl, amely feed URL-címeket és kategóriáka
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Hírcsatorna elemzése"
|
msgstr "Hírcsatorna elemzése"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API kulcs"
|
msgstr "API kulcs"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Ellenőrizze, hogy a feed működik-e"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed következő olvasatlan elem"
|
msgstr "CommaFeed következő olvasatlan elem"
|
||||||
@@ -339,6 +347,14 @@ msgstr ""
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fájl szükséges"
|
msgstr "fájl szükséges"
|
||||||
@@ -665,6 +681,7 @@ msgstr "A regisztrációk le vannak zárva ezen a CommaFeed példányon"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Oszd meg"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Webhelyek megosztása"
|
msgstr "Webhelyek megosztása"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Hírcsatornák és kategóriák megjelenítése olvasatlan bejegyzések
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "A billentyűparancsok súgójának megjelenítése"
|
msgstr "A billentyűparancsok súgójának megjelenítése"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Az aktuális bejegyzés olvasási állapotának váltása"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo"
|
msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "File opml adalah file XML yang berisi URL dan kategori feed. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analisis umpan"
|
msgstr "Analisis umpan"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "kunci API"
|
msgstr "kunci API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Periksa apakah umpannya berfungsi"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed item yang belum dibaca berikutnya"
|
msgstr "CommaFeed item yang belum dibaca berikutnya"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL Umpan"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "file diperlukan"
|
msgstr "file diperlukan"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Pendaftaran ditutup pada instans CommaFeed ini"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Bagikan"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Berbagi situs"
|
msgstr "Berbagi situs"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Pergeseran"
|
msgstr "Pergeseran"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Tampilkan umpan dan kategori tanpa entri yang belum dibaca"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Tampilkan bantuan pintasan keyboard"
|
msgstr "Tampilkan bantuan pintasan keyboard"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Beralih status baca entri saat ini"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Cobalah CommaFeed dengan akun demo: demo/demo"
|
msgstr "Cobalah CommaFeed dengan akun demo: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Un file opml è un file XML contenente URL e categorie di feed. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analizza feed"
|
msgstr "Analizza feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Chiave API"
|
msgstr "Chiave API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Verifica che il feed funzioni"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed successivo elemento non letto"
|
msgstr "CommaFeed successivo elemento non letto"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL feed"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "è richiesto il file"
|
msgstr "è richiesto il file"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Le registrazioni sono chiuse su questa istanza CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "API REST"
|
msgstr "API REST"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Condividi"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Condivisione di siti"
|
msgstr "Condivisione di siti"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Cambio"
|
msgstr "Cambio"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Mostra feed e categorie senza voci non lette"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Mostra la guida alle scorciatoie da tastiera"
|
msgstr "Mostra la guida alle scorciatoie da tastiera"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Commuta lo stato di lettura della voce corrente"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Prova CommaFeed con il conto demo: demo/demo"
|
msgstr "Prova CommaFeed con il conto demo: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "opml ファイルは、フィードの URL とカテゴリを含む XML
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "フィードを分析する"
|
msgstr "フィードを分析する"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "APIキー"
|
msgstr "APIキー"
|
||||||
@@ -175,6 +179,10 @@ msgstr "フィードが動作していることを確認してください"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "次の未読アイテムをカンマフィード"
|
msgstr "次の未読アイテムをカンマフィード"
|
||||||
@@ -339,6 +347,14 @@ msgstr "フィード URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "ファイルが必要です"
|
msgstr "ファイルが必要です"
|
||||||
@@ -665,6 +681,7 @@ msgstr "この CommaFeed インスタンスの登録は終了しています"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "シェア"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "共有サイト"
|
msgstr "共有サイト"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "シフト"
|
msgstr "シフト"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "未読エントリのないフィードとカテゴリを表示する"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "キーボード ショートカットのヘルプを表示"
|
msgstr "キーボード ショートカットのヘルプを表示"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "現在のエントリの読み取りステータスを切り替えます
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "デモアカウントで CommaFeed を試す: demo/demo"
|
msgstr "デモアカウントで CommaFeed を試す: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "opml 파일은 피드 URL과 카테고리를 포함하는 XML 파일입
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "피드 분석"
|
msgstr "피드 분석"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API 키"
|
msgstr "API 키"
|
||||||
@@ -175,6 +179,10 @@ msgstr "피드가 작동하는지 확인"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "다음 읽지 않은 항목을 쉼표로 피드"
|
msgstr "다음 읽지 않은 항목을 쉼표로 피드"
|
||||||
@@ -339,6 +347,14 @@ msgstr "피드 URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "파일이 필요합니다"
|
msgstr "파일이 필요합니다"
|
||||||
@@ -665,6 +681,7 @@ msgstr "이 CommaFeed 인스턴스에 대한 등록이 마감되었습니다."
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "공유"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "사이트 공유"
|
msgstr "사이트 공유"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "시프트"
|
msgstr "시프트"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "읽지 않은 항목이 없는 피드 및 카테고리 표시"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "키보드 단축키 도움말 표시"
|
msgstr "키보드 단축키 도움말 표시"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "현재 항목의 읽기 상태 전환"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo"
|
msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Fail opml ialah fail XML yang mengandungi URL suapan dan kategori. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Menganalisis suapan"
|
msgstr "Menganalisis suapan"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Kunci API"
|
msgstr "Kunci API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Semak sama ada suapan berfungsi"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed item belum dibaca seterusnya"
|
msgstr "CommaFeed item belum dibaca seterusnya"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL Suapan"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fail diperlukan"
|
msgstr "fail diperlukan"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Pendaftaran ditutup pada contoh CommaFeed ini"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "REHAT API"
|
msgstr "REHAT API"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Kongsi"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Berkongsi tapak"
|
msgstr "Berkongsi tapak"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Anjakan"
|
msgstr "Anjakan"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Tunjukkan suapan dan kategori tanpa entri yang belum dibaca"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Tunjukkan bantuan pintasan papan kekunci"
|
msgstr "Tunjukkan bantuan pintasan papan kekunci"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Togol status bacaan entri semasa"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Cuba CommaFeed dengan akaun demo: demo/demo"
|
msgstr "Cuba CommaFeed dengan akaun demo: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "En opml-fil er en XML-fil som inneholder feed-URLer og kategorier. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyser feed"
|
msgstr "Analyser feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nøkkel"
|
msgstr "API-nøkkel"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Sjekk at feeden fungerer"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed neste uleste element"
|
msgstr "CommaFeed neste uleste element"
|
||||||
@@ -339,6 +347,14 @@ msgstr "Feed-URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fil kreves"
|
msgstr "fil kreves"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Del"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Delingssider"
|
msgstr "Delingssider"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Skift"
|
msgstr "Skift"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Vis feeder og kategorier uten uleste oppføringer"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Vis hurtigtasthjelp"
|
msgstr "Vis hurtigtasthjelp"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Veksle lesestatus for gjeldende oppføring"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Een opml-bestand is een XML-bestand met feed-URL's en categorieën. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyseer feed"
|
msgstr "Analyseer feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-sleutel"
|
msgstr "API-sleutel"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Controleer of de feed werkt"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed volgende ongelezen item"
|
msgstr "CommaFeed volgende ongelezen item"
|
||||||
@@ -339,6 +347,14 @@ msgstr "Feed-URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "bestand is vereist"
|
msgstr "bestand is vereist"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Registraties zijn gesloten op deze CommaFeed-instantie"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "REST-API"
|
msgstr "REST-API"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Delen"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Sites delen"
|
msgstr "Sites delen"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Toon feeds en categorieën zonder ongelezen items"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Toon hulp bij sneltoetsen"
|
msgstr "Toon hulp bij sneltoetsen"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Toggle leesstatus van huidige invoer"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Probeer CommaFeed uit met het demo-account: demo/demo"
|
msgstr "Probeer CommaFeed uit met het demo-account: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "En opml-fil er en XML-fil som inneholder feed-URLer og kategorier. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyser feed"
|
msgstr "Analyser feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nøkkel"
|
msgstr "API-nøkkel"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Sjekk at feeden fungerer"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed neste uleste element"
|
msgstr "CommaFeed neste uleste element"
|
||||||
@@ -339,6 +347,14 @@ msgstr "Feed-URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fil kreves"
|
msgstr "fil kreves"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Del"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Delingssider"
|
msgstr "Delingssider"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Skift"
|
msgstr "Skift"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Vis feeder og kategorier uten uleste oppføringer"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Vis hurtigtasthjelp"
|
msgstr "Vis hurtigtasthjelp"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Veksle lesestatus for gjeldende oppføring"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Plik opml to plik XML zawierający adresy URL i kategorie kanałów. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analizuj kanał"
|
msgstr "Analizuj kanał"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "klucz API"
|
msgstr "klucz API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Sprawdź, czy kanał działa"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "Przecinek następny nieprzeczytany element"
|
msgstr "Przecinek następny nieprzeczytany element"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL kanału"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "plik jest wymagany"
|
msgstr "plik jest wymagany"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Rejestracje są zamknięte w tej instancji CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Udostępnij"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Udostępnianie witryn"
|
msgstr "Udostępnianie witryn"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "zmiana"
|
msgstr "zmiana"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Pokaż kanały i kategorie bez nieprzeczytanych wpisów"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Pokaż pomoc dotyczącą skrótów klawiaturowych"
|
msgstr "Pokaż pomoc dotyczącą skrótów klawiaturowych"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Przełącz stan odczytu bieżącego wpisu"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo"
|
msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Um arquivo opml é um arquivo XML contendo URLs e categorias de feed. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analisar feed"
|
msgstr "Analisar feed"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "chave de API"
|
msgstr "chave de API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Verifique se o feed está funcionando"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed próximo item não lido"
|
msgstr "CommaFeed próximo item não lido"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL do feed"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "o arquivo é obrigatório"
|
msgstr "o arquivo é obrigatório"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Os registros estão fechados nesta instância do CommaFeed"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "API REST"
|
msgstr "API REST"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Compartilhar"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Compartilhando sites"
|
msgstr "Compartilhando sites"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Mudar"
|
msgstr "Mudar"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Mostrar feeds e categorias sem entradas não lidas"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Mostrar ajuda de atalho de teclado"
|
msgstr "Mostrar ajuda de atalho de teclado"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Alternar o status de leitura da entrada atual"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Experimente o CommaFeed com a conta demo: demo/demo"
|
msgstr "Experimente o CommaFeed com a conta demo: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "OPML-файл — это XML-файл, содержащий URL-адре
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Анализ канала"
|
msgstr "Анализ канала"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "ключ API"
|
msgstr "ключ API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Проверьте, работает ли лента."
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed следующий непрочитанный элемент"
|
msgstr "CommaFeed следующий непрочитанный элемент"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL-адрес фида"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "требуется файл"
|
msgstr "требуется файл"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Регистрация закрыта для этого экземпля
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr "ОТДЫХА API"
|
msgstr "ОТДЫХА API"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Поделиться"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Обмен сайтами"
|
msgstr "Обмен сайтами"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Сдвиг"
|
msgstr "Сдвиг"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Показать каналы и категории без непроч
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Показать справку по сочетаниям клавиш."
|
msgstr "Показать справку по сочетаниям клавиш."
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Переключить статус чтения текущей запи
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Попробуйте CommaFeed на демо-счете: demo/demo"
|
msgstr "Попробуйте CommaFeed на демо-счете: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "Súbor opml je súbor XML obsahujúci adresy URL kanálov a kategórie.
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analyzujte krmivo"
|
msgstr "Analyzujte krmivo"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Kľúč API"
|
msgstr "Kľúč API"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Skontrolujte, či feed funguje"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed ďalšia neprečítaná položka"
|
msgstr "CommaFeed ďalšia neprečítaná položka"
|
||||||
@@ -339,6 +347,14 @@ msgstr "URL informačného kanála"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -665,6 +681,7 @@ msgstr "V tejto inštancii CommaFeed sú registrácie uzavreté"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Zdieľať"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Zdieľanie stránok"
|
msgstr "Zdieľanie stránok"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Smena"
|
msgstr "Smena"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Zobraziť kanály a kategórie bez neprečítaných záznamov"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Zobraziť pomoc s klávesovými skratkami"
|
msgstr "Zobraziť pomoc s klávesovými skratkami"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Prepne stav čítania aktuálneho záznamu"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo"
|
msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo"
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "En opml-fil är en XML-fil som innehåller feed-URL:er och kategorier. "
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analysera foder"
|
msgstr "Analysera foder"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nyckel"
|
msgstr "API-nyckel"
|
||||||
@@ -175,6 +179,10 @@ msgstr "Kontrollera att matningen fungerar"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed nästa olästa objekt"
|
msgstr "CommaFeed nästa olästa objekt"
|
||||||
@@ -339,6 +347,14 @@ msgstr "Flödes-URL"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "fil krävs"
|
msgstr "fil krävs"
|
||||||
@@ -665,6 +681,7 @@ msgstr "Registreringar är stängda på denna CommaFeed-instans"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "Dela"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Delningssajter"
|
msgstr "Delningssajter"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Skift"
|
msgstr "Skift"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "Visa flöden och kategorier utan olästa poster"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Visa kortkommandohjälp"
|
msgstr "Visa kortkommandohjälp"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "Växla lässtatus för aktuell post"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "Prova CommaFeed med demokontot: demo/demo"
|
msgstr "Prova CommaFeed med demokontot: demo/demo"
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/content/add/CategorySelect.tsx
|
#: src/components/content/add/CategorySelect.tsx
|
||||||
msgid "{0} (in {1})"
|
msgid "{0} (in {1})"
|
||||||
msgstr ""
|
msgstr "{0} ({1} içinde)"
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
|
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
|
||||||
msgstr ""
|
msgstr "<0>CommaFeed açık kaynak kodlu bir proje. Kaynak kodları </0><1>GitHub</1>'da."
|
||||||
|
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "<0>Complete syntax is available </0><1>here</1>."
|
msgid "<0>Complete syntax is available </0><1>here</1>."
|
||||||
msgstr ""
|
msgstr "<0>Tüm sözdizimi </0><1>burada</1>."
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
msgid "<0>Have an account?</0><1>Log in!</1>"
|
msgid "<0>Have an account?</0><1>Log in!</1>"
|
||||||
@@ -31,7 +31,7 @@ msgstr "<0>Hesabınız var mı?</0><1>Giriş yapın!</1>"
|
|||||||
|
|
||||||
#: src/pages/app/DonatePage.tsx
|
#: src/pages/app/DonatePage.tsx
|
||||||
msgid "<0>Hey,</0><1>I'm Jérémie from Belgium and I've been working on CommaFeed in my free time for over 10 years now. Thanks for taking an interest in helping me continue supporting CommaFeed.</1>"
|
msgid "<0>Hey,</0><1>I'm Jérémie from Belgium and I've been working on CommaFeed in my free time for over 10 years now. Thanks for taking an interest in helping me continue supporting CommaFeed.</1>"
|
||||||
msgstr ""
|
msgstr "<0>Merhaba,</0><1>Ben Belçika'dan Jérémie ve 10 yıldır boş zamanlarımda CommaFeed üzerinde çalışıyorum. CommaFeed'i desteklememe ilgi gösterdiğiniz için teşekkürler.</1>"
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "<0>Need an account?</0><1>Sign up!</1>"
|
msgid "<0>Need an account?</0><1>Sign up!</1>"
|
||||||
@@ -73,7 +73,7 @@ msgstr "Tümü"
|
|||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen"
|
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen"
|
||||||
msgstr ""
|
msgstr "Seçilen girişi her zaman sayfanın üstüne kaydır, ekrana tamamen sığsa bile"
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
msgid "An email has been sent if this address was registered. Check your inbox."
|
msgid "An email has been sent if this address was registered. Check your inbox."
|
||||||
@@ -87,6 +87,10 @@ msgstr "Bir opml dosyası, besleme URL'lerini ve kategorilerini içeren bir XML
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Feed'i analiz et"
|
msgstr "Feed'i analiz et"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr "Duyuru"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API anahtarı"
|
msgstr "API anahtarı"
|
||||||
@@ -137,7 +141,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Browser extention"
|
msgid "Browser extention"
|
||||||
msgstr ""
|
msgstr "Tarayıcı eklentisi"
|
||||||
|
|
||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/content/add/AddCategory.tsx
|
#: src/components/content/add/AddCategory.tsx
|
||||||
@@ -173,6 +177,10 @@ msgstr "Feed'in çalışıp çalışmadığını kontrol edin"
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
|
msgstr "CommaFeed tarayıcı eklentisi sürüm {browserExtensionVersion}."
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
@@ -181,7 +189,7 @@ msgstr "CommaFeed sonraki okunmamış öğe"
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed version {version} ({revision})."
|
msgid "CommaFeed version {version} ({revision})."
|
||||||
msgstr ""
|
msgstr "CommaFeed sürüm {version} ({revision})."
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
msgid "Compact"
|
msgid "Compact"
|
||||||
@@ -209,7 +217,7 @@ msgstr "Etiket oluştur: {query}"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Ctrl"
|
msgid "Ctrl"
|
||||||
msgstr ""
|
msgstr "Ctrl"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "Current password"
|
msgid "Current password"
|
||||||
@@ -217,15 +225,15 @@ msgstr "Geçerli şifre"
|
|||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
msgid "Custom code"
|
msgid "Custom code"
|
||||||
msgstr ""
|
msgstr "Özel kod"
|
||||||
|
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
msgid "Custom CSS rules that will be applied"
|
msgid "Custom CSS rules that will be applied"
|
||||||
msgstr ""
|
msgstr "Uygulanacak özel CSS kuralları"
|
||||||
|
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
msgid "Custom JS code that will be executed on page load"
|
msgid "Custom JS code that will be executed on page load"
|
||||||
msgstr ""
|
msgstr "Sayfa yüklendiğinde çalıştırılacak özel JS kodu"
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
msgid "Date created"
|
msgid "Date created"
|
||||||
@@ -323,7 +331,7 @@ msgstr "Aboneliklerinizi ve kategorilerinizi diğer besleme okuma hizmetlerinde
|
|||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Extension options"
|
msgid "Extension options"
|
||||||
msgstr ""
|
msgstr "Eklenti ayarları"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/content/add/Subscribe.tsx
|
||||||
msgid "Feed name"
|
msgid "Feed name"
|
||||||
@@ -337,6 +345,14 @@ msgstr "Feed URL'si"
|
|||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
|
msgstr "Tüm feed'lerimi şimdi çek"
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
@@ -369,7 +385,7 @@ msgstr "Oluşturulan besleme url'si"
|
|||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
msgid "Go to {0}"
|
msgid "Go to {0}"
|
||||||
msgstr ""
|
msgstr "{0}'a git"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Go to the All view"
|
msgid "Go to the All view"
|
||||||
@@ -463,7 +479,7 @@ msgstr "Çıkış"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Long press"
|
msgid "Long press"
|
||||||
msgstr ""
|
msgstr "Uzun bas"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -495,7 +511,7 @@ msgstr "Metrikler"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Middle click"
|
msgid "Middle click"
|
||||||
msgstr ""
|
msgstr "Orta tuş ile tıkla"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Move the page down"
|
msgid "Move the page down"
|
||||||
@@ -560,7 +576,7 @@ msgstr "Hata!"
|
|||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
msgid "Open CommaFeed"
|
msgid "Open CommaFeed"
|
||||||
msgstr ""
|
msgstr "CommaFeed'i aç"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Open current entry in a new tab"
|
msgid "Open current entry in a new tab"
|
||||||
@@ -576,11 +592,11 @@ msgstr "Bağlantıyı aç"
|
|||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
msgid "Open link in new background tab"
|
msgid "Open link in new background tab"
|
||||||
msgstr ""
|
msgstr "Bağlantıyı arkaplanda yeni sekmede aç"
|
||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
msgid "Open link in new tab"
|
msgid "Open link in new tab"
|
||||||
msgstr ""
|
msgstr "Bağlantıyı yeni sekmede aç"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Open next entry"
|
msgid "Open next entry"
|
||||||
@@ -596,7 +612,7 @@ msgstr "Geçerli girişi aç/kapat"
|
|||||||
|
|
||||||
#: src/pages/app/AddPage.tsx
|
#: src/pages/app/AddPage.tsx
|
||||||
msgid "OPML"
|
msgid "OPML"
|
||||||
msgstr ""
|
msgstr "OPML"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "OPML export"
|
msgid "OPML export"
|
||||||
@@ -642,7 +658,7 @@ msgstr "Konum"
|
|||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
msgstr ""
|
msgstr "Önceki"
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
@@ -663,11 +679,12 @@ msgstr "Bu CommaFeed örneğinde kayıtlar kapalı"
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr "REST API"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr "Sağ tık"
|
||||||
|
|
||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
@@ -716,18 +733,27 @@ msgstr "Paylaş"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "Siteleri paylaşma"
|
msgstr "Siteleri paylaşma"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "Vardiya"
|
msgstr "Vardiya"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr "Sağ tıkta CommaFeed'in kendi menüsünü göster"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr "Tüm girişleri okundu işaretlerken onay iste"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr "Giriş menüsünü göster (masaüstü)"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (mobile)"
|
msgid "Show entry menu (mobile)"
|
||||||
msgstr ""
|
msgstr "Giriş menüsünü göster (mobil)"
|
||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "Show feeds and categories with no unread entries"
|
msgid "Show feeds and categories with no unread entries"
|
||||||
@@ -737,6 +763,10 @@ msgstr "Okunmamış girişi olmayan beslemeleri ve kategorileri göster"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "Klavye kısayolu yardımını göster"
|
msgstr "Klavye kısayolu yardımını göster"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr "Orijinal tarayıcı menüsünü göster (masaüstü)"
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -782,7 +812,7 @@ msgstr "Başarı"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Swipe header to the right"
|
msgid "Swipe header to the right"
|
||||||
msgstr ""
|
msgstr "Başlığı sağa kaydır"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -792,7 +822,7 @@ msgstr "Karanlık temaya geç"
|
|||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Switch to light theme"
|
msgid "Switch to light theme"
|
||||||
msgstr "Açık temaya geç"
|
msgstr "Aydınlık temaya geç"
|
||||||
|
|
||||||
#: src/components/content/FeedEntryFooter.tsx
|
#: src/components/content/FeedEntryFooter.tsx
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
@@ -812,6 +842,10 @@ msgstr "Geçerli girişin okuma durumunu değiştir"
|
|||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
|
msgstr "Kenar çubuğunu göster/gizle"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
@@ -820,11 +854,11 @@ msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo"
|
|||||||
|
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
msgid "Try the demo!"
|
msgid "Try the demo!"
|
||||||
msgstr ""
|
msgstr "Demo'yu deneyin!"
|
||||||
|
|
||||||
#: src/components/header/Header.tsx
|
#: src/components/header/Header.tsx
|
||||||
msgid "Unread"
|
msgid "Unread"
|
||||||
msgstr "Okunmadı"
|
msgstr "Okunmamış"
|
||||||
|
|
||||||
#: src/components/content/FeedEntryContextMenu.tsx
|
#: src/components/content/FeedEntryContextMenu.tsx
|
||||||
#: src/components/content/FeedEntryFooter.tsx
|
#: src/components/content/FeedEntryFooter.tsx
|
||||||
@@ -855,8 +889,8 @@ msgstr "Web sitesi"
|
|||||||
|
|
||||||
#: src/pages/app/FeedEntriesPage.tsx
|
#: src/pages/app/FeedEntriesPage.tsx
|
||||||
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
|
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
|
||||||
msgstr "Henüz aboneliğiniz yok. "
|
msgstr "Henüz aboneliğiniz yok. Sayfanın üstündeki + işaretiyle feed ekleyebilirsiniz."
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
msgid "Your feeds have been queued for refresh."
|
msgid "Your feeds have been queued for refresh."
|
||||||
msgstr ""
|
msgstr "Feed'leriniz yenileme için sıraya alındı."
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ msgstr "opml 文件是包含提要 URL 和类别的 XML 文件。"
|
|||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "分析饲料"
|
msgstr "分析饲料"
|
||||||
|
|
||||||
|
#: src/components/AnnouncementDialog.tsx
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API 密钥"
|
msgstr "API 密钥"
|
||||||
@@ -175,6 +179,10 @@ msgstr "检查提要是否正常工作"
|
|||||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "CommaFeed next unread item"
|
msgid "CommaFeed next unread item"
|
||||||
msgstr "CommaFeed 下一个未读项目"
|
msgstr "CommaFeed 下一个未读项目"
|
||||||
@@ -339,6 +347,14 @@ msgstr "供稿网址"
|
|||||||
msgid "Fetch all my feeds now"
|
msgid "Fetch all my feeds now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
msgid "Fever API URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/content/add/ImportOpml.tsx
|
#: src/components/content/add/ImportOpml.tsx
|
||||||
msgid "file is required"
|
msgid "file is required"
|
||||||
msgstr "文件是必需的"
|
msgstr "文件是必需的"
|
||||||
@@ -665,6 +681,7 @@ msgstr "此 CommaFeed 实例上的注册已关闭"
|
|||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Right click"
|
msgid "Right click"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -716,11 +733,20 @@ msgstr "分享"
|
|||||||
msgid "Sharing sites"
|
msgid "Sharing sites"
|
||||||
msgstr "共享站点"
|
msgstr "共享站点"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Shift"
|
msgid "Shift"
|
||||||
msgstr "换档"
|
msgstr "换档"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show CommaFeed's own context menu on right click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Show confirmation when marking all entries as read"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Show entry menu (desktop)"
|
msgid "Show entry menu (desktop)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -737,6 +763,10 @@ msgstr "显示没有未读条目的提要和类别"
|
|||||||
msgid "Show keyboard shortcut help"
|
msgid "Show keyboard shortcut help"
|
||||||
msgstr "显示键盘快捷键帮助"
|
msgstr "显示键盘快捷键帮助"
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Show native menu (desktop)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/auth/RegistrationPage.tsx
|
#: src/pages/auth/RegistrationPage.tsx
|
||||||
#: src/pages/WelcomePage.tsx
|
#: src/pages/WelcomePage.tsx
|
||||||
@@ -814,6 +844,10 @@ msgstr "切换当前条目的读取状态"
|
|||||||
msgid "Toggle sidebar"
|
msgid "Toggle sidebar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
|
msgid "Toggle starred status of current entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/LoginPage.tsx
|
#: src/pages/auth/LoginPage.tsx
|
||||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||||
msgstr "使用演示帐户试用 CommaFeed:demo/demo"
|
msgstr "使用演示帐户试用 CommaFeed:demo/demo"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const shownMeters: { [key: string]: string } = {
|
|||||||
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
|
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
|
||||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
|
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
|
||||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
|
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
|
||||||
|
"com.commafeed.backend.service.DatabaseCleaningService.entriesDeleted": "Entries deleted",
|
||||||
}
|
}
|
||||||
|
|
||||||
const shownGauges: { [key: string]: string } = {
|
const shownGauges: { [key: string]: string } = {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { redirectToAdd, redirectToRootCategory } from "app/slices/redirect"
|
|||||||
import { reloadTree, setMobileMenuOpen, setSidebarWidth } from "app/slices/tree"
|
import { reloadTree, setMobileMenuOpen, setSidebarWidth } from "app/slices/tree"
|
||||||
import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user"
|
import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { AnnouncementDialog } from "components/AnnouncementDialog"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
import { Logo } from "components/Logo"
|
import { Logo } from "components/Logo"
|
||||||
import { OnDesktop } from "components/responsive/OnDesktop"
|
import { OnDesktop } from "components/responsive/OnDesktop"
|
||||||
@@ -93,6 +94,7 @@ export default function Layout(props: LayoutProps) {
|
|||||||
const { loading } = useAppLoading()
|
const { loading } = useAppLoading()
|
||||||
const mobile = useMobile()
|
const mobile = useMobile()
|
||||||
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
||||||
|
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
|
||||||
const sidebarHidden = props.sidebarWidth === 0
|
const sidebarHidden = props.sidebarWidth === 0
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
useWebSocket()
|
useWebSocket()
|
||||||
@@ -100,16 +102,21 @@ export default function Layout(props: LayoutProps) {
|
|||||||
const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth))
|
const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// load initial data
|
||||||
dispatch(reloadSettings())
|
dispatch(reloadSettings())
|
||||||
dispatch(reloadProfile())
|
dispatch(reloadProfile())
|
||||||
dispatch(reloadTree())
|
dispatch(reloadTree())
|
||||||
dispatch(reloadTags())
|
dispatch(reloadTags())
|
||||||
|
|
||||||
// reload tree periodically
|
|
||||||
const id = setInterval(() => dispatch(reloadTree()), 30000)
|
|
||||||
return () => clearInterval(id)
|
|
||||||
}, [dispatch])
|
}, [dispatch])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// reload tree periodically if not receiving websocket events
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (!webSocketConnected) dispatch(reloadTree())
|
||||||
|
}, 30000)
|
||||||
|
return () => clearInterval(timer)
|
||||||
|
}, [dispatch, webSocketConnected])
|
||||||
|
|
||||||
const burger = (
|
const burger = (
|
||||||
<Center>
|
<Center>
|
||||||
<Burger
|
<Burger
|
||||||
@@ -197,6 +204,7 @@ export default function Layout(props: LayoutProps) {
|
|||||||
>
|
>
|
||||||
<Box id="content" className={classes.mainContent}>
|
<Box id="content" className={classes.mainContent}>
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
|
<AnnouncementDialog />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ app:
|
|||||||
# number of database updating threads
|
# number of database updating threads
|
||||||
databaseUpdateThreads: 1
|
databaseUpdateThreads: 1
|
||||||
|
|
||||||
|
# rows to delete per query while cleaning up old entries
|
||||||
|
databaseCleanupBatchSize: 100
|
||||||
|
|
||||||
# settings for sending emails (password recovery)
|
# settings for sending emails (password recovery)
|
||||||
smtpHost: localhost
|
smtpHost: localhost
|
||||||
smtpPort: 25
|
smtpPort: 25
|
||||||
@@ -81,8 +84,12 @@ app:
|
|||||||
|
|
||||||
# Database connection
|
# Database connection
|
||||||
# -------------------
|
# -------------------
|
||||||
|
# for MariaDB
|
||||||
|
# driverClass is org.mariadb.jdbc.Driver
|
||||||
|
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
||||||
|
#
|
||||||
# for MySQL
|
# for MySQL
|
||||||
# driverClass is com.mysql.jdbc.Driver
|
# driverClass is com.mysql.cj.jdbc.Driver
|
||||||
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
||||||
#
|
#
|
||||||
# for PostgreSQL
|
# for PostgreSQL
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ app:
|
|||||||
# number of database updating threads
|
# number of database updating threads
|
||||||
databaseUpdateThreads: 1
|
databaseUpdateThreads: 1
|
||||||
|
|
||||||
|
# rows to delete per query while cleaning up old entries
|
||||||
|
databaseCleanupBatchSize: 100
|
||||||
|
|
||||||
# settings for sending emails (password recovery)
|
# settings for sending emails (password recovery)
|
||||||
smtpHost:
|
smtpHost:
|
||||||
smtpPort:
|
smtpPort:
|
||||||
@@ -82,8 +85,12 @@ app:
|
|||||||
|
|
||||||
# Database connection
|
# Database connection
|
||||||
# -------------------
|
# -------------------
|
||||||
|
# for MariaDB
|
||||||
|
# driverClass is org.mariadb.jdbc.Driver
|
||||||
|
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
||||||
|
#
|
||||||
# for MySQL
|
# for MySQL
|
||||||
# driverClass is com.mysql.jdbc.Driver
|
# driverClass is com.mysql.cj.jdbc.Driver
|
||||||
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
||||||
#
|
#
|
||||||
# for PostgreSQL
|
# for PostgreSQL
|
||||||
@@ -96,7 +103,7 @@ app:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
driverClass: org.h2.Driver
|
driverClass: org.h2.Driver
|
||||||
url: jdbc:h2:./db/commafeed
|
url: jdbc:h2:/commafeed/data/db
|
||||||
user: sa
|
user: sa
|
||||||
password: sa
|
password: sa
|
||||||
properties:
|
properties:
|
||||||
@@ -144,4 +151,3 @@ redis:
|
|||||||
timeout: 2000
|
timeout: 2000
|
||||||
database: 0
|
database: 0
|
||||||
maxTotal: 500
|
maxTotal: 500
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commafeed-server</artifactId>
|
<artifactId>commafeed-server</artifactId>
|
||||||
<name>CommaFeed Server</name>
|
<name>CommaFeed Server</name>
|
||||||
@@ -142,6 +142,12 @@
|
|||||||
<title>CommaFeed</title>
|
<title>CommaFeed</title>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</info>
|
</info>
|
||||||
|
<securityDefinitions>
|
||||||
|
<securityDefinition>
|
||||||
|
<name>basicAuth</name>
|
||||||
|
<type>basic</type>
|
||||||
|
</securityDefinition>
|
||||||
|
</securityDefinitions>
|
||||||
<typesToSkip>
|
<typesToSkip>
|
||||||
<typeToSkip>com.commafeed.backend.model.User</typeToSkip>
|
<typeToSkip>com.commafeed.backend.model.User</typeToSkip>
|
||||||
</typesToSkip>
|
</typesToSkip>
|
||||||
@@ -226,13 +232,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed-client</artifactId>
|
<artifactId>commafeed-client</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.26</version>
|
<version>1.18.30</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -444,6 +450,11 @@
|
|||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
<version>8.0.33</version>
|
<version>8.0.33</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mariadb.jdbc</groupId>
|
||||||
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
|
<version>3.1.4</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.commafeed.frontend.resource.FeedREST;
|
|||||||
import com.commafeed.frontend.resource.PubSubHubbubCallbackREST;
|
import com.commafeed.frontend.resource.PubSubHubbubCallbackREST;
|
||||||
import com.commafeed.frontend.resource.ServerREST;
|
import com.commafeed.frontend.resource.ServerREST;
|
||||||
import com.commafeed.frontend.resource.UserREST;
|
import com.commafeed.frontend.resource.UserREST;
|
||||||
|
import com.commafeed.frontend.resource.fever.FeverREST;
|
||||||
import com.commafeed.frontend.servlet.AnalyticsServlet;
|
import com.commafeed.frontend.servlet.AnalyticsServlet;
|
||||||
import com.commafeed.frontend.servlet.CustomCssServlet;
|
import com.commafeed.frontend.servlet.CustomCssServlet;
|
||||||
import com.commafeed.frontend.servlet.CustomJsServlet;
|
import com.commafeed.frontend.servlet.CustomJsServlet;
|
||||||
@@ -147,7 +148,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
|||||||
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
|
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
|
||||||
|
|
||||||
// session management
|
// session management
|
||||||
environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build());
|
environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build(config.getDataSourceFactory()));
|
||||||
|
|
||||||
// support for "@SecurityCheck User user" injection
|
// support for "@SecurityCheck User user" injection
|
||||||
environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class)));
|
environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class)));
|
||||||
@@ -163,6 +164,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
|||||||
environment.jersey().register(injector.getInstance(PubSubHubbubCallbackREST.class));
|
environment.jersey().register(injector.getInstance(PubSubHubbubCallbackREST.class));
|
||||||
environment.jersey().register(injector.getInstance(ServerREST.class));
|
environment.jersey().register(injector.getInstance(ServerREST.class));
|
||||||
environment.jersey().register(injector.getInstance(UserREST.class));
|
environment.jersey().register(injector.getInstance(UserREST.class));
|
||||||
|
environment.jersey().register(injector.getInstance(FeverREST.class));
|
||||||
|
|
||||||
// Servlets
|
// Servlets
|
||||||
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
|
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import javax.validation.Valid;
|
|||||||
import javax.validation.constraints.Min;
|
import javax.validation.constraints.Min;
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Positive;
|
||||||
|
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
|
||||||
@@ -95,6 +96,11 @@ public class CommaFeedConfiguration extends Configuration {
|
|||||||
@Valid
|
@Valid
|
||||||
private Integer databaseUpdateThreads;
|
private Integer databaseUpdateThreads;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Positive
|
||||||
|
@Valid
|
||||||
|
private Integer databaseCleanupBatchSize = 100;
|
||||||
|
|
||||||
private String smtpHost;
|
private String smtpHost;
|
||||||
private int smtpPort;
|
private int smtpPort;
|
||||||
private boolean smtpTls;
|
private boolean smtpTls;
|
||||||
|
|||||||
@@ -28,13 +28,10 @@ public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
|||||||
return query().select(content).from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)).fetch();
|
return query().select(content).from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)).fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int deleteWithoutEntries(int max) {
|
public long deleteWithoutEntries(int max) {
|
||||||
|
|
||||||
JPQLQuery<Integer> subQuery = JPAExpressions.selectOne().from(entry).where(entry.content.id.eq(content.id));
|
JPQLQuery<Integer> subQuery = JPAExpressions.selectOne().from(entry).where(entry.content.id.eq(content.id));
|
||||||
List<FeedEntryContent> list = query().selectFrom(content).where(subQuery.notExists()).limit(max).fetch();
|
List<Long> ids = query().select(content.id).from(content).where(subQuery.notExists()).limit(max).fetch();
|
||||||
|
|
||||||
int deleted = list.size();
|
return deleteQuery(content).where(content.id.in(ids)).execute();
|
||||||
delete(list);
|
|
||||||
return deleted;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int delete(Long feedId, long max) {
|
public int delete(Long feedId, long max) {
|
||||||
|
|
||||||
List<FeedEntry> list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).limit(max).fetch();
|
List<FeedEntry> list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).limit(max).fetch();
|
||||||
return delete(list);
|
return delete(list);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JPAQuery<FeedEntry> buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List<FeedEntryKeyword> keywords,
|
private JPAQuery<FeedEntry> buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List<FeedEntryKeyword> keywords,
|
||||||
Date newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag) {
|
Date newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag, Long minEntryId, Long maxEntryId) {
|
||||||
|
|
||||||
JPAQuery<FeedEntry> query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed()));
|
JPAQuery<FeedEntry> query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed()));
|
||||||
|
|
||||||
@@ -159,6 +159,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
query.where(entry.inserted.goe(newerThan));
|
query.where(entry.inserted.goe(newerThan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (minEntryId != null) {
|
||||||
|
query.where(entry.id.gt(minEntryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxEntryId != null) {
|
||||||
|
query.where(entry.id.lt(maxEntryId));
|
||||||
|
}
|
||||||
|
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
if (order == ReadingOrder.desc) {
|
if (order == ReadingOrder.desc) {
|
||||||
query.where(entry.updated.gt(last.getEntryUpdated()));
|
query.where(entry.updated.gt(last.getEntryUpdated()));
|
||||||
@@ -189,7 +197,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
|
|
||||||
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
|
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
|
||||||
List<FeedEntryKeyword> keywords, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent,
|
List<FeedEntryKeyword> keywords, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent,
|
||||||
boolean onlyIds, String tag) {
|
boolean onlyIds, String tag, Long minEntryId, Long maxEntryId) {
|
||||||
int capacity = offset + limit;
|
int capacity = offset + limit;
|
||||||
|
|
||||||
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
|
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
|
||||||
@@ -197,7 +205,8 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<>(capacity, comparator);
|
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<>(capacity, comparator);
|
||||||
for (FeedSubscription sub : subs) {
|
for (FeedSubscription sub : subs) {
|
||||||
FeedEntryStatus last = (order != null && set.isFull()) ? set.last() : null;
|
FeedEntryStatus last = (order != null && set.isFull()) ? set.last() : null;
|
||||||
JPAQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
|
JPAQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag, minEntryId,
|
||||||
|
maxEntryId);
|
||||||
List<Tuple> tuples = query.select(entry.id, entry.updated, status.id, entry.content.title).fetch();
|
List<Tuple> tuples = query.select(entry.id, entry.updated, status.id, entry.content.title).fetch();
|
||||||
|
|
||||||
for (Tuple tuple : tuples) {
|
for (Tuple tuple : tuples) {
|
||||||
@@ -250,7 +259,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
|
|
||||||
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
|
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
|
||||||
UnreadCount uc = null;
|
UnreadCount uc = null;
|
||||||
JPAQuery<FeedEntry> query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null);
|
JPAQuery<FeedEntry> query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null, null, null);
|
||||||
List<Tuple> tuples = query.select(entry.count(), entry.updated.max()).fetch();
|
List<Tuple> tuples = query.select(entry.count(), entry.updated.max()).fetch();
|
||||||
for (Tuple tuple : tuples) {
|
for (Tuple tuple : tuples) {
|
||||||
Long count = tuple.get(entry.count());
|
Long count = tuple.get(entry.count());
|
||||||
@@ -270,8 +279,13 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedEntryStatus> getOldStatuses(Date olderThan, int limit) {
|
public long deleteOldStatuses(Date olderThan, int limit) {
|
||||||
return query().selectFrom(status).where(status.entryInserted.lt(olderThan), status.starred.isFalse()).limit(limit).fetch();
|
List<Long> ids = query().select(status.id)
|
||||||
|
.from(status)
|
||||||
|
.where(status.entryInserted.lt(olderThan), status.starred.isFalse())
|
||||||
|
.limit(limit)
|
||||||
|
.fetch();
|
||||||
|
return deleteQuery(status).where(status.id.in(ids)).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.hibernate.annotations.QueryHints;
|
|||||||
|
|
||||||
import com.commafeed.backend.model.AbstractModel;
|
import com.commafeed.backend.model.AbstractModel;
|
||||||
import com.querydsl.core.types.EntityPath;
|
import com.querydsl.core.types.EntityPath;
|
||||||
|
import com.querydsl.jpa.impl.JPADeleteClause;
|
||||||
import com.querydsl.jpa.impl.JPAQuery;
|
import com.querydsl.jpa.impl.JPAQuery;
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
import com.querydsl.jpa.impl.JPAUpdateClause;
|
import com.querydsl.jpa.impl.JPAUpdateClause;
|
||||||
@@ -30,6 +31,10 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
|
|||||||
return new JPAUpdateClause(currentSession(), entityPath);
|
return new JPAUpdateClause(currentSession(), entityPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected JPADeleteClause deleteQuery(EntityPath<T> entityPath) {
|
||||||
|
return new JPADeleteClause(currentSession(), entityPath);
|
||||||
|
}
|
||||||
|
|
||||||
public void saveOrUpdate(T model) {
|
public void saveOrUpdate(T model) {
|
||||||
persist(model);
|
persist(model);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,11 +94,10 @@ public class FeedRefreshWorker {
|
|||||||
|
|
||||||
return new FeedRefreshWorkerResult(feed, Collections.emptyList());
|
return new FeedRefreshWorkerResult(feed, Collections.emptyList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String message = "Unable to refresh feed " + feed.getUrl() + " : " + e.getMessage();
|
log.debug("unable to refresh feed {}", feed.getUrl(), e);
|
||||||
log.debug(e.getClass().getName() + " " + message, e);
|
|
||||||
|
|
||||||
feed.setErrorCount(feed.getErrorCount() + 1);
|
feed.setErrorCount(feed.getErrorCount() + 1);
|
||||||
feed.setMessage(message);
|
feed.setMessage("Unable to refresh feed : " + e.getMessage());
|
||||||
feed.setDisabledUntil(refreshIntervalCalculator.onFetchError(feed));
|
feed.setDisabledUntil(refreshIntervalCalculator.onFetchError(feed));
|
||||||
|
|
||||||
return new FeedRefreshWorkerResult(feed, Collections.emptyList());
|
return new FeedRefreshWorkerResult(feed, Collections.emptyList());
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
package com.commafeed.backend.feed;
|
package com.commafeed.backend.feed;
|
||||||
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -22,16 +19,10 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
|
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Document.OutputSettings;
|
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.jsoup.nodes.Entities.EscapeMode;
|
|
||||||
import org.jsoup.safety.Cleaner;
|
|
||||||
import org.jsoup.safety.Safelist;
|
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
import org.netpreserve.urlcanon.Canonicalizer;
|
import org.netpreserve.urlcanon.Canonicalizer;
|
||||||
import org.netpreserve.urlcanon.ParsedUrl;
|
import org.netpreserve.urlcanon.ParsedUrl;
|
||||||
import org.w3c.css.sac.InputSource;
|
|
||||||
import org.w3c.dom.css.CSSStyleDeclaration;
|
|
||||||
|
|
||||||
import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
|
import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
@@ -41,7 +32,6 @@ import com.google.gwt.i18n.client.HasDirection.Direction;
|
|||||||
import com.google.gwt.i18n.shared.BidiUtils;
|
import com.google.gwt.i18n.shared.BidiUtils;
|
||||||
import com.ibm.icu.text.CharsetDetector;
|
import com.ibm.icu.text.CharsetDetector;
|
||||||
import com.ibm.icu.text.CharsetMatch;
|
import com.ibm.icu.text.CharsetMatch;
|
||||||
import com.steadystate.css.parser.CSSOMParser;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -54,12 +44,6 @@ public class FeedUtils {
|
|||||||
|
|
||||||
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
||||||
|
|
||||||
private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList("height", "width", "border");
|
|
||||||
private static final List<String> ALLOWED_IMG_CSS_RULES = Arrays.asList("display", "width", "height");
|
|
||||||
private static final char[] FORBIDDEN_CSS_RULE_CHARACTERS = new char[] { '(', ')' };
|
|
||||||
|
|
||||||
private static final Safelist WHITELIST = buildWhiteList();
|
|
||||||
|
|
||||||
public static String truncate(String string, int length) {
|
public static String truncate(String string, int length) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
string = string.substring(0, Math.min(length, string.length()));
|
string = string.substring(0, Math.min(length, string.length()));
|
||||||
@@ -67,40 +51,6 @@ public class FeedUtils {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized Safelist buildWhiteList() {
|
|
||||||
Safelist whitelist = new Safelist();
|
|
||||||
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1",
|
|
||||||
"h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong", "sub", "sup",
|
|
||||||
"table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
|
|
||||||
|
|
||||||
whitelist.addAttributes("div", "dir");
|
|
||||||
whitelist.addAttributes("pre", "dir");
|
|
||||||
whitelist.addAttributes("code", "dir");
|
|
||||||
whitelist.addAttributes("table", "dir");
|
|
||||||
whitelist.addAttributes("p", "dir");
|
|
||||||
whitelist.addAttributes("a", "href", "title");
|
|
||||||
whitelist.addAttributes("blockquote", "cite");
|
|
||||||
whitelist.addAttributes("col", "span", "width");
|
|
||||||
whitelist.addAttributes("colgroup", "span", "width");
|
|
||||||
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
|
|
||||||
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width", "style");
|
|
||||||
whitelist.addAttributes("ol", "start", "type");
|
|
||||||
whitelist.addAttributes("q", "cite");
|
|
||||||
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
|
|
||||||
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
|
|
||||||
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
|
|
||||||
whitelist.addAttributes("ul", "type");
|
|
||||||
|
|
||||||
whitelist.addProtocols("a", "href", "ftp", "http", "https", "magnet", "mailto");
|
|
||||||
whitelist.addProtocols("blockquote", "cite", "http", "https");
|
|
||||||
whitelist.addProtocols("img", "src", "http", "https");
|
|
||||||
whitelist.addProtocols("q", "cite", "http", "https");
|
|
||||||
|
|
||||||
whitelist.addEnforcedAttribute("a", "target", "_blank");
|
|
||||||
whitelist.addEnforcedAttribute("a", "rel", "noreferrer");
|
|
||||||
return whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
|
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
|
||||||
* feed
|
* feed
|
||||||
@@ -233,87 +183,6 @@ public class FeedUtils {
|
|||||||
return encoding;
|
return encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String handleContent(String content, String baseUri, boolean keepTextOnly) {
|
|
||||||
if (StringUtils.isNotBlank(content)) {
|
|
||||||
baseUri = StringUtils.trimToEmpty(baseUri);
|
|
||||||
|
|
||||||
Document dirty = Jsoup.parseBodyFragment(content, baseUri);
|
|
||||||
Cleaner cleaner = new Cleaner(WHITELIST);
|
|
||||||
Document clean = cleaner.clean(dirty);
|
|
||||||
|
|
||||||
for (Element e : clean.select("iframe[style]")) {
|
|
||||||
String style = e.attr("style");
|
|
||||||
String escaped = escapeIFrameCss(style);
|
|
||||||
e.attr("style", escaped);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Element e : clean.select("img[style]")) {
|
|
||||||
String style = e.attr("style");
|
|
||||||
String escaped = escapeImgCss(style);
|
|
||||||
e.attr("style", escaped);
|
|
||||||
}
|
|
||||||
|
|
||||||
clean.outputSettings(new OutputSettings().escapeMode(EscapeMode.base).prettyPrint(false));
|
|
||||||
Element body = clean.body();
|
|
||||||
if (keepTextOnly) {
|
|
||||||
content = body.text();
|
|
||||||
} else {
|
|
||||||
content = body.html();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String escapeIFrameCss(String orig) {
|
|
||||||
String rule = "";
|
|
||||||
CSSOMParser parser = new CSSOMParser();
|
|
||||||
try {
|
|
||||||
List<String> rules = new ArrayList<>();
|
|
||||||
CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
|
||||||
|
|
||||||
for (int i = 0; i < decl.getLength(); i++) {
|
|
||||||
String property = decl.item(i);
|
|
||||||
String value = decl.getPropertyValue(property);
|
|
||||||
if (StringUtils.isBlank(property) || StringUtils.isBlank(value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ALLOWED_IFRAME_CSS_RULES.contains(property) && StringUtils.containsNone(value, FORBIDDEN_CSS_RULE_CHARACTERS)) {
|
|
||||||
rules.add(property + ":" + decl.getPropertyValue(property) + ";");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rule = StringUtils.join(rules, "");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String escapeImgCss(String orig) {
|
|
||||||
String rule = "";
|
|
||||||
CSSOMParser parser = new CSSOMParser();
|
|
||||||
try {
|
|
||||||
List<String> rules = new ArrayList<>();
|
|
||||||
CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
|
||||||
|
|
||||||
for (int i = 0; i < decl.getLength(); i++) {
|
|
||||||
String property = decl.item(i);
|
|
||||||
String value = decl.getPropertyValue(property);
|
|
||||||
if (StringUtils.isBlank(property) || StringUtils.isBlank(value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ALLOWED_IMG_CSS_RULES.contains(property) && StringUtils.containsNone(value, FORBIDDEN_CSS_RULE_CHARACTERS)) {
|
|
||||||
rules.add(property + ":" + decl.getPropertyValue(property) + ";");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rule = StringUtils.join(rules, "");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRTL(FeedEntry entry) {
|
public static boolean isRTL(FeedEntry entry) {
|
||||||
String text = entry.getContent().getContent();
|
String text = entry.getContent().getContent();
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ public class UserSettings extends AbstractModel {
|
|||||||
private int scrollSpeed;
|
private int scrollSpeed;
|
||||||
|
|
||||||
private boolean alwaysScrollToEntry;
|
private boolean alwaysScrollToEntry;
|
||||||
|
private boolean markAllAsReadConfirmation;
|
||||||
|
private boolean customContextMenu;
|
||||||
|
|
||||||
private boolean email;
|
private boolean email;
|
||||||
private boolean gmail;
|
private boolean gmail;
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import java.util.List;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import com.codahale.metrics.Meter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
@@ -14,7 +17,6 @@ import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
|||||||
import com.commafeed.backend.dao.UnitOfWork;
|
import com.commafeed.backend.dao.UnitOfWork;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,29 +24,42 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class DatabaseCleaningService {
|
public class DatabaseCleaningService {
|
||||||
|
|
||||||
private static final int BATCH_SIZE = 100;
|
private final int batchSize;
|
||||||
|
|
||||||
private final UnitOfWork unitOfWork;
|
private final UnitOfWork unitOfWork;
|
||||||
private final FeedDAO feedDAO;
|
private final FeedDAO feedDAO;
|
||||||
private final FeedEntryDAO feedEntryDAO;
|
private final FeedEntryDAO feedEntryDAO;
|
||||||
private final FeedEntryContentDAO feedEntryContentDAO;
|
private final FeedEntryContentDAO feedEntryContentDAO;
|
||||||
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
||||||
|
private final Meter entriesDeletedMeter;
|
||||||
|
|
||||||
public long cleanFeedsWithoutSubscriptions() {
|
@Inject
|
||||||
|
public DatabaseCleaningService(CommaFeedConfiguration config, UnitOfWork unitOfWork, FeedDAO feedDAO, FeedEntryDAO feedEntryDAO,
|
||||||
|
FeedEntryContentDAO feedEntryContentDAO, FeedEntryStatusDAO feedEntryStatusDAO, MetricRegistry metrics) {
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
|
this.feedDAO = feedDAO;
|
||||||
|
this.feedEntryDAO = feedEntryDAO;
|
||||||
|
this.feedEntryContentDAO = feedEntryContentDAO;
|
||||||
|
this.feedEntryStatusDAO = feedEntryStatusDAO;
|
||||||
|
this.batchSize = config.getApplicationSettings().getDatabaseCleanupBatchSize();
|
||||||
|
this.entriesDeletedMeter = metrics.meter(MetricRegistry.name(getClass(), "entriesDeleted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanFeedsWithoutSubscriptions() {
|
||||||
log.info("cleaning feeds without subscriptions");
|
log.info("cleaning feeds without subscriptions");
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = 0;
|
int deleted;
|
||||||
long entriesTotal = 0;
|
long entriesTotal = 0;
|
||||||
do {
|
do {
|
||||||
List<Feed> feeds = unitOfWork.call(() -> feedDAO.findWithoutSubscriptions(1));
|
List<Feed> feeds = unitOfWork.call(() -> feedDAO.findWithoutSubscriptions(1));
|
||||||
for (Feed feed : feeds) {
|
for (Feed feed : feeds) {
|
||||||
int entriesDeleted = 0;
|
long entriesDeleted;
|
||||||
do {
|
do {
|
||||||
entriesDeleted = unitOfWork.call(() -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE));
|
entriesDeleted = unitOfWork.call(() -> feedEntryDAO.delete(feed.getId(), batchSize));
|
||||||
|
entriesDeletedMeter.mark(entriesDeleted);
|
||||||
entriesTotal += entriesDeleted;
|
entriesTotal += entriesDeleted;
|
||||||
log.info("removed {} entries for feeds without subscriptions", entriesTotal);
|
log.info("removed {} entries for feeds without subscriptions", entriesTotal);
|
||||||
} while (entriesDeleted > 0);
|
} while (entriesDeleted > 0);
|
||||||
@@ -54,26 +69,24 @@ public class DatabaseCleaningService {
|
|||||||
log.info("removed {} feeds without subscriptions", total);
|
log.info("removed {} feeds without subscriptions", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
log.info("cleanup done: {} feeds without subscriptions deleted", total);
|
log.info("cleanup done: {} feeds without subscriptions deleted", total);
|
||||||
return total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long cleanContentsWithoutEntries() {
|
public void cleanContentsWithoutEntries() {
|
||||||
log.info("cleaning contents without entries");
|
log.info("cleaning contents without entries");
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = 0;
|
long deleted;
|
||||||
do {
|
do {
|
||||||
deleted = unitOfWork.call(() -> feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE));
|
deleted = unitOfWork.call(() -> feedEntryContentDAO.deleteWithoutEntries(batchSize));
|
||||||
total += deleted;
|
total += deleted;
|
||||||
log.info("removed {} contents without entries", total);
|
log.info("removed {} contents without entries", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
log.info("cleanup done: {} contents without entries deleted", total);
|
log.info("cleanup done: {} contents without entries deleted", total);
|
||||||
return total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long cleanEntriesForFeedsExceedingCapacity(final int maxFeedCapacity) {
|
public void cleanEntriesForFeedsExceedingCapacity(final int maxFeedCapacity) {
|
||||||
long total = 0;
|
long total = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
List<FeedCapacity> feeds = unitOfWork.call(() -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, BATCH_SIZE));
|
List<FeedCapacity> feeds = unitOfWork.call(() -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, batchSize));
|
||||||
if (feeds.isEmpty()) {
|
if (feeds.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -82,7 +95,8 @@ public class DatabaseCleaningService {
|
|||||||
long remaining = feed.getCapacity() - maxFeedCapacity;
|
long remaining = feed.getCapacity() - maxFeedCapacity;
|
||||||
do {
|
do {
|
||||||
final long rem = remaining;
|
final long rem = remaining;
|
||||||
int deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(BATCH_SIZE, rem)));
|
int deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(batchSize, rem)));
|
||||||
|
entriesDeletedMeter.mark(deleted);
|
||||||
total += deleted;
|
total += deleted;
|
||||||
remaining -= deleted;
|
remaining -= deleted;
|
||||||
log.info("removed {} entries for feeds exceeding capacity", total);
|
log.info("removed {} entries for feeds exceeding capacity", total);
|
||||||
@@ -90,19 +104,17 @@ public class DatabaseCleaningService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("cleanup done: {} entries for feeds exceeding capacity deleted", total);
|
log.info("cleanup done: {} entries for feeds exceeding capacity deleted", total);
|
||||||
return total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long cleanStatusesOlderThan(final Date olderThan) {
|
public void cleanStatusesOlderThan(final Date olderThan) {
|
||||||
log.info("cleaning old read statuses");
|
log.info("cleaning old read statuses");
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = 0;
|
long deleted;
|
||||||
do {
|
do {
|
||||||
deleted = unitOfWork.call(() -> feedEntryStatusDAO.delete(feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE)));
|
deleted = unitOfWork.call(() -> feedEntryStatusDAO.deleteOldStatuses(olderThan, batchSize));
|
||||||
total += deleted;
|
total += deleted;
|
||||||
log.info("removed {} old read statuses", total);
|
log.info("removed {} old read statuses", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
log.info("cleanup done: {} old read statuses deleted", total);
|
log.info("cleanup done: {} old read statuses deleted", total);
|
||||||
return total;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.commafeed.backend.service;
|
package com.commafeed.backend.service;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -8,27 +11,47 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Document.OutputSettings;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.nodes.Entities.EscapeMode;
|
||||||
|
import org.jsoup.safety.Cleaner;
|
||||||
|
import org.jsoup.safety.Safelist;
|
||||||
|
import org.w3c.css.sac.CSSException;
|
||||||
|
import org.w3c.css.sac.CSSParseException;
|
||||||
|
import org.w3c.css.sac.ErrorHandler;
|
||||||
|
import org.w3c.css.sac.InputSource;
|
||||||
|
import org.w3c.dom.css.CSSStyleDeclaration;
|
||||||
|
|
||||||
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
||||||
import com.commafeed.backend.feed.FeedUtils;
|
import com.commafeed.backend.feed.FeedUtils;
|
||||||
import com.commafeed.backend.model.FeedEntryContent;
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
|
import com.steadystate.css.parser.CSSOMParser;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||||
|
@Slf4j
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FeedEntryContentService {
|
public class FeedEntryContentService {
|
||||||
|
|
||||||
|
private static final Safelist HTML_WHITELIST = buildWhiteList();
|
||||||
|
private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList("height", "width", "border");
|
||||||
|
private static final List<String> ALLOWED_IMG_CSS_RULES = Arrays.asList("display", "width", "height");
|
||||||
|
private static final char[] FORBIDDEN_CSS_RULE_CHARACTERS = new char[] { '(', ')' };
|
||||||
|
|
||||||
private final FeedEntryContentDAO feedEntryContentDAO;
|
private final FeedEntryContentDAO feedEntryContentDAO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is NOT thread-safe
|
* this is NOT thread-safe
|
||||||
*/
|
*/
|
||||||
public FeedEntryContent findOrCreate(FeedEntryContent content, String baseUrl) {
|
public FeedEntryContent findOrCreate(FeedEntryContent content, String baseUrl) {
|
||||||
content.setAuthor(FeedUtils.truncate(FeedUtils.handleContent(content.getAuthor(), baseUrl, true), 128));
|
content.setAuthor(FeedUtils.truncate(handleContent(content.getAuthor(), baseUrl, true), 128));
|
||||||
content.setTitle(FeedUtils.truncate(FeedUtils.handleContent(content.getTitle(), baseUrl, true), 2048));
|
content.setTitle(FeedUtils.truncate(handleContent(content.getTitle(), baseUrl, true), 2048));
|
||||||
content.setContent(FeedUtils.handleContent(content.getContent(), baseUrl, false));
|
content.setContent(handleContent(content.getContent(), baseUrl, false));
|
||||||
content.setMediaDescription(FeedUtils.handleContent(content.getMediaDescription(), baseUrl, false));
|
content.setMediaDescription(handleContent(content.getMediaDescription(), baseUrl, false));
|
||||||
|
|
||||||
String contentHash = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.getContent()));
|
String contentHash = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.getContent()));
|
||||||
content.setContentHash(contentHash);
|
content.setContentHash(contentHash);
|
||||||
@@ -37,7 +60,7 @@ public class FeedEntryContentService {
|
|||||||
content.setTitleHash(titleHash);
|
content.setTitleHash(titleHash);
|
||||||
|
|
||||||
List<FeedEntryContent> existing = feedEntryContentDAO.findExisting(contentHash, titleHash);
|
List<FeedEntryContent> existing = feedEntryContentDAO.findExisting(contentHash, titleHash);
|
||||||
Optional<FeedEntryContent> equivalentContent = existing.stream().filter(c -> content.equivalentTo(c)).findFirst();
|
Optional<FeedEntryContent> equivalentContent = existing.stream().filter(content::equivalentTo).findFirst();
|
||||||
if (equivalentContent.isPresent()) {
|
if (equivalentContent.isPresent()) {
|
||||||
return equivalentContent.get();
|
return equivalentContent.get();
|
||||||
}
|
}
|
||||||
@@ -45,4 +68,140 @@ public class FeedEntryContentService {
|
|||||||
feedEntryContentDAO.saveOrUpdate(content);
|
feedEntryContentDAO.saveOrUpdate(content);
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Safelist buildWhiteList() {
|
||||||
|
Safelist whitelist = new Safelist();
|
||||||
|
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1",
|
||||||
|
"h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong", "sub", "sup",
|
||||||
|
"table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
|
||||||
|
|
||||||
|
whitelist.addAttributes("div", "dir");
|
||||||
|
whitelist.addAttributes("pre", "dir");
|
||||||
|
whitelist.addAttributes("code", "dir");
|
||||||
|
whitelist.addAttributes("table", "dir");
|
||||||
|
whitelist.addAttributes("p", "dir");
|
||||||
|
whitelist.addAttributes("a", "href", "title");
|
||||||
|
whitelist.addAttributes("blockquote", "cite");
|
||||||
|
whitelist.addAttributes("col", "span", "width");
|
||||||
|
whitelist.addAttributes("colgroup", "span", "width");
|
||||||
|
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
|
||||||
|
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width", "style");
|
||||||
|
whitelist.addAttributes("ol", "start", "type");
|
||||||
|
whitelist.addAttributes("q", "cite");
|
||||||
|
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
|
||||||
|
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
|
||||||
|
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
|
||||||
|
whitelist.addAttributes("ul", "type");
|
||||||
|
|
||||||
|
whitelist.addProtocols("a", "href", "ftp", "http", "https", "magnet", "mailto");
|
||||||
|
whitelist.addProtocols("blockquote", "cite", "http", "https");
|
||||||
|
whitelist.addProtocols("img", "src", "http", "https");
|
||||||
|
whitelist.addProtocols("q", "cite", "http", "https");
|
||||||
|
|
||||||
|
whitelist.addEnforcedAttribute("a", "target", "_blank");
|
||||||
|
whitelist.addEnforcedAttribute("a", "rel", "noreferrer");
|
||||||
|
return whitelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String handleContent(String content, String baseUri, boolean keepTextOnly) {
|
||||||
|
if (StringUtils.isNotBlank(content)) {
|
||||||
|
baseUri = StringUtils.trimToEmpty(baseUri);
|
||||||
|
|
||||||
|
Document dirty = Jsoup.parseBodyFragment(content, baseUri);
|
||||||
|
Cleaner cleaner = new Cleaner(HTML_WHITELIST);
|
||||||
|
Document clean = cleaner.clean(dirty);
|
||||||
|
|
||||||
|
for (Element e : clean.select("iframe[style]")) {
|
||||||
|
String style = e.attr("style");
|
||||||
|
String escaped = escapeIFrameCss(style);
|
||||||
|
e.attr("style", escaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Element e : clean.select("img[style]")) {
|
||||||
|
String style = e.attr("style");
|
||||||
|
String escaped = escapeImgCss(style);
|
||||||
|
e.attr("style", escaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
clean.outputSettings(new OutputSettings().escapeMode(EscapeMode.base).prettyPrint(false));
|
||||||
|
Element body = clean.body();
|
||||||
|
if (keepTextOnly) {
|
||||||
|
content = body.text();
|
||||||
|
} else {
|
||||||
|
content = body.html();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeIFrameCss(String orig) {
|
||||||
|
String rule = "";
|
||||||
|
try {
|
||||||
|
List<String> rules = new ArrayList<>();
|
||||||
|
CSSStyleDeclaration decl = buildCssParser().parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
||||||
|
|
||||||
|
for (int i = 0; i < decl.getLength(); i++) {
|
||||||
|
String property = decl.item(i);
|
||||||
|
String value = decl.getPropertyValue(property);
|
||||||
|
if (StringUtils.isBlank(property) || StringUtils.isBlank(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ALLOWED_IFRAME_CSS_RULES.contains(property) && StringUtils.containsNone(value, FORBIDDEN_CSS_RULE_CHARACTERS)) {
|
||||||
|
rules.add(property + ":" + decl.getPropertyValue(property) + ";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule = StringUtils.join(rules, "");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeImgCss(String orig) {
|
||||||
|
String rule = "";
|
||||||
|
try {
|
||||||
|
List<String> rules = new ArrayList<>();
|
||||||
|
CSSStyleDeclaration decl = buildCssParser().parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
||||||
|
|
||||||
|
for (int i = 0; i < decl.getLength(); i++) {
|
||||||
|
String property = decl.item(i);
|
||||||
|
String value = decl.getPropertyValue(property);
|
||||||
|
if (StringUtils.isBlank(property) || StringUtils.isBlank(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ALLOWED_IMG_CSS_RULES.contains(property) && StringUtils.containsNone(value, FORBIDDEN_CSS_RULE_CHARACTERS)) {
|
||||||
|
rules.add(property + ":" + decl.getPropertyValue(property) + ";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule = StringUtils.join(rules, "");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CSSOMParser buildCssParser() {
|
||||||
|
CSSOMParser parser = new CSSOMParser();
|
||||||
|
|
||||||
|
parser.setErrorHandler(new ErrorHandler() {
|
||||||
|
@Override
|
||||||
|
public void warning(CSSParseException exception) throws CSSException {
|
||||||
|
log.debug("warning while parsing css: {}", exception.getMessage(), exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(CSSParseException exception) throws CSSException {
|
||||||
|
log.debug("error while parsing css: {}", exception.getMessage(), exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fatalError(CSSParseException exception) throws CSSException {
|
||||||
|
log.debug("fatal error while parsing css: {}", exception.getMessage(), exception);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class FeedEntryService {
|
|||||||
|
|
||||||
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan, List<FeedEntryKeyword> keywords) {
|
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan, List<FeedEntryKeyword> keywords) {
|
||||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, keywords, null, -1, -1, null,
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, keywords, null, -1, -1, null,
|
||||||
false, false, null);
|
false, false, null, null, null);
|
||||||
markList(statuses, olderThan);
|
markList(statuses, olderThan);
|
||||||
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||||
cache.invalidateUserRootCategory(user);
|
cache.invalidateUserRootCategory(user);
|
||||||
|
|||||||
@@ -82,6 +82,28 @@ public class UserService {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* try to log in with given fever api key
|
||||||
|
*/
|
||||||
|
public Optional<User> login(long userId, String feverApiKey) {
|
||||||
|
if (feverApiKey == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userDAO.findById(userId);
|
||||||
|
if (user == null || user.isDisabled() || user.getApiKey() == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String computedFeverApiKey = DigestUtils.md5Hex(user.getName() + ":" + user.getApiKey());
|
||||||
|
if (!computedFeverApiKey.equals(feverApiKey)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
performPostLoginActivities(user);
|
||||||
|
return Optional.of(user);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* should triggers after successful login
|
* should triggers after successful login
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class Entries implements Serializable {
|
|||||||
@ApiModelProperty(value = "times the server tried to refresh the feed and failed", required = true)
|
@ApiModelProperty(value = "times the server tried to refresh the feed and failed", required = true)
|
||||||
private int errorCount;
|
private int errorCount;
|
||||||
|
|
||||||
@ApiModelProperty(value = "URL of the website, extracted from the feed", required = true)
|
@ApiModelProperty(value = "URL of the website, extracted from the feed, only filled if querying for feed entries, not category entries")
|
||||||
private String feedLink;
|
private String feedLink;
|
||||||
|
|
||||||
@ApiModelProperty(value = "list generation timestamp", required = true)
|
@ApiModelProperty(value = "list generation timestamp", required = true)
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ public class Settings implements Serializable {
|
|||||||
@ApiModelProperty(value = "always scroll selected entry to the top of the page, even if it fits entirely on screen", required = true)
|
@ApiModelProperty(value = "always scroll selected entry to the top of the page, even if it fits entirely on screen", required = true)
|
||||||
private boolean alwaysScrollToEntry;
|
private boolean alwaysScrollToEntry;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "ask for confirmation when marking all entries as read", required = true)
|
||||||
|
private boolean markAllAsReadConfirmation;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "show commafeed's own context menu on right click", required = true)
|
||||||
|
private boolean customContextMenu;
|
||||||
|
|
||||||
@ApiModelProperty(value = "sharing settings", required = true)
|
@ApiModelProperty(value = "sharing settings", required = true)
|
||||||
private SharingSettings sharingSettings = new SharingSettings();
|
private SharingSettings sharingSettings = new SharingSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -23,19 +23,16 @@ public class Subscription implements Serializable {
|
|||||||
@ApiModelProperty(value = "subscription name", required = true)
|
@ApiModelProperty(value = "subscription name", required = true)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ApiModelProperty(value = "error message while fetching the feed", required = true)
|
@ApiModelProperty(value = "error message while fetching the feed")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
@ApiModelProperty(value = "error count", required = true)
|
@ApiModelProperty(value = "error count", required = true)
|
||||||
private int errorCount;
|
private int errorCount;
|
||||||
|
|
||||||
@ApiModelProperty(value = "last time the feed was refreshed", dataType = "number", required = true)
|
@ApiModelProperty(value = "last time the feed was refreshed", dataType = "number")
|
||||||
private Date lastRefresh;
|
private Date lastRefresh;
|
||||||
|
|
||||||
@ApiModelProperty(
|
@ApiModelProperty(value = "next time the feed refresh is planned, null if refresh is already queued", dataType = "number")
|
||||||
value = "next time the feed refresh is planned, null if refresh is already queued",
|
|
||||||
dataType = "number",
|
|
||||||
required = true)
|
|
||||||
private Date nextRefresh;
|
private Date nextRefresh;
|
||||||
|
|
||||||
@ApiModelProperty(value = "this subscription's feed url", required = true)
|
@ApiModelProperty(value = "this subscription's feed url", required = true)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class UserModel implements Serializable {
|
|||||||
@ApiModelProperty(value = "account status", required = true)
|
@ApiModelProperty(value = "account status", required = true)
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
@ApiModelProperty(value = "account creation date", dataType = "number", required = true)
|
@ApiModelProperty(value = "account creation date", dataType = "number")
|
||||||
private Date created;
|
private Date created;
|
||||||
|
|
||||||
@ApiModelProperty(value = "last login date", dataType = "number")
|
@ApiModelProperty(value = "last login date", dataType = "number")
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public class CategoryREST {
|
|||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||||
removeExcludedSubscriptions(subs, excludedIds);
|
removeExcludedSubscriptions(subs, excludedIds);
|
||||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
||||||
offset, limit + 1, order, true, onlyIds, tag);
|
offset, limit + 1, order, true, onlyIds, tag, null, null);
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
||||||
@@ -160,7 +160,7 @@ public class CategoryREST {
|
|||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
|
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
|
||||||
removeExcludedSubscriptions(subs, excludedIds);
|
removeExcludedSubscriptions(subs, excludedIds);
|
||||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
||||||
offset, limit + 1, order, true, onlyIds, tag);
|
offset, limit + 1, order, true, onlyIds, tag, null, null);
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ public class FeedREST {
|
|||||||
entries.setFeedLink(subscription.getFeed().getLink());
|
entries.setFeedLink(subscription.getFeed().getLink());
|
||||||
|
|
||||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, Collections.singletonList(subscription), unreadOnly,
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, Collections.singletonList(subscription), unreadOnly,
|
||||||
entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null);
|
entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null, null, null);
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
||||||
@@ -379,7 +379,7 @@ public class FeedREST {
|
|||||||
@POST
|
@POST
|
||||||
@Path("/subscribe")
|
@Path("/subscribe")
|
||||||
@UnitOfWork
|
@UnitOfWork
|
||||||
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed", response = Long.class)
|
||||||
@Timed
|
@Timed
|
||||||
public Response subscribe(@ApiParam(hidden = true) @SecurityCheck User user,
|
public Response subscribe(@ApiParam(hidden = true) @SecurityCheck User user,
|
||||||
@Valid @ApiParam(value = "subscription request", required = true) SubscribeRequest req) {
|
@Valid @ApiParam(value = "subscription request", required = true) SubscribeRequest req) {
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ public class UserREST {
|
|||||||
s.setLanguage(settings.getLanguage());
|
s.setLanguage(settings.getLanguage());
|
||||||
s.setScrollSpeed(settings.getScrollSpeed());
|
s.setScrollSpeed(settings.getScrollSpeed());
|
||||||
s.setAlwaysScrollToEntry(settings.isAlwaysScrollToEntry());
|
s.setAlwaysScrollToEntry(settings.isAlwaysScrollToEntry());
|
||||||
|
s.setMarkAllAsReadConfirmation(settings.isMarkAllAsReadConfirmation());
|
||||||
|
s.setCustomContextMenu(settings.isCustomContextMenu());
|
||||||
} else {
|
} else {
|
||||||
s.setReadingMode(ReadingMode.unread.name());
|
s.setReadingMode(ReadingMode.unread.name());
|
||||||
s.setReadingOrder(ReadingOrder.desc.name());
|
s.setReadingOrder(ReadingOrder.desc.name());
|
||||||
@@ -122,6 +124,8 @@ public class UserREST {
|
|||||||
s.setLanguage("en");
|
s.setLanguage("en");
|
||||||
s.setScrollSpeed(400);
|
s.setScrollSpeed(400);
|
||||||
s.setAlwaysScrollToEntry(false);
|
s.setAlwaysScrollToEntry(false);
|
||||||
|
s.setMarkAllAsReadConfirmation(true);
|
||||||
|
s.setCustomContextMenu(true);
|
||||||
}
|
}
|
||||||
return Response.ok(s).build();
|
return Response.ok(s).build();
|
||||||
}
|
}
|
||||||
@@ -148,6 +152,8 @@ public class UserREST {
|
|||||||
s.setLanguage(settings.getLanguage());
|
s.setLanguage(settings.getLanguage());
|
||||||
s.setScrollSpeed(settings.getScrollSpeed());
|
s.setScrollSpeed(settings.getScrollSpeed());
|
||||||
s.setAlwaysScrollToEntry(settings.isAlwaysScrollToEntry());
|
s.setAlwaysScrollToEntry(settings.isAlwaysScrollToEntry());
|
||||||
|
s.setMarkAllAsReadConfirmation(settings.isMarkAllAsReadConfirmation());
|
||||||
|
s.setCustomContextMenu(settings.isCustomContextMenu());
|
||||||
|
|
||||||
s.setEmail(settings.getSharingSettings().isEmail());
|
s.setEmail(settings.getSharingSettings().isEmail());
|
||||||
s.setGmail(settings.getSharingSettings().isGmail());
|
s.setGmail(settings.getSharingSettings().isGmail());
|
||||||
|
|||||||
@@ -0,0 +1,320 @@
|
|||||||
|
package com.commafeed.frontend.resource.fever;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
|
||||||
|
|
||||||
|
import com.codahale.metrics.annotation.Timed;
|
||||||
|
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
|
import com.commafeed.backend.favicon.AbstractFaviconFetcher.Favicon;
|
||||||
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.FeedCategory;
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.commafeed.backend.model.User;
|
||||||
|
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||||
|
import com.commafeed.backend.service.FeedEntryService;
|
||||||
|
import com.commafeed.backend.service.FeedService;
|
||||||
|
import com.commafeed.backend.service.UserService;
|
||||||
|
import com.commafeed.frontend.resource.fever.FeverResponse.FeverFavicon;
|
||||||
|
import com.commafeed.frontend.resource.fever.FeverResponse.FeverFeed;
|
||||||
|
import com.commafeed.frontend.resource.fever.FeverResponse.FeverFeedGroup;
|
||||||
|
import com.commafeed.frontend.resource.fever.FeverResponse.FeverGroup;
|
||||||
|
import com.commafeed.frontend.resource.fever.FeverResponse.FeverItem;
|
||||||
|
|
||||||
|
import io.dropwizard.hibernate.UnitOfWork;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fever-compatible API
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>url: /rest/fever/user/${userId}</li>
|
||||||
|
* <li>login: username</li>
|
||||||
|
* <li>password: api key</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* See https://feedafever.com/api
|
||||||
|
*/
|
||||||
|
@Path("/fever")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||||
|
@Singleton
|
||||||
|
public class FeverREST {
|
||||||
|
|
||||||
|
private static final String PATH = "/user/{userId}{optionalTrailingFever : (/fever)?}{optionalTrailingSlash : (/)?}";
|
||||||
|
private static final int UNREAD_ITEM_IDS_BATCH_SIZE = 1000;
|
||||||
|
private static final int SAVED_ITEM_IDS_BATCH_SIZE = 1000;
|
||||||
|
private static final int ITEMS_BATCH_SIZE = 200;
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final FeedEntryService feedEntryService;
|
||||||
|
private final FeedService feedService;
|
||||||
|
private final FeedEntryDAO feedEntryDAO;
|
||||||
|
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
|
private final FeedCategoryDAO feedCategoryDAO;
|
||||||
|
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
||||||
|
|
||||||
|
// expected Fever API
|
||||||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
@Path(PATH)
|
||||||
|
@POST
|
||||||
|
@UnitOfWork
|
||||||
|
@Timed
|
||||||
|
public FeverResponse formUrlencoded(@Context UriInfo uri, @PathParam("userId") Long userId, MultivaluedMap<String, String> form) {
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
uri.getQueryParameters().forEach((k, v) -> params.put(k, v.get(0)));
|
||||||
|
form.forEach((k, v) -> params.put(k, v.get(0)));
|
||||||
|
return handle(userId, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround for some readers that post data without any media type, and all params in the url
|
||||||
|
// e.g. FeedMe
|
||||||
|
@Path(PATH)
|
||||||
|
@POST
|
||||||
|
@UnitOfWork
|
||||||
|
@Timed
|
||||||
|
public FeverResponse noForm(@Context UriInfo uri, @PathParam("userId") Long userId) {
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
uri.getQueryParameters().forEach((k, v) -> params.put(k, v.get(0)));
|
||||||
|
return handle(userId, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround for some readers that post data using MultiPart FormData instead of the classic POST
|
||||||
|
// e.g. Raven Reader
|
||||||
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
|
@Path(PATH)
|
||||||
|
@POST
|
||||||
|
@UnitOfWork
|
||||||
|
@Timed
|
||||||
|
public FeverResponse formData(@Context UriInfo uri, @PathParam("userId") Long userId, FormDataMultiPart form) {
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
uri.getQueryParameters().forEach((k, v) -> params.put(k, v.get(0)));
|
||||||
|
form.getFields().forEach((k, v) -> params.put(k, v.get(0).getValue()));
|
||||||
|
return handle(userId, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeverResponse handle(long userId, Map<String, String> params) {
|
||||||
|
User user = auth(userId, params.get("api_key")).orElse(null);
|
||||||
|
if (user == null) {
|
||||||
|
FeverResponse resp = new FeverResponse();
|
||||||
|
resp.setAuth(false);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
FeverResponse resp = new FeverResponse();
|
||||||
|
resp.setAuth(true);
|
||||||
|
|
||||||
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||||
|
resp.setLastRefreshedOnTime(buildLastRefreshedOnTime(subscriptions));
|
||||||
|
|
||||||
|
if (params.containsKey("groups") || params.containsKey("feeds")) {
|
||||||
|
resp.setFeedsGroups(buildFeedsGroups(subscriptions));
|
||||||
|
|
||||||
|
if (params.containsKey("groups")) {
|
||||||
|
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||||
|
resp.setGroups(buildGroups(categories));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("feeds")) {
|
||||||
|
resp.setFeeds(buildFeeds(subscriptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("unread_item_ids")) {
|
||||||
|
resp.setUnreadItemIds(buildUnreadItemIds(user, subscriptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("saved_item_ids")) {
|
||||||
|
resp.setSavedItemIds(buildSavedItemIds(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("items")) {
|
||||||
|
if (params.containsKey("with_ids")) {
|
||||||
|
String withIds = params.get("with_ids");
|
||||||
|
List<String> entryIds = Stream.of(withIds.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
resp.setItems(buildItems(user, subscriptions, entryIds));
|
||||||
|
} else {
|
||||||
|
Long sinceId = params.containsKey("since_id") ? Long.valueOf(params.get("since_id")) : null;
|
||||||
|
Long maxId = params.containsKey("max_id") ? Long.valueOf(params.get("max_id")) : null;
|
||||||
|
resp.setItems(buildItems(user, subscriptions, sinceId, maxId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("favicons")) {
|
||||||
|
resp.setFavicons(buildFavicons(subscriptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("links")) {
|
||||||
|
resp.setLinks(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.containsKey("mark") && params.containsKey("id") && params.containsKey("as")) {
|
||||||
|
long id = Long.parseLong(params.get("id"));
|
||||||
|
String before = params.get("before");
|
||||||
|
Date olderThan = before == null ? null : Date.from(Instant.ofEpochSecond(Long.parseLong(before)));
|
||||||
|
mark(user, params.get("mark"), id, params.get("as"), olderThan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<User> auth(Long userId, String feverApiKey) {
|
||||||
|
return userService.login(userId, feverApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long buildLastRefreshedOnTime(List<FeedSubscription> subscriptions) {
|
||||||
|
return subscriptions.stream()
|
||||||
|
.map(FeedSubscription::getFeed)
|
||||||
|
.map(Feed::getLastUpdated)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.max(Comparator.naturalOrder())
|
||||||
|
.map(d -> d.toInstant().getEpochSecond())
|
||||||
|
.orElse(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeverFeedGroup> buildFeedsGroups(List<FeedSubscription> subscriptions) {
|
||||||
|
return subscriptions.stream()
|
||||||
|
.collect(Collectors.groupingBy(s -> s.getCategory() == null ? 0 : s.getCategory().getId()))
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.map(e -> {
|
||||||
|
FeverFeedGroup fg = new FeverFeedGroup();
|
||||||
|
fg.setGroupId(e.getKey());
|
||||||
|
fg.setFeedIds(e.getValue().stream().map(FeedSubscription::getId).collect(Collectors.toList()));
|
||||||
|
return fg;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeverGroup> buildGroups(List<FeedCategory> categories) {
|
||||||
|
return categories.stream().map(c -> {
|
||||||
|
FeverGroup g = new FeverGroup();
|
||||||
|
g.setId(c.getId());
|
||||||
|
g.setTitle(c.getName());
|
||||||
|
return g;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeverFeed> buildFeeds(List<FeedSubscription> subscriptions) {
|
||||||
|
return subscriptions.stream().map(s -> {
|
||||||
|
FeverFeed f = new FeverFeed();
|
||||||
|
f.setId(s.getId());
|
||||||
|
f.setFaviconId(s.getId());
|
||||||
|
f.setTitle(s.getTitle());
|
||||||
|
f.setUrl(s.getFeed().getUrl());
|
||||||
|
f.setSiteUrl(s.getFeed().getLink());
|
||||||
|
f.setSpark(false);
|
||||||
|
f.setLastUpdatedOnTime(s.getFeed().getLastUpdated() == null ? 0 : s.getFeed().getLastUpdated().toInstant().getEpochSecond());
|
||||||
|
return f;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Long> buildUnreadItemIds(User user, List<FeedSubscription> subscriptions) {
|
||||||
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, 0,
|
||||||
|
UNREAD_ITEM_IDS_BATCH_SIZE, ReadingOrder.desc, false, true, null, null, null);
|
||||||
|
return statuses.stream().map(s -> s.getEntry().getId()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Long> buildSavedItemIds(User user) {
|
||||||
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findStarred(user, null, 0, SAVED_ITEM_IDS_BATCH_SIZE, ReadingOrder.desc, false);
|
||||||
|
return statuses.stream().map(s -> s.getEntry().getId()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeverItem> buildItems(User user, List<FeedSubscription> subscriptions, List<String> entryIds) {
|
||||||
|
List<FeverItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<Long, FeedSubscription> subscriptionsByFeedId = subscriptions.stream()
|
||||||
|
.collect(Collectors.toMap(s -> s.getFeed().getId(), s -> s));
|
||||||
|
for (String entryId : entryIds) {
|
||||||
|
FeedEntry entry = feedEntryDAO.findById(Long.parseLong(entryId));
|
||||||
|
FeedSubscription sub = subscriptionsByFeedId.get(entry.getFeed().getId());
|
||||||
|
if (sub != null) {
|
||||||
|
FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry);
|
||||||
|
items.add(mapStatus(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeverItem> buildItems(User user, List<FeedSubscription> subscriptions, Long sinceId, Long maxId) {
|
||||||
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, false, null, null, 0, ITEMS_BATCH_SIZE,
|
||||||
|
ReadingOrder.desc, false, false, null, sinceId, maxId);
|
||||||
|
return statuses.stream().map(this::mapStatus).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private FeverItem mapStatus(FeedEntryStatus s) {
|
||||||
|
FeverItem i = new FeverItem();
|
||||||
|
i.setId(s.getEntry().getId());
|
||||||
|
i.setFeedId(s.getSubscription().getId());
|
||||||
|
i.setTitle(s.getEntry().getContent().getTitle());
|
||||||
|
i.setAuthor(s.getEntry().getContent().getAuthor());
|
||||||
|
i.setHtml(Optional.ofNullable(s.getEntry().getContent().getContent()).orElse(""));
|
||||||
|
i.setUrl(s.getEntry().getUrl());
|
||||||
|
i.setSaved(s.isStarred());
|
||||||
|
i.setRead(s.isRead());
|
||||||
|
i.setCreatedOnTime(s.getEntryUpdated().toInstant().getEpochSecond());
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeverFavicon> buildFavicons(List<FeedSubscription> subscriptions) {
|
||||||
|
return subscriptions.stream().map(s -> {
|
||||||
|
Favicon favicon = feedService.fetchFavicon(s.getFeed());
|
||||||
|
|
||||||
|
FeverFavicon f = new FeverFavicon();
|
||||||
|
f.setId(s.getFeed().getId());
|
||||||
|
f.setData(String.format("data:%s;base64,%s", favicon.getMediaType(), Base64.getEncoder().encodeToString(favicon.getIcon())));
|
||||||
|
return f;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mark(User user, String source, long id, String action, Date olderThan) {
|
||||||
|
if ("item".equals(source)) {
|
||||||
|
if ("read".equals(action) || "unread".equals(action)) {
|
||||||
|
feedEntryService.markEntry(user, id, "read".equals(action));
|
||||||
|
} else if ("saved".equals(action) || "unsaved".equals(action)) {
|
||||||
|
FeedEntry entry = feedEntryDAO.findById(id);
|
||||||
|
FeedSubscription sub = feedSubscriptionDAO.findByFeed(user, entry.getFeed());
|
||||||
|
feedEntryService.starEntry(user, id, sub.getId(), "saved".equals(action));
|
||||||
|
}
|
||||||
|
} else if ("feed".equals(source)) {
|
||||||
|
FeedSubscription subscription = feedSubscriptionDAO.findById(user, id);
|
||||||
|
feedEntryService.markSubscriptionEntries(user, Collections.singletonList(subscription), olderThan, null);
|
||||||
|
} else if ("group".equals(source)) {
|
||||||
|
FeedCategory parent = feedCategoryDAO.findById(user, id);
|
||||||
|
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, parent);
|
||||||
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user, categories);
|
||||||
|
feedEntryService.markSubscriptionEntries(user, subscriptions, olderThan, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package com.commafeed.frontend.resource.fever;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@JsonInclude(Include.NON_NULL)
|
||||||
|
@Data
|
||||||
|
public class FeverResponse {
|
||||||
|
|
||||||
|
@JsonProperty("api_version")
|
||||||
|
private int apiVersion = 3;
|
||||||
|
|
||||||
|
@JsonProperty("auth")
|
||||||
|
@JsonFormat(shape = Shape.NUMBER)
|
||||||
|
private boolean auth;
|
||||||
|
|
||||||
|
@JsonProperty("last_refreshed_on_time")
|
||||||
|
private long lastRefreshedOnTime;
|
||||||
|
|
||||||
|
@JsonProperty("groups")
|
||||||
|
private List<FeverGroup> groups;
|
||||||
|
|
||||||
|
@JsonProperty("feeds")
|
||||||
|
private List<FeverFeed> feeds;
|
||||||
|
|
||||||
|
@JsonProperty("feeds_groups")
|
||||||
|
private List<FeverFeedGroup> feedsGroups;
|
||||||
|
|
||||||
|
@JsonProperty("unread_item_ids")
|
||||||
|
@JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class)
|
||||||
|
private List<Long> unreadItemIds;
|
||||||
|
|
||||||
|
@JsonProperty("saved_item_ids")
|
||||||
|
@JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class)
|
||||||
|
private List<Long> savedItemIds;
|
||||||
|
|
||||||
|
@JsonProperty("items")
|
||||||
|
private List<FeverItem> items;
|
||||||
|
|
||||||
|
@JsonProperty("favicons")
|
||||||
|
private List<FeverFavicon> favicons;
|
||||||
|
|
||||||
|
@JsonProperty("links")
|
||||||
|
private List<FeverLink> links;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FeverGroup {
|
||||||
|
|
||||||
|
@JsonProperty("id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FeverFeed {
|
||||||
|
|
||||||
|
@JsonProperty("id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@JsonProperty("favicon_id")
|
||||||
|
private long faviconId;
|
||||||
|
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@JsonProperty("url")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@JsonProperty("site_url")
|
||||||
|
private String siteUrl;
|
||||||
|
|
||||||
|
@JsonProperty("is_spark")
|
||||||
|
@JsonFormat(shape = Shape.NUMBER)
|
||||||
|
private boolean spark;
|
||||||
|
|
||||||
|
@JsonProperty("last_updated_on_time")
|
||||||
|
private long lastUpdatedOnTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FeverFeedGroup {
|
||||||
|
|
||||||
|
@JsonProperty("group_id")
|
||||||
|
private long groupId;
|
||||||
|
|
||||||
|
@JsonProperty("feed_ids")
|
||||||
|
@JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class)
|
||||||
|
private List<Long> feedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FeverItem {
|
||||||
|
|
||||||
|
@JsonProperty("id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@JsonProperty("feed_id")
|
||||||
|
private long feedId;
|
||||||
|
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@JsonProperty("author")
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
@JsonProperty("html")
|
||||||
|
private String html;
|
||||||
|
|
||||||
|
@JsonProperty("url")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@JsonProperty("is_saved")
|
||||||
|
@JsonFormat(shape = Shape.NUMBER)
|
||||||
|
private boolean saved;
|
||||||
|
|
||||||
|
@JsonProperty("is_read")
|
||||||
|
@JsonFormat(shape = Shape.NUMBER)
|
||||||
|
private boolean read;
|
||||||
|
|
||||||
|
@JsonProperty("created_on_time")
|
||||||
|
private long createdOnTime;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FeverFavicon {
|
||||||
|
|
||||||
|
@JsonProperty("id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@JsonProperty("data")
|
||||||
|
private String data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FeverLink {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LongListToCommaSeparatedStringSerializer extends JsonSerializer<List<Long>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(List<Long> input, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||||
|
String output = input.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||||
|
jsonGenerator.writeObject(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ public class NextUnreadServlet extends HttpServlet {
|
|||||||
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user.get());
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user.get());
|
||||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1, order,
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1, order,
|
||||||
true, false, null);
|
true, false, null, null, null);
|
||||||
s = Iterables.getFirst(statuses, null);
|
s = Iterables.getFirst(statuses, null);
|
||||||
} else {
|
} else {
|
||||||
FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId));
|
FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId));
|
||||||
@@ -77,7 +77,7 @@ public class NextUnreadServlet extends HttpServlet {
|
|||||||
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user.get(), category);
|
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user.get(), category);
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children);
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children);
|
||||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null, null, 0,
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null, null, 0,
|
||||||
1, order, true, false, null);
|
1, order, true, false, null, null, null);
|
||||||
s = Iterables.getFirst(statuses, null);
|
s = Iterables.getFirst(statuses, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
package com.commafeed.frontend.session;
|
package com.commafeed.frontend.session;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.servlet.SessionTrackingMode;
|
import javax.servlet.SessionTrackingMode;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.session.DatabaseAdaptor;
|
||||||
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
||||||
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
import org.eclipse.jetty.server.session.JDBCSessionDataStore;
|
||||||
import org.eclipse.jetty.server.session.SessionCache;
|
import org.eclipse.jetty.server.session.SessionCache;
|
||||||
import org.eclipse.jetty.server.session.SessionHandler;
|
import org.eclipse.jetty.server.session.SessionHandler;
|
||||||
|
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
|
import io.dropwizard.db.DataSourceFactory;
|
||||||
import io.dropwizard.util.Duration;
|
import io.dropwizard.util.Duration;
|
||||||
|
|
||||||
public class SessionHandlerFactory {
|
public class SessionHandlerFactory {
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String path = "sessions";
|
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Duration cookieMaxAge = Duration.days(30);
|
private Duration cookieMaxAge = Duration.days(30);
|
||||||
|
|
||||||
@@ -31,26 +29,24 @@ public class SessionHandlerFactory {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Duration savePeriod = Duration.minutes(5);
|
private Duration savePeriod = Duration.minutes(5);
|
||||||
|
|
||||||
public SessionHandler build() {
|
public SessionHandler build(DataSourceFactory dataSourceFactory) {
|
||||||
SessionHandler sessionHandler = new SessionHandler() {
|
SessionHandler sessionHandler = new SessionHandler();
|
||||||
{
|
|
||||||
// no setter available for maxCookieAge
|
|
||||||
_maxCookieAge = (int) cookieMaxAge.toSeconds();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
|
|
||||||
sessionHandler.setSessionCache(sessionCache);
|
|
||||||
FileSessionDataStore dataStore = new FileSessionDataStore();
|
|
||||||
sessionCache.setSessionDataStore(dataStore);
|
|
||||||
|
|
||||||
sessionHandler.setHttpOnly(true);
|
sessionHandler.setHttpOnly(true);
|
||||||
sessionHandler.setSessionTrackingModes(ImmutableSet.of(SessionTrackingMode.COOKIE));
|
sessionHandler.setSessionTrackingModes(ImmutableSet.of(SessionTrackingMode.COOKIE));
|
||||||
sessionHandler.setMaxInactiveInterval((int) maxInactiveInterval.toSeconds());
|
sessionHandler.setMaxInactiveInterval((int) maxInactiveInterval.toSeconds());
|
||||||
sessionHandler.setRefreshCookieAge((int) cookieRefreshAge.toSeconds());
|
sessionHandler.setRefreshCookieAge((int) cookieRefreshAge.toSeconds());
|
||||||
|
sessionHandler.getSessionCookieConfig().setMaxAge((int) cookieMaxAge.toSeconds());
|
||||||
|
|
||||||
dataStore.setDeleteUnrestorableFiles(true);
|
SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
|
||||||
dataStore.setStoreDir(new File(path));
|
sessionHandler.setSessionCache(sessionCache);
|
||||||
|
|
||||||
|
JDBCSessionDataStore dataStore = new JDBCSessionDataStore();
|
||||||
dataStore.setSavePeriodSec((int) savePeriod.toSeconds());
|
dataStore.setSavePeriodSec((int) savePeriod.toSeconds());
|
||||||
|
sessionCache.setSessionDataStore(dataStore);
|
||||||
|
|
||||||
|
DatabaseAdaptor adaptor = new DatabaseAdaptor();
|
||||||
|
adaptor.setDatasource(dataSourceFactory.build(new MetricRegistry(), "sessions"));
|
||||||
|
dataStore.setDatabaseAdaptor(adaptor);
|
||||||
|
|
||||||
return sessionHandler;
|
return sessionHandler;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?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="mark-all-as-read-confirmation" author="athou">
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="markAllAsReadConfirmation" type="BOOLEAN" defaultValueBoolean="true">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="custom-context-menu" author="athou">
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="customContextMenu" type="BOOLEAN" defaultValueBoolean="true">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -21,5 +21,6 @@
|
|||||||
<include file="changelogs/db.changelog-3.5.xml" />
|
<include file="changelogs/db.changelog-3.5.xml" />
|
||||||
<include file="changelogs/db.changelog-3.6.xml" />
|
<include file="changelogs/db.changelog-3.6.xml" />
|
||||||
<include file="changelogs/db.changelog-3.8.xml" />
|
<include file="changelogs/db.changelog-3.8.xml" />
|
||||||
|
<include file="changelogs/db.changelog-3.9.xml" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.commafeed;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
|
||||||
|
import io.dropwizard.testing.ResourceHelpers;
|
||||||
|
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
||||||
|
|
||||||
|
public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension<CommaFeedConfiguration> {
|
||||||
|
|
||||||
|
public CommaFeedDropwizardAppExtension() {
|
||||||
|
super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void after() {
|
||||||
|
super.after();
|
||||||
|
|
||||||
|
// clean database after each test
|
||||||
|
DataSource dataSource = getConfiguration().getDataSourceFactory().build(new MetricRegistry(), "cleanup");
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("DROP ALL OBJECTS")) {
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("could not cleanup database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,20 +3,16 @@ package com.commafeed.e2e;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import com.commafeed.CommaFeedApplication;
|
import com.commafeed.CommaFeedDropwizardAppExtension;
|
||||||
import com.commafeed.CommaFeedConfiguration;
|
|
||||||
import com.microsoft.playwright.Locator;
|
import com.microsoft.playwright.Locator;
|
||||||
import com.microsoft.playwright.assertions.PlaywrightAssertions;
|
import com.microsoft.playwright.assertions.PlaywrightAssertions;
|
||||||
|
|
||||||
import io.dropwizard.testing.ResourceHelpers;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||||
|
|
||||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||||
class AuthentificationIT extends PlaywrightTestBase {
|
class AuthentificationIT extends PlaywrightTestBase {
|
||||||
|
|
||||||
private static final DropwizardAppExtension<CommaFeedConfiguration> EXT = new DropwizardAppExtension<>(CommaFeedApplication.class,
|
private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension();
|
||||||
ResourceHelpers.resourceFilePath("config.test.yml"));
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void loginFail() {
|
void loginFail() {
|
||||||
|
|||||||
@@ -12,22 +12,18 @@ import org.mockserver.junit.jupiter.MockServerExtension;
|
|||||||
import org.mockserver.model.HttpRequest;
|
import org.mockserver.model.HttpRequest;
|
||||||
import org.mockserver.model.HttpResponse;
|
import org.mockserver.model.HttpResponse;
|
||||||
|
|
||||||
import com.commafeed.CommaFeedApplication;
|
import com.commafeed.CommaFeedDropwizardAppExtension;
|
||||||
import com.commafeed.CommaFeedConfiguration;
|
|
||||||
import com.microsoft.playwright.Locator;
|
import com.microsoft.playwright.Locator;
|
||||||
import com.microsoft.playwright.Locator.WaitForOptions;
|
import com.microsoft.playwright.Locator.WaitForOptions;
|
||||||
import com.microsoft.playwright.assertions.PlaywrightAssertions;
|
import com.microsoft.playwright.assertions.PlaywrightAssertions;
|
||||||
|
|
||||||
import io.dropwizard.testing.ResourceHelpers;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||||
|
|
||||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||||
@ExtendWith(MockServerExtension.class)
|
@ExtendWith(MockServerExtension.class)
|
||||||
class ReadingIT extends PlaywrightTestBase {
|
class ReadingIT extends PlaywrightTestBase {
|
||||||
|
|
||||||
private static final DropwizardAppExtension<CommaFeedConfiguration> EXT = new DropwizardAppExtension<CommaFeedConfiguration>(
|
private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension();
|
||||||
CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"));
|
|
||||||
|
|
||||||
private MockServerClient mockServerClient;
|
private MockServerClient mockServerClient;
|
||||||
|
|
||||||
|
|||||||
@@ -22,21 +22,17 @@ import org.mockserver.junit.jupiter.MockServerExtension;
|
|||||||
import org.mockserver.model.HttpRequest;
|
import org.mockserver.model.HttpRequest;
|
||||||
import org.mockserver.model.HttpResponse;
|
import org.mockserver.model.HttpResponse;
|
||||||
|
|
||||||
import com.commafeed.CommaFeedApplication;
|
import com.commafeed.CommaFeedDropwizardAppExtension;
|
||||||
import com.commafeed.CommaFeedConfiguration;
|
|
||||||
import com.commafeed.frontend.model.Entries;
|
import com.commafeed.frontend.model.Entries;
|
||||||
import com.commafeed.frontend.model.request.SubscribeRequest;
|
import com.commafeed.frontend.model.request.SubscribeRequest;
|
||||||
|
|
||||||
import io.dropwizard.testing.ResourceHelpers;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||||
|
|
||||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||||
@ExtendWith(MockServerExtension.class)
|
@ExtendWith(MockServerExtension.class)
|
||||||
class FeedIT {
|
class FeedIT {
|
||||||
|
|
||||||
private static final DropwizardAppExtension<CommaFeedConfiguration> EXT = new DropwizardAppExtension<CommaFeedConfiguration>(
|
private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension() {
|
||||||
CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml")) {
|
|
||||||
@Override
|
@Override
|
||||||
protected JerseyClientBuilder clientBuilder() {
|
protected JerseyClientBuilder clientBuilder() {
|
||||||
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "admin");
|
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "admin");
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ app:
|
|||||||
# number of database updating threads
|
# number of database updating threads
|
||||||
databaseUpdateThreads: 1
|
databaseUpdateThreads: 1
|
||||||
|
|
||||||
|
# rows to delete per query while cleaning up old entries
|
||||||
|
databaseCleanupBatchSize: 100
|
||||||
|
|
||||||
# settings for sending emails (password recovery)
|
# settings for sending emails (password recovery)
|
||||||
smtpHost: localhost
|
smtpHost: localhost
|
||||||
smtpPort: 25
|
smtpPort: 25
|
||||||
@@ -81,8 +84,12 @@ app:
|
|||||||
|
|
||||||
# Database connection
|
# Database connection
|
||||||
# -------------------
|
# -------------------
|
||||||
|
# for MariaDB
|
||||||
|
# driverClass is org.mariadb.jdbc.Driver
|
||||||
|
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
||||||
|
#
|
||||||
# for MySQL
|
# for MySQL
|
||||||
# driverClass is com.mysql.jdbc.Driver
|
# driverClass is com.mysql.cj.jdbc.Driver
|
||||||
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
|
||||||
#
|
#
|
||||||
# for PostgreSQL
|
# for PostgreSQL
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.1</version>
|
||||||
<name>CommaFeed</name>
|
<name>CommaFeed</name>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user