mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
refactor
This commit is contained in:
@@ -17,6 +17,7 @@ Google Reader inspired self-hosted RSS reader, based on Quarkus and React/TypeSc
|
|||||||
- REST API
|
- REST API
|
||||||
- Fever-compatible API for native mobile apps
|
- Fever-compatible API for native mobile apps
|
||||||
- Can automatically mark articles as read based on user-defined rules
|
- Can automatically mark articles as read based on user-defined rules
|
||||||
|
- Push notifications when new articles are published
|
||||||
- Highly customizable with [custom CSS](https://athou.github.io/commafeed/documentation/custom-css) and JavaScript
|
- Highly customizable with [custom CSS](https://athou.github.io/commafeed/documentation/custom-css) and JavaScript
|
||||||
- [Browser extension](https://github.com/Athou/commafeed-browser-extension)
|
- [Browser extension](https://github.com/Athou/commafeed-browser-extension)
|
||||||
- Compiles to native code for blazing fast startup and low memory usage
|
- Compiles to native code for blazing fast startup and low memory usage
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const createFeed = (id: number, unread: number): Subscription => ({
|
|||||||
feedUrl: "",
|
feedUrl: "",
|
||||||
feedLink: "",
|
feedLink: "",
|
||||||
iconUrl: "",
|
iconUrl: "",
|
||||||
notifyOnNewEntries: true,
|
pushNotificationsEnabled: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const root = createCategory("root")
|
const root = createCategory("root")
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export interface Subscription {
|
|||||||
newestItemTime?: number
|
newestItemTime?: number
|
||||||
filter?: string
|
filter?: string
|
||||||
filterLegacy?: string
|
filterLegacy?: string
|
||||||
notifyOnNewEntries: boolean
|
pushNotificationsEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
@@ -111,7 +111,7 @@ export interface FeedModificationRequest {
|
|||||||
categoryId?: string
|
categoryId?: string
|
||||||
position?: number
|
position?: number
|
||||||
filter?: string
|
filter?: string
|
||||||
notifyOnNewEntries?: boolean
|
pushNotificationsEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetEntriesRequest {
|
export interface GetEntriesRequest {
|
||||||
@@ -238,6 +238,7 @@ export interface ServerInfo {
|
|||||||
forceRefreshCooldownDuration: number
|
forceRefreshCooldownDuration: number
|
||||||
initialSetupRequired: boolean
|
initialSetupRequired: boolean
|
||||||
minimumPasswordLength: number
|
minimumPasswordLength: number
|
||||||
|
pushNotificationsEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SharingSettings {
|
export interface SharingSettings {
|
||||||
@@ -251,14 +252,13 @@ export interface SharingSettings {
|
|||||||
buffer: boolean
|
buffer: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationService = "disabled" | "ntfy" | "gotify" | "pushover"
|
export type PushNotificationType = "ntfy" | "gotify" | "pushover"
|
||||||
|
|
||||||
export interface NotificationSettings {
|
export interface PushNotificationSettings {
|
||||||
enabled: boolean
|
type?: PushNotificationType
|
||||||
type?: Exclude<NotificationService, "disabled">
|
|
||||||
serverUrl?: string
|
serverUrl?: string
|
||||||
token?: string
|
userId?: string
|
||||||
userKey?: string
|
userSecret?: string
|
||||||
topic?: string
|
topic?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ export interface Settings {
|
|||||||
disablePullToRefresh: boolean
|
disablePullToRefresh: boolean
|
||||||
primaryColor?: string
|
primaryColor?: string
|
||||||
sharingSettings: SharingSettings
|
sharingSettings: SharingSettings
|
||||||
notificationSettings: NotificationSettings
|
pushNotificationSettings: PushNotificationSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocalSettings {
|
export interface LocalSettings {
|
||||||
@@ -304,7 +304,7 @@ export interface SubscribeRequest {
|
|||||||
url: string
|
url: string
|
||||||
title: string
|
title: string
|
||||||
categoryId?: string
|
categoryId?: string
|
||||||
notifyOnNewEntries: boolean
|
pushNotificationsEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TagRequest {
|
export interface TagRequest {
|
||||||
|
|||||||
@@ -151,10 +151,7 @@ export const userSlice = createSlice({
|
|||||||
})
|
})
|
||||||
builder.addCase(changeNotificationSettings.pending, (state, action) => {
|
builder.addCase(changeNotificationSettings.pending, (state, action) => {
|
||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.notificationSettings = {
|
state.settings.pushNotificationSettings = action.meta.arg
|
||||||
...state.settings.notificationSettings,
|
|
||||||
...action.meta.arg,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.addMatcher(
|
builder.addMatcher(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createAppAsyncThunk } from "@/app/async-thunk"
|
import { createAppAsyncThunk } from "@/app/async-thunk"
|
||||||
import { client } from "@/app/client"
|
import { client } from "@/app/client"
|
||||||
import { reloadEntries } from "@/app/entries/thunks"
|
import { reloadEntries } from "@/app/entries/thunks"
|
||||||
import type { IconDisplayMode, NotificationSettings, ReadingMode, ReadingOrder, ScrollMode, SharingSettings } from "@/app/types"
|
import type { IconDisplayMode, PushNotificationSettings, ReadingMode, ReadingOrder, ScrollMode, SharingSettings } from "@/app/types"
|
||||||
|
|
||||||
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
|
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
|
||||||
|
|
||||||
@@ -160,15 +160,12 @@ export const changeSharingSetting = createAppAsyncThunk(
|
|||||||
|
|
||||||
export const changeNotificationSettings = createAppAsyncThunk(
|
export const changeNotificationSettings = createAppAsyncThunk(
|
||||||
"settings/notificationSettings",
|
"settings/notificationSettings",
|
||||||
(notificationUpdate: Partial<NotificationSettings>, thunkApi) => {
|
(pushNotificationSettings: PushNotificationSettings, thunkApi) => {
|
||||||
const { settings } = thunkApi.getState().user
|
const { settings } = thunkApi.getState().user
|
||||||
if (!settings) return
|
if (!settings) return
|
||||||
client.user.saveSettings({
|
client.user.saveSettings({
|
||||||
...settings,
|
...settings,
|
||||||
notificationSettings: {
|
pushNotificationSettings,
|
||||||
...settings.notificationSettings,
|
|
||||||
...notificationUpdate,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Trans } from "@lingui/react/macro"
|
||||||
|
import { Checkbox, type CheckboxProps } from "@mantine/core"
|
||||||
|
import type { ReactNode } from "react"
|
||||||
|
import { useAppSelector } from "@/app/store"
|
||||||
|
|
||||||
|
export const ReceivePushNotificationsChechbox = (props: CheckboxProps) => {
|
||||||
|
const pushNotificationsEnabled = useAppSelector(state => state.server.serverInfos?.pushNotificationsEnabled)
|
||||||
|
const pushNotificationsConfigured = useAppSelector(state => !!state.user.settings?.pushNotificationSettings.type)
|
||||||
|
|
||||||
|
const disabled = !pushNotificationsEnabled || !pushNotificationsConfigured
|
||||||
|
let description: ReactNode = ""
|
||||||
|
if (!pushNotificationsEnabled) {
|
||||||
|
description = <Trans>Push notifications are not enabled on this CommaFeed instance.</Trans>
|
||||||
|
} else if (!pushNotificationsConfigured) {
|
||||||
|
description = <Trans>Push notifications are not configured in your user settings.</Trans>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Checkbox label={<Trans>Receive push notifications</Trans>} disabled={disabled} description={description} {...props} />
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Trans } from "@lingui/react/macro"
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { Box, Button, Checkbox, Group, Stack, Stepper, TextInput } from "@mantine/core"
|
import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useAsyncCallback } from "react-async-hook"
|
import { useAsyncCallback } from "react-async-hook"
|
||||||
@@ -11,6 +11,7 @@ import { useAppDispatch } from "@/app/store"
|
|||||||
import { reloadTree } from "@/app/tree/thunks"
|
import { reloadTree } from "@/app/tree/thunks"
|
||||||
import type { FeedInfoRequest, SubscribeRequest } from "@/app/types"
|
import type { FeedInfoRequest, SubscribeRequest } from "@/app/types"
|
||||||
import { Alert } from "@/components/Alert"
|
import { Alert } from "@/components/Alert"
|
||||||
|
import { ReceivePushNotificationsChechbox } from "@/components/ReceivePushNotificationsChechbox"
|
||||||
import { CategorySelect } from "./CategorySelect"
|
import { CategorySelect } from "./CategorySelect"
|
||||||
|
|
||||||
export function Subscribe() {
|
export function Subscribe() {
|
||||||
@@ -28,7 +29,7 @@ export function Subscribe() {
|
|||||||
url: "",
|
url: "",
|
||||||
title: "",
|
title: "",
|
||||||
categoryId: Constants.categories.all.id,
|
categoryId: Constants.categories.all.id,
|
||||||
notifyOnNewEntries: true,
|
pushNotificationsEnabled: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -104,9 +105,8 @@ export function Subscribe() {
|
|||||||
<TextInput label={<Trans>Feed URL</Trans>} {...step1Form.getInputProps("url")} disabled />
|
<TextInput label={<Trans>Feed URL</Trans>} {...step1Form.getInputProps("url")} disabled />
|
||||||
<TextInput label={<Trans>Feed name</Trans>} {...step1Form.getInputProps("title")} required autoFocus />
|
<TextInput label={<Trans>Feed name</Trans>} {...step1Form.getInputProps("title")} required autoFocus />
|
||||||
<CategorySelect label={<Trans>Category</Trans>} {...step1Form.getInputProps("categoryId")} clearable />
|
<CategorySelect label={<Trans>Category</Trans>} {...step1Form.getInputProps("categoryId")} clearable />
|
||||||
<Checkbox
|
<ReceivePushNotificationsChechbox
|
||||||
label={<Trans>Receive notifications</Trans>}
|
{...step1Form.getInputProps("pushNotificationsEnabled", { type: "checkbox" })}
|
||||||
{...step1Form.getInputProps("notifyOnNewEntries", { type: "checkbox" })}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
import { Trans } from "@lingui/react/macro"
|
|
||||||
import { Select, Stack, TextInput } from "@mantine/core"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
|
||||||
import type { NotificationService as NotificationServiceType, NotificationSettings as NotificationSettingsType } from "@/app/types"
|
|
||||||
import { changeNotificationSettings } from "@/app/user/thunks"
|
|
||||||
|
|
||||||
function useDebouncedSave(value: string, settingsKey: string, dispatch: ReturnType<typeof useAppDispatch>) {
|
|
||||||
const [localValue, setLocalValue] = useState(value)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLocalValue(value)
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
const onBlur = async () => {
|
|
||||||
if (localValue !== value) {
|
|
||||||
await dispatch(changeNotificationSettings({ [settingsKey]: localValue }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { localValue, setLocalValue, onBlur }
|
|
||||||
}
|
|
||||||
|
|
||||||
function toServiceValue(settings?: NotificationSettingsType): NotificationServiceType {
|
|
||||||
if (settings?.enabled && settings.type) {
|
|
||||||
return settings.type
|
|
||||||
}
|
|
||||||
return "disabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NotificationSettings() {
|
|
||||||
const notificationSettings = useAppSelector(state => state.user.settings?.notificationSettings)
|
|
||||||
const dispatch = useAppDispatch()
|
|
||||||
|
|
||||||
const serviceValue = toServiceValue(notificationSettings)
|
|
||||||
|
|
||||||
const serverUrl = useDebouncedSave(notificationSettings?.serverUrl ?? "", "serverUrl", dispatch)
|
|
||||||
const token = useDebouncedSave(notificationSettings?.token ?? "", "token", dispatch)
|
|
||||||
const userKey = useDebouncedSave(notificationSettings?.userKey ?? "", "userKey", dispatch)
|
|
||||||
const topic = useDebouncedSave(notificationSettings?.topic ?? "", "topic", dispatch)
|
|
||||||
|
|
||||||
const onServiceChange = async (value: string | null) => {
|
|
||||||
if (value === "disabled" || !value) {
|
|
||||||
await dispatch(changeNotificationSettings({ enabled: false, type: undefined }))
|
|
||||||
} else {
|
|
||||||
await dispatch(changeNotificationSettings({ enabled: true, type: value as Exclude<NotificationServiceType, "disabled"> }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Select
|
|
||||||
label={<Trans>Notification service</Trans>}
|
|
||||||
data={[
|
|
||||||
{ value: "disabled", label: "Disabled" },
|
|
||||||
{ value: "ntfy", label: "ntfy" },
|
|
||||||
{ value: "gotify", label: "Gotify" },
|
|
||||||
{ value: "pushover", label: "Pushover" },
|
|
||||||
]}
|
|
||||||
value={serviceValue}
|
|
||||||
onChange={onServiceChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{serviceValue === "ntfy" && (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>Server URL</Trans>}
|
|
||||||
placeholder="https://ntfy.sh"
|
|
||||||
value={serverUrl.localValue}
|
|
||||||
onChange={e => serverUrl.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={serverUrl.onBlur}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>Topic</Trans>}
|
|
||||||
placeholder="commafeed"
|
|
||||||
value={topic.localValue}
|
|
||||||
onChange={e => topic.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={topic.onBlur}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>Access token (optional)</Trans>}
|
|
||||||
value={token.localValue}
|
|
||||||
onChange={e => token.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={token.onBlur}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{serviceValue === "gotify" && (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>Server URL</Trans>}
|
|
||||||
placeholder="https://gotify.example.com"
|
|
||||||
value={serverUrl.localValue}
|
|
||||||
onChange={e => serverUrl.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={serverUrl.onBlur}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>App token</Trans>}
|
|
||||||
value={token.localValue}
|
|
||||||
onChange={e => token.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={token.onBlur}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{serviceValue === "pushover" && (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>API token</Trans>}
|
|
||||||
value={token.localValue}
|
|
||||||
onChange={e => token.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={token.onBlur}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Trans>User key</Trans>}
|
|
||||||
value={userKey.localValue}
|
|
||||||
onChange={e => userKey.setLocalValue(e.currentTarget.value)}
|
|
||||||
onBlur={userKey.onBlur}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { useLingui } from "@lingui/react"
|
||||||
|
import { Trans } from "@lingui/react/macro"
|
||||||
|
import { Button, Group, Select, Stack, TextInput } from "@mantine/core"
|
||||||
|
import { useForm } from "@mantine/form"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { TbDeviceFloppy } from "react-icons/tb"
|
||||||
|
import { redirectToSelectedSource } from "@/app/redirect/thunks"
|
||||||
|
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||||
|
import type { PushNotificationSettings as PushNotificationSettingsModel } from "@/app/types"
|
||||||
|
import { changeNotificationSettings } from "@/app/user/thunks"
|
||||||
|
|
||||||
|
export function PushNotificationSettings() {
|
||||||
|
const notificationSettings = useAppSelector(state => state.user.settings?.pushNotificationSettings)
|
||||||
|
const pushNotificationsEnabled = useAppSelector(state => state.server.serverInfos?.pushNotificationsEnabled)
|
||||||
|
const { _ } = useLingui()
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const form = useForm<PushNotificationSettingsModel>()
|
||||||
|
useEffect(() => {
|
||||||
|
if (notificationSettings) form.initialize(notificationSettings)
|
||||||
|
}, [form.initialize, notificationSettings])
|
||||||
|
|
||||||
|
const handleSubmit = (values: PushNotificationSettingsModel) => {
|
||||||
|
dispatch(changeNotificationSettings(values))
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeInputProps = form.getInputProps("type")
|
||||||
|
|
||||||
|
if (!pushNotificationsEnabled) {
|
||||||
|
return <Trans>Push notifications are not enabled on this CommaFeed instance.</Trans>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
|
<Stack>
|
||||||
|
<Select
|
||||||
|
label={<Trans>Push notification service</Trans>}
|
||||||
|
data={[
|
||||||
|
{ value: "ntfy", label: "ntfy" },
|
||||||
|
{ value: "gotify", label: "Gotify" },
|
||||||
|
{ value: "pushover", label: "Pushover" },
|
||||||
|
]}
|
||||||
|
clearable
|
||||||
|
{...typeInputProps}
|
||||||
|
onChange={value => {
|
||||||
|
typeInputProps.onChange(value)
|
||||||
|
form.setFieldValue("serverUrl", "")
|
||||||
|
form.setFieldValue("topic", "")
|
||||||
|
form.setFieldValue("userSecret", "")
|
||||||
|
form.setFieldValue("userId", "")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{form.values.type === "ntfy" && (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
label={<Trans>Server URL</Trans>}
|
||||||
|
placeholder="https://ntfy.sh"
|
||||||
|
required
|
||||||
|
{...form.getInputProps("serverUrl")}
|
||||||
|
/>
|
||||||
|
<TextInput label={<Trans>Topic</Trans>} placeholder="commafeed" required {...form.getInputProps("topic")} />
|
||||||
|
<TextInput label={<Trans>Access token</Trans>} {...form.getInputProps("userSecret")} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{form.values.type === "gotify" && (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
label={<Trans>Server URL</Trans>}
|
||||||
|
placeholder="https://gotify.example.com"
|
||||||
|
required
|
||||||
|
{...form.getInputProps("serverUrl")}
|
||||||
|
/>
|
||||||
|
<TextInput label={<Trans>App token</Trans>} required {...form.getInputProps("userSecret")} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{form.values.type === "pushover" && (
|
||||||
|
<>
|
||||||
|
<TextInput label={<Trans>User key</Trans>} required {...form.getInputProps("userId")} />
|
||||||
|
<TextInput label={<Trans>API token</Trans>} required {...form.getInputProps("userSecret")} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Group>
|
||||||
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />}>
|
||||||
|
<Trans>Save</Trans>
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -34,8 +34,8 @@ msgstr "<0> هل تحتاج إلى حساب؟ </0> <1> اشترك! </ 1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "حول"
|
msgstr "حول"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "مفتاح API"
|
msgstr "مفتاح API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "لم يتم العثور على شيء"
|
msgstr "لم يتم العثور على شيء"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "الأقدم أولا"
|
msgstr "الأقدم أولا"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "الملف الشخصي"
|
msgstr "الملف الشخصي"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "إلغاء النجم"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "إلغاء الاشتراك"
|
msgstr "إلغاء الاشتراك"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Necessites un compte?</0><1>Registreu-vos!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Sobre"
|
msgstr "Sobre"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "Anunci"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Clau API"
|
msgstr "Clau API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr "No hi ha opcions de compartició disponibles."
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "No s'ha trobat res"
|
msgstr "No s'ha trobat res"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "el més vell primer"
|
msgstr "el més vell primer"
|
||||||
@@ -827,9 +820,25 @@ msgstr "Color primari"
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Perfil"
|
msgstr "Perfil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "Clic dret"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr "Selecciona el següent canal/categoria no llegit"
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr "Selecciona el canal/categoria anterior sense llegir"
|
msgstr "Selecciona el canal/categoria anterior sense llegir"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Canvia la barra lateral"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Commuta l'estat destacat de l'entrada actual"
|
msgstr "Commuta l'estat destacat de l'entrada actual"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Desestrellar"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Donar-se de baixa"
|
msgstr "Donar-se de baixa"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Potřebujete účet?</0><1>Zaregistrujte se!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Asi"
|
msgstr "Asi"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Klíč API"
|
msgstr "Klíč API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nic nebylo nalezeno"
|
msgstr "Nic nebylo nalezeno"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Nejdříve nejstarší"
|
msgstr "Nejdříve nejstarší"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Odstranit hvězdu"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Odhlásit odběr"
|
msgstr "Odhlásit odběr"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Angen cyfrif?</0><1>Ymunwch!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Ynghylch"
|
msgstr "Ynghylch"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Allwedd API"
|
msgstr "Allwedd API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Dim wedi'i ddarganfod"
|
msgstr "Dim wedi'i ddarganfod"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Hynaf yn gyntaf"
|
msgstr "Hynaf yn gyntaf"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Proffil"
|
msgstr "Proffil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "dad-seren"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Dad-danysgrifio"
|
msgstr "Dad-danysgrifio"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Har du brug for en konto?</0><1>Tilmeld dig!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Omkring"
|
msgstr "Omkring"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nøgle"
|
msgstr "API-nøgle"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Intet fundet"
|
msgstr "Intet fundet"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Ældst først"
|
msgstr "Ældst først"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr ""
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Afmeld"
|
msgstr "Afmeld"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Benötigen Sie ein Konto?</0><1>Hier geht's zur Registrierung!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Über"
|
msgstr "Über"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "Ankündigung"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-Schlüssel"
|
msgstr "API-Schlüssel"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nichts gefunden"
|
msgstr "Nichts gefunden"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Älteste zuerst"
|
msgstr "Älteste zuerst"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "Rechtsklick"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Sidebar an- und ausschalten"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Markierungsstatus des aktuellen Eintrags ändern"
|
msgstr "Markierungsstatus des aktuellen Eintrags ändern"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Stern entfernen"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Abbestellen"
|
msgstr "Abbestellen"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ msgstr "<0>Need an account?</0><1>Sign up!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "About"
|
msgstr "About"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr "Access token (optional)"
|
msgstr "Access token"
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
@@ -98,11 +98,11 @@ msgstr "Announcement"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API key"
|
msgstr "API key"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr "API token"
|
msgstr "API token"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr "App token"
|
msgstr "App token"
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr "Build a filter expression to indicate what you want to read. Entries tha
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr "No sharing options available."
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nothing found"
|
msgstr "Nothing found"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr "Notification service"
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr "Notifications"
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Oldest first"
|
msgstr "Oldest first"
|
||||||
@@ -827,10 +820,26 @@ msgstr "Primary color"
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profile"
|
msgstr "Profile"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr "Push notification service"
|
||||||
msgstr "Receive notifications"
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr "Push notifications"
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr "Push notifications are not configured in your user settings."
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
|
msgstr "Receive push notifications"
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
msgid "Recover password"
|
msgid "Recover password"
|
||||||
@@ -866,6 +875,7 @@ msgstr "Right click"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr "Select next unread feed/category"
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr "Select previous unread feed/category"
|
msgstr "Select previous unread feed/category"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr "Server URL"
|
msgstr "Server URL"
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Toggle sidebar"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Toggle starred status of current entry"
|
msgstr "Toggle starred status of current entry"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr "Topic"
|
msgstr "Topic"
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Unstar"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Unsubscribe"
|
msgstr "Unsubscribe"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr "User key"
|
msgstr "User key"
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ msgstr "<0>¿Necesitas una cuenta?</0><1>¡Regístrate!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Acerca de"
|
msgstr "Acerca de"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -99,11 +99,11 @@ msgstr "Anuncio"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Clave API"
|
msgstr "Clave API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -171,6 +171,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -679,14 +680,6 @@ msgstr "No hay opciones para compartir disponibles."
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nada encontrado"
|
msgstr "Nada encontrado"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Las más antiguas primero"
|
msgstr "Las más antiguas primero"
|
||||||
@@ -828,9 +821,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Perfil"
|
msgstr "Perfil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -867,6 +876,7 @@ msgstr "Clic derecho"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -899,8 +909,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1081,7 +1091,7 @@ msgstr "Alternar barra lateral"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Alternar estado destacado de la entrada actual"
|
msgstr "Alternar estado destacado de la entrada actual"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1108,7 +1118,7 @@ msgstr "Desmarcar"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Cancelar suscripción"
|
msgstr "Cancelar suscripción"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>به یک حساب نیاز دارید؟</0><1>ثبت نام کنید
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "در مورد"
|
msgstr "در مورد"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "کلید API"
|
msgstr "کلید API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "چیزی پیدا نشد"
|
msgstr "چیزی پیدا نشد"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "قدیمی ترین اول"
|
msgstr "قدیمی ترین اول"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "نمایه"
|
msgstr "نمایه"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr ""
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "لغو اشتراک"
|
msgstr "لغو اشتراک"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Tarvitsetko tilin?</0><1>Rekisteröidy!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Noin"
|
msgstr "Noin"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-avain"
|
msgstr "API-avain"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Mitään ei löytynyt"
|
msgstr "Mitään ei löytynyt"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Vanhin ensin"
|
msgstr "Vanhin ensin"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profiili"
|
msgstr "Profiili"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Poista tähti"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Peruuta tilaus"
|
msgstr "Peruuta tilaus"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Besoin d'un compte ?</0><1>Enregistrez-vous !</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "À propos"
|
msgstr "À propos"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "Annonces"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Clé API"
|
msgstr "Clé API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr "Aucune option de partage disponible"
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Aucun résultat"
|
msgstr "Aucun résultat"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Du plus ancien au plus récent"
|
msgstr "Du plus ancien au plus récent"
|
||||||
@@ -827,9 +820,25 @@ msgstr "Couleur d'ambiance"
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "Clic droit"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr "Sélectionner l'article non lu suivant/la catégorie non lue suivante"
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr "Sélectionner l'article non lu précédent/la catégorie non lue précédente"
|
msgstr "Sélectionner l'article non lu précédent/la catégorie non lue précédente"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Montrer/cacher la barre latérale"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Montrer/cacher le statut favori de l'entrée"
|
msgstr "Montrer/cacher le statut favori de l'entrée"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Retirer des favoris"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Se désabonner"
|
msgstr "Se désabonner"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ msgstr "<0>Necesitas unha conta?</0><1>Crea unha!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Sobre"
|
msgstr "Sobre"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -99,11 +99,11 @@ msgstr "Anuncio"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Clave API"
|
msgstr "Clave API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -171,6 +171,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -679,14 +680,6 @@ msgstr "Non hai opcións para poder compartir."
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Non se atopou nada"
|
msgstr "Non se atopou nada"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Primeiro o máis antigo"
|
msgstr "Primeiro o máis antigo"
|
||||||
@@ -828,9 +821,25 @@ msgstr "Cor destacada"
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Perfil"
|
msgstr "Perfil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -867,6 +876,7 @@ msgstr "Botón dereito"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -899,8 +909,8 @@ msgstr "Seleccionar a seguinte canle/categoría sen ler"
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr "Seleccionar a canle/categoría anterior sen ler"
|
msgstr "Seleccionar a canle/categoría anterior sen ler"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1081,7 +1091,7 @@ msgstr "Activación do panel lateral"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Activación do marcado con estrela do artigo actual"
|
msgstr "Activación do marcado con estrela do artigo actual"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1108,7 +1118,7 @@ msgstr "Retirar estrela"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Cancelar a subscrición"
|
msgstr "Cancelar a subscrición"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Fiókra van szüksége?</0><1>Regisztráljon!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Kb"
|
msgstr "Kb"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API kulcs"
|
msgstr "API kulcs"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Semmi sem található"
|
msgstr "Semmi sem található"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "A legidősebb első"
|
msgstr "A legidősebb első"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr ""
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Leiratkozás"
|
msgstr "Leiratkozás"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Butuh akun?</0><1>Daftar!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Tentang"
|
msgstr "Tentang"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "kunci API"
|
msgstr "kunci API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Tidak ada yang ditemukan"
|
msgstr "Tidak ada yang ditemukan"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Tertua dulu"
|
msgstr "Tertua dulu"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Hapus bintang"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Berhenti berlangganan"
|
msgstr "Berhenti berlangganan"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Hai bisogno di un account?</0><1>Registrati!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Circa"
|
msgstr "Circa"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Chiave API"
|
msgstr "Chiave API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Non è stato trovato nulla"
|
msgstr "Non è stato trovato nulla"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Il più vecchio prima"
|
msgstr "Il più vecchio prima"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profilo"
|
msgstr "Profilo"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Elimina le stelle"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Annulla iscrizione"
|
msgstr "Annulla iscrizione"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>アカウントが必要ですか?</0><1>サインアップ!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "About"
|
msgstr "About"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "お知らせ"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "APIキー"
|
msgstr "APIキー"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr "共有オプションは利用できません。"
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "何も見つかりませんでした"
|
msgstr "何も見つかりませんでした"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "古い順"
|
msgstr "古い順"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "プロフィール"
|
msgstr "プロフィール"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "右クリック"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "サイドバーを切り替える"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "現在のエントリーのスターステータスを切り替える"
|
msgstr "現在のエントリーのスターステータスを切り替える"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "スターを外す"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "退会"
|
msgstr "退会"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>계정이 필요하십니까?</0><1>가입하세요!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "정보"
|
msgstr "정보"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API 키"
|
msgstr "API 키"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "아무것도 찾을 수 없습니다"
|
msgstr "아무것도 찾을 수 없습니다"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "가장 오래된 것부터"
|
msgstr "가장 오래된 것부터"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "프로필"
|
msgstr "프로필"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "별표 제거"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "구독 취소"
|
msgstr "구독 취소"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Perlukan akaun?</0><1>Daftar!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Mengenai"
|
msgstr "Mengenai"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Kunci API"
|
msgstr "Kunci API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Tiada apa-apa dijumpai"
|
msgstr "Tiada apa-apa dijumpai"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Tertua dahulu"
|
msgstr "Tertua dahulu"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Nyahbintang"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Nyahlanggan"
|
msgstr "Nyahlanggan"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Trenger du en konto?</0><1>Registrer deg!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Omtrent"
|
msgstr "Omtrent"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nøkkel"
|
msgstr "API-nøkkel"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Ingenting funnet"
|
msgstr "Ingenting funnet"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Eldste først"
|
msgstr "Eldste først"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Fjern stjerne"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Avslutt abonnementet"
|
msgstr "Avslutt abonnementet"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Een account nodig?</0><1>Meld je aan!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Over"
|
msgstr "Over"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-sleutel"
|
msgstr "API-sleutel"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Niets gevonden"
|
msgstr "Niets gevonden"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Oudste eerst"
|
msgstr "Oudste eerst"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profiel"
|
msgstr "Profiel"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Sterren uit"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Afmelden"
|
msgstr "Afmelden"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Trenger du en konto?</0><1>Registrer deg!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Omtrent"
|
msgstr "Omtrent"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nøkkel"
|
msgstr "API-nøkkel"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Ingenting funnet"
|
msgstr "Ingenting funnet"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Eldste først"
|
msgstr "Eldste først"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Fjern stjerne"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Avslutt abonnementet"
|
msgstr "Avslutt abonnementet"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Potrzebujesz konta?</0><1>Zarejestruj się!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "O"
|
msgstr "O"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "klucz API"
|
msgstr "klucz API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nic nie znaleziono"
|
msgstr "Nic nie znaleziono"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Najstarsze jako pierwsze"
|
msgstr "Najstarsze jako pierwsze"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr ""
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Anuluj subskrypcję"
|
msgstr "Anuluj subskrypcję"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Precisa de uma conta?</0><1>Inscreva-se!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Sobre"
|
msgstr "Sobre"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "Aviso"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "chave de API"
|
msgstr "chave de API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr "Nenhuma opção de compartilhamento disponível"
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nada encontrado"
|
msgstr "Nada encontrado"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Mais antigo primeiro"
|
msgstr "Mais antigo primeiro"
|
||||||
@@ -827,9 +820,25 @@ msgstr "Cor primária"
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Perfil"
|
msgstr "Perfil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "Clique com o botão direito"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr "Selecionar próximo feed/categoria não lido"
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr "Selecionar feed/categoria não lido anterior"
|
msgstr "Selecionar feed/categoria não lido anterior"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Alternar barra lateral"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Alternar estrela da entrada atual"
|
msgstr "Alternar estrela da entrada atual"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Desestrelar"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Cancelar inscrição"
|
msgstr "Cancelar inscrição"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Нужен аккаунт?</0><1>Зарегистрируйтесь!<
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "О CommaFeed"
|
msgstr "О CommaFeed"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "Объявление"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Ключ API"
|
msgstr "Ключ API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Ничего не найдено"
|
msgstr "Ничего не найдено"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Сначала самые старые"
|
msgstr "Сначала самые старые"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Профиль"
|
msgstr "Профиль"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "Правый клик"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Переключить боковую панель"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "Переключение статуса избранное для текущей записи"
|
msgstr "Переключение статуса избранное для текущей записи"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Удалить из избранного"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Отписаться"
|
msgstr "Отписаться"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Potrebujete účet?</0><1>Zaregistrujte sa!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Asi"
|
msgstr "Asi"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "Kľúč API"
|
msgstr "Kľúč API"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Nič sa nenašlo"
|
msgstr "Nič sa nenašlo"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Najprv najstarší"
|
msgstr "Najprv najstarší"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Odobrať hviezdičku"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Zrušte odber"
|
msgstr "Zrušte odber"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Behöver du ett konto?</0><1>Registrera dig!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Ungefär"
|
msgstr "Ungefär"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr ""
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API-nyckel"
|
msgstr "API-nyckel"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Inget hittades"
|
msgstr "Inget hittades"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Äldst först"
|
msgstr "Äldst först"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr ""
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr ""
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr ""
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Avregistrera"
|
msgstr "Avregistrera"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>Bir hesaba mı ihtiyacınız var?</0><1>Kaydolun!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Hakkında"
|
msgstr "Hakkında"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "Duyuru"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API anahtarı"
|
msgstr "API anahtarı"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr ""
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "Hiçbir şey bulunamadı"
|
msgstr "Hiçbir şey bulunamadı"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "Önce en eski"
|
msgstr "Önce en eski"
|
||||||
@@ -827,9 +820,25 @@ msgstr ""
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "Sağ tık"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr ""
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "Kenar çubuğunu göster/gizle"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "Yıldızı kaldır"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Aboneliği iptal et"
|
msgstr "Aboneliği iptal et"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ msgstr "<0>需要一个帐户?</0><1>注册!</1>"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "关于"
|
msgstr "关于"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Access token (optional)"
|
msgid "Access token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -98,11 +98,11 @@ msgstr "公告"
|
|||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "API 密钥"
|
msgstr "API 密钥"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "App token"
|
msgid "App token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -170,6 +170,7 @@ msgstr ""
|
|||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
@@ -678,14 +679,6 @@ msgstr "没有可用的分享选项"
|
|||||||
msgid "Nothing found"
|
msgid "Nothing found"
|
||||||
msgstr "没有找到"
|
msgstr "没有找到"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
|
||||||
msgid "Notification service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/SettingsPage.tsx
|
|
||||||
msgid "Notifications"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Oldest first"
|
msgid "Oldest first"
|
||||||
msgstr "最早的优先"
|
msgstr "最早的优先"
|
||||||
@@ -827,9 +820,25 @@ msgstr "主颜色"
|
|||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "配置文件"
|
msgstr "配置文件"
|
||||||
|
|
||||||
#: src/components/content/add/Subscribe.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
msgid "Push notification service"
|
||||||
msgid "Receive notifications"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/app/SettingsPage.tsx
|
||||||
|
msgid "Push notifications"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Push notifications are not configured in your user settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
|
msgid "Push notifications are not enabled on this CommaFeed instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ReceivePushNotificationsChechbox.tsx
|
||||||
|
msgid "Receive push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
@@ -866,6 +875,7 @@ msgstr "右键单击"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/components/settings/CustomCodeSettings.tsx
|
#: src/components/settings/CustomCodeSettings.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
#: src/pages/app/FeedDetailsPage.tsx
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -898,8 +908,8 @@ msgstr "选择下一个未读信息流/类别"
|
|||||||
msgid "Select previous unread feed/category"
|
msgid "Select previous unread feed/category"
|
||||||
msgstr "选择上一个未读信息流/类别"
|
msgstr "选择上一个未读信息流/类别"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Server URL"
|
msgid "Server URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1080,7 +1090,7 @@ msgstr "切换侧边栏"
|
|||||||
msgid "Toggle starred status of current entry"
|
msgid "Toggle starred status of current entry"
|
||||||
msgstr "切换当前条目的星标状态"
|
msgstr "切换当前条目的星标状态"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1107,7 +1117,7 @@ msgstr "取消星标"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "取消订阅"
|
msgstr "取消订阅"
|
||||||
|
|
||||||
#: src/components/settings/NotificationSettings.tsx
|
#: src/components/settings/PushNotificationSettings.tsx
|
||||||
msgid "User key"
|
msgid "User key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,8 @@ const shownGauges: Record<string, string> = {
|
|||||||
"com.commafeed.backend.feed.FeedRefreshEngine.queue.size": "Feed Refresh Engine queue size",
|
"com.commafeed.backend.feed.FeedRefreshEngine.queue.size": "Feed Refresh Engine queue size",
|
||||||
"com.commafeed.backend.feed.FeedRefreshEngine.worker.active": "Feed Refresh Engine active HTTP workers",
|
"com.commafeed.backend.feed.FeedRefreshEngine.worker.active": "Feed Refresh Engine active HTTP workers",
|
||||||
"com.commafeed.backend.feed.FeedRefreshEngine.updater.active": "Feed Refresh Engine active database update workers",
|
"com.commafeed.backend.feed.FeedRefreshEngine.updater.active": "Feed Refresh Engine active database update workers",
|
||||||
"com.commafeed.backend.HttpGetter.pool.max": "HttpGetter max pool size",
|
"com.commafeed.backend.feed.FeedRefreshEngine.notifier.active": "Feed Refresh Engine active push notifications workers",
|
||||||
"com.commafeed.backend.HttpGetter.pool.size": "HttpGetter current pool size",
|
"com.commafeed.backend.feed.FeedRefreshEngine.notifier.queue": "Feed Refresh Engine queued push notifications workers",
|
||||||
"com.commafeed.backend.HttpGetter.pool.leased": "HttpGetter active connections",
|
|
||||||
"com.commafeed.backend.HttpGetter.pool.pending": "HttpGetter waiting for a connection",
|
|
||||||
"com.commafeed.backend.HttpGetter.cache.size": "HttpGetter cached entries",
|
"com.commafeed.backend.HttpGetter.cache.size": "HttpGetter cached entries",
|
||||||
"com.commafeed.backend.HttpGetter.cache.memoryUsage": "HttpGetter cache memory usage",
|
"com.commafeed.backend.HttpGetter.cache.memoryUsage": "HttpGetter cache memory usage",
|
||||||
"com.commafeed.frontend.ws.WebSocketSessions.users": "WebSocket users",
|
"com.commafeed.frontend.ws.WebSocketSessions.users": "WebSocket users",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Anchor,
|
Anchor,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
|
||||||
Code,
|
Code,
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
@@ -31,6 +30,7 @@ import { Alert } from "@/components/Alert"
|
|||||||
import { CategorySelect } from "@/components/content/add/CategorySelect"
|
import { CategorySelect } from "@/components/content/add/CategorySelect"
|
||||||
import { FilteringExpressionEditor } from "@/components/content/edit/FilteringExpressionEditor"
|
import { FilteringExpressionEditor } from "@/components/content/edit/FilteringExpressionEditor"
|
||||||
import { Loader } from "@/components/Loader"
|
import { Loader } from "@/components/Loader"
|
||||||
|
import { ReceivePushNotificationsChechbox } from "@/components/ReceivePushNotificationsChechbox"
|
||||||
import { RelativeDate } from "@/components/RelativeDate"
|
import { RelativeDate } from "@/components/RelativeDate"
|
||||||
|
|
||||||
export function FeedDetailsPage() {
|
export function FeedDetailsPage() {
|
||||||
@@ -143,6 +143,7 @@ export function FeedDetailsPage() {
|
|||||||
<TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
|
<TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
|
||||||
<CategorySelect label={<Trans>Category</Trans>} {...form.getInputProps("categoryId")} clearable />
|
<CategorySelect label={<Trans>Category</Trans>} {...form.getInputProps("categoryId")} clearable />
|
||||||
<NumberInput label={<Trans>Position</Trans>} {...form.getInputProps("position")} required min={0} />
|
<NumberInput label={<Trans>Position</Trans>} {...form.getInputProps("position")} required min={0} />
|
||||||
|
<ReceivePushNotificationsChechbox {...form.getInputProps("pushNotificationsEnabled", { type: "checkbox" })} />
|
||||||
<Input.Wrapper
|
<Input.Wrapper
|
||||||
label={<Trans>Filtering expression</Trans>}
|
label={<Trans>Filtering expression</Trans>}
|
||||||
description={
|
description={
|
||||||
@@ -164,10 +165,6 @@ export function FeedDetailsPage() {
|
|||||||
<FilteringExpressionEditor initialValue={feed.filter} onChange={value => form.setFieldValue("filter", value)} />
|
<FilteringExpressionEditor initialValue={feed.filter} onChange={value => form.setFieldValue("filter", value)} />
|
||||||
</Box>
|
</Box>
|
||||||
</Input.Wrapper>
|
</Input.Wrapper>
|
||||||
<Checkbox
|
|
||||||
label={<Trans>Receive notifications</Trans>}
|
|
||||||
{...form.getInputProps("notifyOnNewEntries", { type: "checkbox" })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { Container, Tabs } from "@mantine/core"
|
|||||||
import { TbBell, TbCode, TbPhoto, TbUser } from "react-icons/tb"
|
import { TbBell, TbCode, TbPhoto, TbUser } from "react-icons/tb"
|
||||||
import { CustomCodeSettings } from "@/components/settings/CustomCodeSettings"
|
import { CustomCodeSettings } from "@/components/settings/CustomCodeSettings"
|
||||||
import { DisplaySettings } from "@/components/settings/DisplaySettings"
|
import { DisplaySettings } from "@/components/settings/DisplaySettings"
|
||||||
import { NotificationSettings } from "@/components/settings/NotificationSettings"
|
|
||||||
import { ProfileSettings } from "@/components/settings/ProfileSettings"
|
import { ProfileSettings } from "@/components/settings/ProfileSettings"
|
||||||
|
import { PushNotificationSettings } from "@/components/settings/PushNotificationSettings"
|
||||||
|
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
return (
|
return (
|
||||||
@@ -14,8 +14,8 @@ export function SettingsPage() {
|
|||||||
<Tabs.Tab value="display" leftSection={<TbPhoto size={16} />}>
|
<Tabs.Tab value="display" leftSection={<TbPhoto size={16} />}>
|
||||||
<Trans>Display</Trans>
|
<Trans>Display</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="notifications" leftSection={<TbBell size={16} />}>
|
<Tabs.Tab value="push-notifications" leftSection={<TbBell size={16} />}>
|
||||||
<Trans>Notifications</Trans>
|
<Trans>Push notifications</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="customCode" leftSection={<TbCode size={16} />}>
|
<Tabs.Tab value="customCode" leftSection={<TbCode size={16} />}>
|
||||||
<Trans>Custom code</Trans>
|
<Trans>Custom code</Trans>
|
||||||
@@ -29,8 +29,8 @@ export function SettingsPage() {
|
|||||||
<DisplaySettings />
|
<DisplaySettings />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="notifications" pt="xl">
|
<Tabs.Panel value="push-notifications" pt="xl">
|
||||||
<NotificationSettings />
|
<PushNotificationSettings />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="customCode" pt="xl">
|
<Tabs.Panel value="customCode" pt="xl">
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ public interface CommaFeedConfiguration {
|
|||||||
@ConfigDocSection
|
@ConfigDocSection
|
||||||
FeedRefresh feedRefresh();
|
FeedRefresh feedRefresh();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push notification settings.
|
||||||
|
*/
|
||||||
|
@ConfigDocSection
|
||||||
|
PushNotifications pushNotifications();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database settings.
|
* Database settings.
|
||||||
*/
|
*/
|
||||||
@@ -242,6 +248,28 @@ public interface CommaFeedConfiguration {
|
|||||||
Duration forceRefreshCooldownDuration();
|
Duration forceRefreshCooldownDuration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PushNotifications {
|
||||||
|
/**
|
||||||
|
* Whether to enable push notifications to notify users of new entries in their feeds.
|
||||||
|
*/
|
||||||
|
@WithDefault("true")
|
||||||
|
boolean enabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of threads used to send external notifications about new entries.
|
||||||
|
*/
|
||||||
|
@Min(1)
|
||||||
|
@WithDefault("5")
|
||||||
|
int threads();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum amount of notifications that can be queued before new notifications are discarded.
|
||||||
|
*/
|
||||||
|
@Min(1)
|
||||||
|
@WithDefault("100")
|
||||||
|
int queueCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
interface FeedRefreshErrorHandling {
|
interface FeedRefreshErrorHandling {
|
||||||
/**
|
/**
|
||||||
* Number of retries before backoff is applied.
|
* Number of retries before backoff is applied.
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.commafeed.backend;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.SequencedMap;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
|
||||||
|
import org.apache.hc.client5.http.DnsResolver;
|
||||||
|
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||||
|
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||||
|
import org.apache.hc.client5.http.config.TlsConfig;
|
||||||
|
import org.apache.hc.client5.http.entity.DeflateInputStream;
|
||||||
|
import org.apache.hc.client5.http.entity.InputStreamFactory;
|
||||||
|
import org.apache.hc.client5.http.entity.compress.ContentCoding;
|
||||||
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||||
|
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||||
|
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||||
|
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||||
|
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
||||||
|
import org.apache.hc.core5.http.Header;
|
||||||
|
import org.apache.hc.core5.http.message.BasicHeader;
|
||||||
|
import org.apache.hc.core5.util.TimeValue;
|
||||||
|
import org.apache.hc.core5.util.Timeout;
|
||||||
|
import org.brotli.dec.BrotliInputStream;
|
||||||
|
|
||||||
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
|
import com.commafeed.CommaFeedVersion;
|
||||||
|
import com.google.common.net.HttpHeaders;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import nl.altindag.ssl.SSLFactory;
|
||||||
|
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HttpClientFactory {
|
||||||
|
|
||||||
|
private final CommaFeedConfiguration config;
|
||||||
|
private final CommaFeedVersion version;
|
||||||
|
|
||||||
|
public CloseableHttpClient newClient(int poolSize) {
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = newConnectionManager(config, poolSize);
|
||||||
|
String userAgent = config.httpClient()
|
||||||
|
.userAgent()
|
||||||
|
.orElseGet(() -> String.format("CommaFeed/%s (https://github.com/Athou/commafeed)", version.getVersion()));
|
||||||
|
return newClient(connectionManager, userAgent, config.httpClient().idleConnectionsEvictionInterval());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CloseableHttpClient newClient(HttpClientConnectionManager connectionManager, String userAgent,
|
||||||
|
Duration idleConnectionsEvictionInterval) {
|
||||||
|
List<Header> headers = new ArrayList<>();
|
||||||
|
headers.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en"));
|
||||||
|
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
||||||
|
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
||||||
|
|
||||||
|
SequencedMap<String, InputStreamFactory> contentDecoderMap = new LinkedHashMap<>();
|
||||||
|
contentDecoderMap.put(ContentCoding.GZIP.token(), GZIPInputStream::new);
|
||||||
|
contentDecoderMap.put(ContentCoding.DEFLATE.token(), DeflateInputStream::new);
|
||||||
|
contentDecoderMap.put(ContentCoding.BROTLI.token(), BrotliInputStream::new);
|
||||||
|
|
||||||
|
return HttpClientBuilder.create()
|
||||||
|
.useSystemProperties()
|
||||||
|
.disableAutomaticRetries()
|
||||||
|
.disableCookieManagement()
|
||||||
|
.setUserAgent(userAgent)
|
||||||
|
.setDefaultHeaders(headers)
|
||||||
|
.setConnectionManager(connectionManager)
|
||||||
|
.evictExpiredConnections()
|
||||||
|
.evictIdleConnections(TimeValue.of(idleConnectionsEvictionInterval))
|
||||||
|
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PoolingHttpClientConnectionManager newConnectionManager(CommaFeedConfiguration config, int poolSize) {
|
||||||
|
SSLFactory sslFactory = SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
||||||
|
DnsResolver dnsResolver = config.httpClient().blockLocalAddresses()
|
||||||
|
? new BlockLocalAddressesDnsResolver(SystemDefaultDnsResolver.INSTANCE)
|
||||||
|
: SystemDefaultDnsResolver.INSTANCE;
|
||||||
|
|
||||||
|
return PoolingHttpClientConnectionManagerBuilder.create()
|
||||||
|
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
|
||||||
|
.setDefaultConnectionConfig(ConnectionConfig.custom()
|
||||||
|
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
|
||||||
|
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
|
||||||
|
.setTimeToLive(Timeout.of(config.httpClient().connectionTimeToLive()))
|
||||||
|
.build())
|
||||||
|
.setDefaultTlsConfig(TlsConfig.custom().setHandshakeTimeout(Timeout.of(config.httpClient().sslHandshakeTimeout())).build())
|
||||||
|
.setMaxConnPerRoute(poolSize)
|
||||||
|
.setMaxConnTotal(poolSize)
|
||||||
|
.setDnsResolver(dnsResolver)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private record BlockLocalAddressesDnsResolver(DnsResolver delegate) implements DnsResolver {
|
||||||
|
@Override
|
||||||
|
public InetAddress[] resolve(String host) throws UnknownHostException {
|
||||||
|
InetAddress[] addresses = delegate.resolve(host);
|
||||||
|
for (InetAddress addr : addresses) {
|
||||||
|
if (isLocalAddress(addr)) {
|
||||||
|
throw new UnknownHostException("Access to address blocked: " + addr.getHostAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveCanonicalHostname(String host) throws UnknownHostException {
|
||||||
|
return delegate.resolveCanonicalHostname(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocalAddress(InetAddress address) {
|
||||||
|
return address.isSiteLocalAddress() || address.isAnyLocalAddress() || address.isLinkLocalAddress()
|
||||||
|
|| address.isLoopbackAddress() || address.isMulticastAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,20 +2,12 @@ package com.commafeed.backend;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.InstantSource;
|
import java.time.InstantSource;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.SequencedMap;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
|
|
||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
import jakarta.ws.rs.core.CacheControl;
|
import jakarta.ws.rs.core.CacheControl;
|
||||||
@@ -24,36 +16,22 @@ import org.apache.commons.lang3.ArrayUtils;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.hc.client5.http.DnsResolver;
|
import org.apache.hc.client5.http.DnsResolver;
|
||||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
|
||||||
import org.apache.hc.client5.http.config.RequestConfig;
|
import org.apache.hc.client5.http.config.RequestConfig;
|
||||||
import org.apache.hc.client5.http.config.TlsConfig;
|
|
||||||
import org.apache.hc.client5.http.entity.DeflateInputStream;
|
|
||||||
import org.apache.hc.client5.http.entity.InputStreamFactory;
|
|
||||||
import org.apache.hc.client5.http.entity.compress.ContentCoding;
|
|
||||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
|
||||||
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
|
||||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||||
import org.apache.hc.client5.http.protocol.RedirectLocations;
|
import org.apache.hc.client5.http.protocol.RedirectLocations;
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
import org.apache.hc.client5.http.utils.DateUtils;
|
||||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||||
import org.apache.hc.core5.http.Header;
|
|
||||||
import org.apache.hc.core5.http.HttpEntity;
|
import org.apache.hc.core5.http.HttpEntity;
|
||||||
import org.apache.hc.core5.http.HttpStatus;
|
import org.apache.hc.core5.http.HttpStatus;
|
||||||
import org.apache.hc.core5.http.NameValuePair;
|
import org.apache.hc.core5.http.NameValuePair;
|
||||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||||
import org.apache.hc.core5.http.message.BasicHeader;
|
|
||||||
import org.apache.hc.core5.util.TimeValue;
|
|
||||||
import org.apache.hc.core5.util.Timeout;
|
import org.apache.hc.core5.util.Timeout;
|
||||||
import org.brotli.dec.BrotliInputStream;
|
|
||||||
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
|
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
|
||||||
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.CommaFeedConfiguration;
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
import com.commafeed.CommaFeedConfiguration.HttpClientCache;
|
import com.commafeed.CommaFeedConfiguration.HttpClientCache;
|
||||||
import com.commafeed.CommaFeedVersion;
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -66,8 +44,6 @@ import lombok.Getter;
|
|||||||
import lombok.Lombok;
|
import lombok.Lombok;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import nl.altindag.ssl.SSLFactory;
|
|
||||||
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
||||||
@@ -82,42 +58,27 @@ public class HttpGetter {
|
|||||||
private final CloseableHttpClient client;
|
private final CloseableHttpClient client;
|
||||||
private final Cache<HttpRequest, HttpResponse> cache;
|
private final Cache<HttpRequest, HttpResponse> cache;
|
||||||
|
|
||||||
public HttpGetter(CommaFeedConfiguration config, InstantSource instantSource, CommaFeedVersion version, MetricRegistry metrics) {
|
public HttpGetter(CommaFeedConfiguration config, InstantSource instantSource, HttpClientFactory httpClientFactory,
|
||||||
|
MetricRegistry metrics) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.instantSource = instantSource;
|
this.instantSource = instantSource;
|
||||||
|
this.client = httpClientFactory.newClient(config.feedRefresh().httpThreads());
|
||||||
PoolingHttpClientConnectionManager connectionManager = newConnectionManager(config);
|
|
||||||
String userAgent = config.httpClient()
|
|
||||||
.userAgent()
|
|
||||||
.orElseGet(() -> String.format("CommaFeed/%s (https://github.com/Athou/commafeed)", version.getVersion()));
|
|
||||||
|
|
||||||
this.client = newClient(connectionManager, userAgent, config.httpClient().idleConnectionsEvictionInterval());
|
|
||||||
this.cache = newCache(config);
|
this.cache = newCache(config);
|
||||||
|
|
||||||
metrics.registerGauge(MetricRegistry.name(getClass(), "pool", "max"), () -> connectionManager.getTotalStats().getMax());
|
|
||||||
metrics.registerGauge(MetricRegistry.name(getClass(), "pool", "size"),
|
|
||||||
() -> connectionManager.getTotalStats().getAvailable() + connectionManager.getTotalStats().getLeased());
|
|
||||||
metrics.registerGauge(MetricRegistry.name(getClass(), "pool", "leased"), () -> connectionManager.getTotalStats().getLeased());
|
|
||||||
metrics.registerGauge(MetricRegistry.name(getClass(), "pool", "pending"), () -> connectionManager.getTotalStats().getPending());
|
|
||||||
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "size"), () -> cache == null ? 0 : cache.size());
|
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "size"), () -> cache == null ? 0 : cache.size());
|
||||||
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "memoryUsage"),
|
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "memoryUsage"),
|
||||||
() -> cache == null ? 0 : cache.asMap().values().stream().mapToInt(e -> ArrayUtils.getLength(e.content)).sum());
|
() -> cache == null ? 0 : cache.asMap().values().stream().mapToInt(e -> ArrayUtils.getLength(e.content)).sum());
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResult get(String url)
|
public HttpResult get(String url) throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException, HostNotAllowedException {
|
|
||||||
return get(HttpRequest.builder(url).build());
|
return get(HttpRequest.builder(url).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResult get(HttpRequest request)
|
public HttpResult get(HttpRequest request)
|
||||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException, HostNotAllowedException {
|
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||||
URI uri = URI.create(request.getUrl());
|
URI uri = URI.create(request.getUrl());
|
||||||
ensureHttpScheme(uri.getScheme());
|
ensureHttpScheme(uri.getScheme());
|
||||||
|
|
||||||
if (config.httpClient().blockLocalAddresses()) {
|
|
||||||
ensurePublicAddress(uri.getHost());
|
|
||||||
}
|
|
||||||
|
|
||||||
final HttpResponse response;
|
final HttpResponse response;
|
||||||
if (cache == null) {
|
if (cache == null) {
|
||||||
response = invoke(request);
|
response = invoke(request);
|
||||||
@@ -164,22 +125,6 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensurePublicAddress(String host) throws HostNotAllowedException, UnknownHostException {
|
|
||||||
if (host == null) {
|
|
||||||
throw new HostNotAllowedException(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
InetAddress[] addresses = DNS_RESOLVER.resolve(host);
|
|
||||||
if (Stream.of(addresses).anyMatch(this::isPrivateAddress)) {
|
|
||||||
throw new HostNotAllowedException(host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPrivateAddress(InetAddress address) {
|
|
||||||
return address.isSiteLocalAddress() || address.isAnyLocalAddress() || address.isLinkLocalAddress() || address.isLoopbackAddress()
|
|
||||||
|| address.isMulticastAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpResponse invoke(HttpRequest request) throws IOException {
|
private HttpResponse invoke(HttpRequest request) throws IOException {
|
||||||
log.debug("fetching {}", request.getUrl());
|
log.debug("fetching {}", request.getUrl());
|
||||||
|
|
||||||
@@ -268,50 +213,6 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PoolingHttpClientConnectionManager newConnectionManager(CommaFeedConfiguration config) {
|
|
||||||
SSLFactory sslFactory = SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
|
||||||
|
|
||||||
int poolSize = config.feedRefresh().httpThreads();
|
|
||||||
return PoolingHttpClientConnectionManagerBuilder.create()
|
|
||||||
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
|
|
||||||
.setDefaultConnectionConfig(ConnectionConfig.custom()
|
|
||||||
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
|
|
||||||
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
|
|
||||||
.setTimeToLive(Timeout.of(config.httpClient().connectionTimeToLive()))
|
|
||||||
.build())
|
|
||||||
.setDefaultTlsConfig(TlsConfig.custom().setHandshakeTimeout(Timeout.of(config.httpClient().sslHandshakeTimeout())).build())
|
|
||||||
.setMaxConnPerRoute(poolSize)
|
|
||||||
.setMaxConnTotal(poolSize)
|
|
||||||
.setDnsResolver(DNS_RESOLVER)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CloseableHttpClient newClient(HttpClientConnectionManager connectionManager, String userAgent,
|
|
||||||
Duration idleConnectionsEvictionInterval) {
|
|
||||||
List<Header> headers = new ArrayList<>();
|
|
||||||
headers.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en"));
|
|
||||||
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
|
||||||
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
|
||||||
|
|
||||||
SequencedMap<String, InputStreamFactory> contentDecoderMap = new LinkedHashMap<>();
|
|
||||||
contentDecoderMap.put(ContentCoding.GZIP.token(), GZIPInputStream::new);
|
|
||||||
contentDecoderMap.put(ContentCoding.DEFLATE.token(), DeflateInputStream::new);
|
|
||||||
contentDecoderMap.put(ContentCoding.BROTLI.token(), BrotliInputStream::new);
|
|
||||||
|
|
||||||
return HttpClientBuilder.create()
|
|
||||||
.useSystemProperties()
|
|
||||||
.disableAutomaticRetries()
|
|
||||||
.disableCookieManagement()
|
|
||||||
.setUserAgent(userAgent)
|
|
||||||
.setDefaultHeaders(headers)
|
|
||||||
.setConnectionManager(connectionManager)
|
|
||||||
.evictExpiredConnections()
|
|
||||||
.evictIdleConnections(TimeValue.of(idleConnectionsEvictionInterval))
|
|
||||||
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cache<HttpRequest, HttpResponse> newCache(CommaFeedConfiguration config) {
|
private static Cache<HttpRequest, HttpResponse> newCache(CommaFeedConfiguration config) {
|
||||||
HttpClientCache cacheConfig = config.httpClient().cache();
|
HttpClientCache cacheConfig = config.httpClient().cache();
|
||||||
if (!cacheConfig.enabled()) {
|
if (!cacheConfig.enabled()) {
|
||||||
@@ -333,14 +234,6 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HostNotAllowedException extends Exception {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public HostNotAllowedException(String host) {
|
|
||||||
super("Host not allowed: " + host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class NotModifiedException extends Exception {
|
public static class NotModifiedException extends Exception {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ public class Urls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String removeTrailingSlash(String url) {
|
public static String removeTrailingSlash(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (url.endsWith("/")) {
|
if (url.endsWith("/")) {
|
||||||
url = url.substring(0, url.length() - 1);
|
url = url.substring(0, url.length() - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import org.apache.hc.core5.net.URIBuilder;
|
|||||||
|
|
||||||
import com.commafeed.CommaFeedConfiguration;
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
import com.commafeed.backend.HttpGetter;
|
import com.commafeed.backend.HttpGetter;
|
||||||
import com.commafeed.backend.HttpGetter.HostNotAllowedException;
|
|
||||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||||
import com.commafeed.backend.HttpGetter.SchemeNotAllowedException;
|
import com.commafeed.backend.HttpGetter.SchemeNotAllowedException;
|
||||||
@@ -98,7 +97,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] fetchForUser(String googleAuthKey, String userId)
|
private byte[] fetchForUser(String googleAuthKey, String userId)
|
||||||
throws IOException, NotModifiedException, TooManyRequestsException, HostNotAllowedException, SchemeNotAllowedException {
|
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||||
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
||||||
.queryParam("part", PART_SNIPPET)
|
.queryParam("part", PART_SNIPPET)
|
||||||
.queryParam("key", googleAuthKey)
|
.queryParam("key", googleAuthKey)
|
||||||
@@ -108,7 +107,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] fetchForChannel(String googleAuthKey, String channelId)
|
private byte[] fetchForChannel(String googleAuthKey, String channelId)
|
||||||
throws IOException, NotModifiedException, TooManyRequestsException, HostNotAllowedException, SchemeNotAllowedException {
|
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||||
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
||||||
.queryParam("part", PART_SNIPPET)
|
.queryParam("part", PART_SNIPPET)
|
||||||
.queryParam("key", googleAuthKey)
|
.queryParam("key", googleAuthKey)
|
||||||
@@ -118,7 +117,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
|
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
|
||||||
throws IOException, NotModifiedException, TooManyRequestsException, HostNotAllowedException, SchemeNotAllowedException {
|
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||||
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/playlists")
|
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/playlists")
|
||||||
.queryParam("part", PART_SNIPPET)
|
.queryParam("part", PART_SNIPPET)
|
||||||
.queryParam("key", googleAuthKey)
|
.queryParam("key", googleAuthKey)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import org.apache.commons.lang3.Strings;
|
|||||||
|
|
||||||
import com.commafeed.backend.Digests;
|
import com.commafeed.backend.Digests;
|
||||||
import com.commafeed.backend.HttpGetter;
|
import com.commafeed.backend.HttpGetter;
|
||||||
import com.commafeed.backend.HttpGetter.HostNotAllowedException;
|
|
||||||
import com.commafeed.backend.HttpGetter.HttpRequest;
|
import com.commafeed.backend.HttpGetter.HttpRequest;
|
||||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||||
@@ -46,7 +45,7 @@ public class FeedFetcher {
|
|||||||
|
|
||||||
public FeedFetcherResult fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag,
|
public FeedFetcherResult fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag,
|
||||||
Instant lastPublishedDate, String lastContentHash) throws FeedParsingException, IOException, NotModifiedException,
|
Instant lastPublishedDate, String lastContentHash) throws FeedParsingException, IOException, NotModifiedException,
|
||||||
TooManyRequestsException, SchemeNotAllowedException, HostNotAllowedException, NoFeedFoundException {
|
TooManyRequestsException, SchemeNotAllowedException, NoFeedFoundException {
|
||||||
log.debug("Fetching feed {}", feedUrl);
|
log.debug("Fetching feed {}", feedUrl);
|
||||||
|
|
||||||
HttpResult result = getter.get(HttpRequest.builder(feedUrl).lastModified(lastModified).eTag(eTag).build());
|
HttpResult result = getter.get(HttpRequest.builder(feedUrl).lastModified(lastModified).eTag(eTag).build());
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -21,6 +22,8 @@ import com.commafeed.backend.dao.FeedDAO;
|
|||||||
import com.commafeed.backend.dao.UnitOfWork;
|
import com.commafeed.backend.dao.UnitOfWork;
|
||||||
import com.commafeed.backend.model.AbstractModel;
|
import com.commafeed.backend.model.AbstractModel;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ public class FeedRefreshEngine {
|
|||||||
private final FeedDAO feedDAO;
|
private final FeedDAO feedDAO;
|
||||||
private final FeedRefreshWorker worker;
|
private final FeedRefreshWorker worker;
|
||||||
private final FeedRefreshUpdater updater;
|
private final FeedRefreshUpdater updater;
|
||||||
|
private final FeedUpdateNotifier notifier;
|
||||||
private final CommaFeedConfiguration config;
|
private final CommaFeedConfiguration config;
|
||||||
private final Meter refill;
|
private final Meter refill;
|
||||||
|
|
||||||
@@ -42,13 +46,15 @@ public class FeedRefreshEngine {
|
|||||||
private final ExecutorService refillExecutor;
|
private final ExecutorService refillExecutor;
|
||||||
private final ThreadPoolExecutor workerExecutor;
|
private final ThreadPoolExecutor workerExecutor;
|
||||||
private final ThreadPoolExecutor databaseUpdaterExecutor;
|
private final ThreadPoolExecutor databaseUpdaterExecutor;
|
||||||
|
private final ThreadPoolExecutor notifierExecutor;
|
||||||
|
|
||||||
public FeedRefreshEngine(UnitOfWork unitOfWork, FeedDAO feedDAO, FeedRefreshWorker worker, FeedRefreshUpdater updater,
|
public FeedRefreshEngine(UnitOfWork unitOfWork, FeedDAO feedDAO, FeedRefreshWorker worker, FeedRefreshUpdater updater,
|
||||||
CommaFeedConfiguration config, MetricRegistry metrics) {
|
FeedUpdateNotifier notifier, CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||||
this.unitOfWork = unitOfWork;
|
this.unitOfWork = unitOfWork;
|
||||||
this.feedDAO = feedDAO;
|
this.feedDAO = feedDAO;
|
||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
|
this.notifier = notifier;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
|
this.refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
|
||||||
|
|
||||||
@@ -59,10 +65,14 @@ public class FeedRefreshEngine {
|
|||||||
this.refillExecutor = newDiscardingSingleThreadExecutorService();
|
this.refillExecutor = newDiscardingSingleThreadExecutorService();
|
||||||
this.workerExecutor = newBlockingExecutorService(config.feedRefresh().httpThreads());
|
this.workerExecutor = newBlockingExecutorService(config.feedRefresh().httpThreads());
|
||||||
this.databaseUpdaterExecutor = newBlockingExecutorService(config.feedRefresh().databaseThreads());
|
this.databaseUpdaterExecutor = newBlockingExecutorService(config.feedRefresh().databaseThreads());
|
||||||
|
this.notifierExecutor = newDiscardingExecutorService(config.pushNotifications().threads(),
|
||||||
|
config.pushNotifications().queueCapacity());
|
||||||
|
|
||||||
metrics.register(MetricRegistry.name(getClass(), "queue", "size"), (Gauge<Integer>) queue::size);
|
metrics.register(MetricRegistry.name(getClass(), "queue", "size"), (Gauge<Integer>) queue::size);
|
||||||
metrics.register(MetricRegistry.name(getClass(), "worker", "active"), (Gauge<Integer>) workerExecutor::getActiveCount);
|
metrics.register(MetricRegistry.name(getClass(), "worker", "active"), (Gauge<Integer>) workerExecutor::getActiveCount);
|
||||||
metrics.register(MetricRegistry.name(getClass(), "updater", "active"), (Gauge<Integer>) databaseUpdaterExecutor::getActiveCount);
|
metrics.register(MetricRegistry.name(getClass(), "updater", "active"), (Gauge<Integer>) databaseUpdaterExecutor::getActiveCount);
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), "notifier", "active"), (Gauge<Integer>) notifierExecutor::getActiveCount);
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), "notifier", "queue"), (Gauge<Integer>) () -> notifierExecutor.getQueue().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
@@ -152,10 +162,19 @@ public class FeedRefreshEngine {
|
|||||||
private void processFeedAsync(Feed feed) {
|
private void processFeedAsync(Feed feed) {
|
||||||
CompletableFuture.supplyAsync(() -> worker.update(feed), workerExecutor)
|
CompletableFuture.supplyAsync(() -> worker.update(feed), workerExecutor)
|
||||||
.thenApplyAsync(r -> updater.update(r.feed(), r.entries()), databaseUpdaterExecutor)
|
.thenApplyAsync(r -> updater.update(r.feed(), r.entries()), databaseUpdaterExecutor)
|
||||||
.whenComplete((data, ex) -> {
|
.thenCompose(r -> {
|
||||||
if (ex != null) {
|
List<CompletableFuture<Void>> futures = r.insertedUnreadEntriesBySubscription().entrySet().stream().map(e -> {
|
||||||
log.error("error while processing feed {}", feed.getUrl(), ex);
|
FeedSubscription sub = e.getKey();
|
||||||
}
|
List<FeedEntry> entries = e.getValue();
|
||||||
|
|
||||||
|
notifier.notifyOverWebsocket(sub, entries);
|
||||||
|
return CompletableFuture.runAsync(() -> notifier.sendPushNotifications(sub, entries), notifierExecutor);
|
||||||
|
}).toList();
|
||||||
|
return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new));
|
||||||
|
})
|
||||||
|
.exceptionally(ex -> {
|
||||||
|
log.error("error while processing feed {}", feed.getUrl(), ex);
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +202,7 @@ public class FeedRefreshEngine {
|
|||||||
this.refillExecutor.shutdownNow();
|
this.refillExecutor.shutdownNow();
|
||||||
this.workerExecutor.shutdownNow();
|
this.workerExecutor.shutdownNow();
|
||||||
this.databaseUpdaterExecutor.shutdownNow();
|
this.databaseUpdaterExecutor.shutdownNow();
|
||||||
|
this.notifierExecutor.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,6 +214,16 @@ public class FeedRefreshEngine {
|
|||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns an ExecutorService that discards tasks if the queue is full
|
||||||
|
*/
|
||||||
|
private ThreadPoolExecutor newDiscardingExecutorService(int threads, int queueCapacity) {
|
||||||
|
ThreadPoolExecutor pool = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS,
|
||||||
|
new LinkedBlockingQueue<>(queueCapacity));
|
||||||
|
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns an ExecutorService that blocks submissions until a thread is available
|
* returns an ExecutorService that blocks submissions until a thread is available
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,19 +20,14 @@ import com.codahale.metrics.MetricRegistry;
|
|||||||
import com.commafeed.backend.Digests;
|
import com.commafeed.backend.Digests;
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
import com.commafeed.backend.dao.UnitOfWork;
|
import com.commafeed.backend.dao.UnitOfWork;
|
||||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
|
||||||
import com.commafeed.backend.feed.parser.FeedParserResult.Content;
|
import com.commafeed.backend.feed.parser.FeedParserResult.Content;
|
||||||
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
|
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.Models;
|
import com.commafeed.backend.model.Models;
|
||||||
import com.commafeed.backend.model.UserSettings;
|
|
||||||
import com.commafeed.backend.service.FeedEntryService;
|
import com.commafeed.backend.service.FeedEntryService;
|
||||||
import com.commafeed.backend.service.FeedService;
|
import com.commafeed.backend.service.FeedService;
|
||||||
import com.commafeed.backend.service.NotificationService;
|
|
||||||
import com.commafeed.frontend.ws.WebSocketMessageBuilder;
|
|
||||||
import com.commafeed.frontend.ws.WebSocketSessions;
|
|
||||||
import com.google.common.util.concurrent.Striped;
|
import com.google.common.util.concurrent.Striped;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -48,9 +43,6 @@ public class FeedRefreshUpdater {
|
|||||||
private final FeedService feedService;
|
private final FeedService feedService;
|
||||||
private final FeedEntryService feedEntryService;
|
private final FeedEntryService feedEntryService;
|
||||||
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
private final UserSettingsDAO userSettingsDAO;
|
|
||||||
private final WebSocketSessions webSocketSessions;
|
|
||||||
private final NotificationService notificationService;
|
|
||||||
|
|
||||||
private final Striped<Lock> locks;
|
private final Striped<Lock> locks;
|
||||||
|
|
||||||
@@ -58,15 +50,11 @@ public class FeedRefreshUpdater {
|
|||||||
private final Meter entryInserted;
|
private final Meter entryInserted;
|
||||||
|
|
||||||
public FeedRefreshUpdater(UnitOfWork unitOfWork, FeedService feedService, FeedEntryService feedEntryService, MetricRegistry metrics,
|
public FeedRefreshUpdater(UnitOfWork unitOfWork, FeedService feedService, FeedEntryService feedEntryService, MetricRegistry metrics,
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO, UserSettingsDAO userSettingsDAO, WebSocketSessions webSocketSessions,
|
FeedSubscriptionDAO feedSubscriptionDAO) {
|
||||||
NotificationService notificationService) {
|
|
||||||
this.unitOfWork = unitOfWork;
|
this.unitOfWork = unitOfWork;
|
||||||
this.feedService = feedService;
|
this.feedService = feedService;
|
||||||
this.feedEntryService = feedEntryService;
|
this.feedEntryService = feedEntryService;
|
||||||
this.feedSubscriptionDAO = feedSubscriptionDAO;
|
this.feedSubscriptionDAO = feedSubscriptionDAO;
|
||||||
this.userSettingsDAO = userSettingsDAO;
|
|
||||||
this.webSocketSessions = webSocketSessions;
|
|
||||||
this.notificationService = notificationService;
|
|
||||||
|
|
||||||
locks = Striped.lazyWeakLock(100000);
|
locks = Striped.lazyWeakLock(100000);
|
||||||
|
|
||||||
@@ -100,19 +88,20 @@ public class FeedRefreshUpdater {
|
|||||||
if (locked1 && locked2) {
|
if (locked1 && locked2) {
|
||||||
processed = true;
|
processed = true;
|
||||||
insertedEntry = unitOfWork.call(() -> {
|
insertedEntry = unitOfWork.call(() -> {
|
||||||
FeedEntry feedEntry = feedEntryService.find(feed, entry);
|
if (feedEntryService.find(feed, entry) != null) {
|
||||||
if (feedEntry == null) {
|
// entry already exists, nothing to do
|
||||||
feedEntry = feedEntryService.create(feed, entry);
|
return null;
|
||||||
entryInserted.mark();
|
|
||||||
for (FeedSubscription sub : subscriptions) {
|
|
||||||
boolean unread = feedEntryService.applyFilter(sub, feedEntry);
|
|
||||||
if (unread) {
|
|
||||||
subscriptionsForWhichEntryIsUnread.add(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return feedEntry;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
FeedEntry feedEntry = feedEntryService.create(feed, entry);
|
||||||
|
entryInserted.mark();
|
||||||
|
for (FeedSubscription sub : subscriptions) {
|
||||||
|
boolean unread = feedEntryService.applyFilter(sub, feedEntry);
|
||||||
|
if (unread) {
|
||||||
|
subscriptionsForWhichEntryIsUnread.add(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return feedEntry;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.error("lock timeout for {} - {}", feed.getUrl(), key1);
|
log.error("lock timeout for {} - {}", feed.getUrl(), key1);
|
||||||
@@ -131,11 +120,10 @@ public class FeedRefreshUpdater {
|
|||||||
return new AddEntryResult(processed, insertedEntry, subscriptionsForWhichEntryIsUnread);
|
return new AddEntryResult(processed, insertedEntry, subscriptionsForWhichEntryIsUnread);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean update(Feed feed, List<Entry> entries) {
|
public FeedRefreshUpdaterResult update(Feed feed, List<Entry> entries) {
|
||||||
boolean processed = true;
|
boolean processed = true;
|
||||||
long inserted = 0;
|
long inserted = 0;
|
||||||
Map<FeedSubscription, Long> unreadCountBySubscription = new HashMap<>();
|
Map<FeedSubscription, List<FeedEntry>> insertedUnreadEntriesBySubscription = new HashMap<>();
|
||||||
Map<FeedSubscription, List<FeedEntry>> insertedEntriesBySubscription = new HashMap<>();
|
|
||||||
|
|
||||||
if (!entries.isEmpty()) {
|
if (!entries.isEmpty()) {
|
||||||
List<FeedSubscription> subscriptions = null;
|
List<FeedSubscription> subscriptions = null;
|
||||||
@@ -147,9 +135,8 @@ public class FeedRefreshUpdater {
|
|||||||
processed &= addEntryResult.processed;
|
processed &= addEntryResult.processed;
|
||||||
inserted += addEntryResult.insertedEntry != null ? 1 : 0;
|
inserted += addEntryResult.insertedEntry != null ? 1 : 0;
|
||||||
addEntryResult.subscriptionsForWhichEntryIsUnread.forEach(sub -> {
|
addEntryResult.subscriptionsForWhichEntryIsUnread.forEach(sub -> {
|
||||||
unreadCountBySubscription.merge(sub, 1L, Long::sum);
|
|
||||||
if (addEntryResult.insertedEntry != null) {
|
if (addEntryResult.insertedEntry != null) {
|
||||||
insertedEntriesBySubscription.computeIfAbsent(sub, k -> new ArrayList<>()).add(addEntryResult.insertedEntry);
|
insertedUnreadEntriesBySubscription.computeIfAbsent(sub, k -> new ArrayList<>()).add(addEntryResult.insertedEntry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -172,36 +159,13 @@ public class FeedRefreshUpdater {
|
|||||||
|
|
||||||
unitOfWork.run(() -> feedService.update(feed));
|
unitOfWork.run(() -> feedService.update(feed));
|
||||||
|
|
||||||
notifyOverWebsocket(unreadCountBySubscription);
|
return new FeedRefreshUpdaterResult(insertedUnreadEntriesBySubscription);
|
||||||
sendNotifications(insertedEntriesBySubscription);
|
|
||||||
|
|
||||||
return processed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyOverWebsocket(Map<FeedSubscription, Long> unreadCountBySubscription) {
|
|
||||||
unreadCountBySubscription.forEach((sub, unreadCount) -> webSocketSessions.sendMessage(sub.getUser(),
|
|
||||||
WebSocketMessageBuilder.newFeedEntries(sub, unreadCount)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendNotifications(Map<FeedSubscription, List<FeedEntry>> insertedEntriesBySubscription) {
|
|
||||||
insertedEntriesBySubscription.forEach((sub, feedEntries) -> {
|
|
||||||
if (!sub.isNotifyOnNewEntries()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
UserSettings settings = unitOfWork.call(() -> userSettingsDAO.findByUser(sub.getUser()));
|
|
||||||
if (settings != null && settings.isNotificationEnabled()) {
|
|
||||||
for (FeedEntry feedEntry : feedEntries) {
|
|
||||||
notificationService.notify(settings, sub, feedEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("error sending push notification for subscription {}", sub.getId(), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private record AddEntryResult(boolean processed, FeedEntry insertedEntry, Set<FeedSubscription> subscriptionsForWhichEntryIsUnread) {
|
private record AddEntryResult(boolean processed, FeedEntry insertedEntry, Set<FeedSubscription> subscriptionsForWhichEntryIsUnread) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record FeedRefreshUpdaterResult(Map<FeedSubscription, List<FeedEntry>> insertedUnreadEntriesBySubscription) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.commafeed.backend.feed;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
|
||||||
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
|
import com.commafeed.backend.dao.UnitOfWork;
|
||||||
|
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.commafeed.backend.model.UserSettings;
|
||||||
|
import com.commafeed.backend.service.PushNotificationService;
|
||||||
|
import com.commafeed.frontend.ws.WebSocketMessageBuilder;
|
||||||
|
import com.commafeed.frontend.ws.WebSocketSessions;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Singleton
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FeedUpdateNotifier {
|
||||||
|
|
||||||
|
private final CommaFeedConfiguration config;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
private final UserSettingsDAO userSettingsDAO;
|
||||||
|
private final WebSocketSessions webSocketSessions;
|
||||||
|
private final PushNotificationService pushNotificationService;
|
||||||
|
|
||||||
|
public void notifyOverWebsocket(FeedSubscription sub, List<FeedEntry> entries) {
|
||||||
|
if (!entries.isEmpty()) {
|
||||||
|
webSocketSessions.sendMessage(sub.getUser(), WebSocketMessageBuilder.newFeedEntries(sub, entries.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPushNotifications(FeedSubscription sub, List<FeedEntry> entries) {
|
||||||
|
if (!config.pushNotifications().enabled() || !sub.isPushNotificationsEnabled() || entries.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSettings settings = unitOfWork.call(() -> userSettingsDAO.findByUser(sub.getUser()));
|
||||||
|
if (settings != null && settings.getPushNotificationType() != null) {
|
||||||
|
for (FeedEntry entry : entries) {
|
||||||
|
pushNotificationService.notify(settings, sub, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ public class FeedSubscription extends AbstractModel {
|
|||||||
@Column(name = "filtering_expression_legacy", length = 4096)
|
@Column(name = "filtering_expression_legacy", length = 4096)
|
||||||
private String filterLegacy;
|
private String filterLegacy;
|
||||||
|
|
||||||
@Column(name = "notify_on_new_entries", length = 4096)
|
@Column(name = "push_notifications_enabled")
|
||||||
private boolean notifyOnNewEntries = true;
|
private boolean pushNotificationsEnabled;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class UserSettings extends AbstractModel {
|
|||||||
ON_MOBILE
|
ON_MOBILE
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NotificationType {
|
public enum PushNotificationType {
|
||||||
@JsonProperty("ntfy")
|
@JsonProperty("ntfy")
|
||||||
NTFY,
|
NTFY,
|
||||||
|
|
||||||
@@ -144,23 +144,21 @@ public class UserSettings extends AbstractModel {
|
|||||||
private boolean unreadCountFavicon;
|
private boolean unreadCountFavicon;
|
||||||
private boolean disablePullToRefresh;
|
private boolean disablePullToRefresh;
|
||||||
|
|
||||||
private boolean notificationEnabled;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(name = "notification_type", length = 16)
|
@Column(name = "push_notification_type", length = 16)
|
||||||
private NotificationType notificationType;
|
private PushNotificationType pushNotificationType;
|
||||||
|
|
||||||
@Column(name = "notification_server_url", length = 1024)
|
@Column(name = "push_notification_server_url", length = 1024)
|
||||||
private String notificationServerUrl;
|
private String pushNotificationServerUrl;
|
||||||
|
|
||||||
@Column(name = "notification_token", length = 512)
|
@Column(name = "push_notification_user_id", length = 512)
|
||||||
private String notificationToken;
|
private String pushNotificationUserId;
|
||||||
|
|
||||||
@Column(name = "notification_user_key", length = 512)
|
@Column(name = "push_notification_user_secret", length = 512)
|
||||||
private String notificationUserKey;
|
private String pushNotificationUserSecret;
|
||||||
|
|
||||||
@Column(name = "notification_topic", length = 256)
|
@Column(name = "push_notification_topic", length = 256)
|
||||||
private String notificationTopic;
|
private String pushNotificationTopic;
|
||||||
|
|
||||||
private boolean email;
|
private boolean email;
|
||||||
private boolean gmail;
|
private boolean gmail;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class OPMLImporter {
|
|||||||
}
|
}
|
||||||
// make sure we continue with the import process even if a feed failed
|
// make sure we continue with the import process even if a feed failed
|
||||||
try {
|
try {
|
||||||
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent, position, true);
|
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent, position, false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("error while importing {}: {}", outline.getXmlUrl(), e.getMessage());
|
log.error("error while importing {}: {}", outline.getXmlUrl(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class FeedSubscriptionService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public long subscribe(User user, String url, String title, FeedCategory category, int position, boolean notifyOnNewEntries) {
|
public long subscribe(User user, String url, String title, FeedCategory category, int position, boolean pushNotificationsEnabled) {
|
||||||
Integer maxFeedsPerUser = config.database().cleanup().maxFeedsPerUser();
|
Integer maxFeedsPerUser = config.database().cleanup().maxFeedsPerUser();
|
||||||
if (maxFeedsPerUser > 0 && feedSubscriptionDAO.count(user) >= maxFeedsPerUser) {
|
if (maxFeedsPerUser > 0 && feedSubscriptionDAO.count(user) >= maxFeedsPerUser) {
|
||||||
String message = String.format("You cannot subscribe to more feeds on this CommaFeed instance (max %s feeds per user)",
|
String message = String.format("You cannot subscribe to more feeds on this CommaFeed instance (max %s feeds per user)",
|
||||||
@@ -73,7 +73,7 @@ public class FeedSubscriptionService {
|
|||||||
sub.setCategory(category);
|
sub.setCategory(category);
|
||||||
sub.setPosition(position);
|
sub.setPosition(position);
|
||||||
sub.setTitle(FeedUtils.truncate(title, 128));
|
sub.setTitle(FeedUtils.truncate(title, 128));
|
||||||
sub.setNotifyOnNewEntries(notifyOnNewEntries);
|
sub.setPushNotificationsEnabled(pushNotificationsEnabled);
|
||||||
return feedSubscriptionDAO.merge(sub).getId();
|
return feedSubscriptionDAO.merge(sub).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
package com.commafeed.backend.service;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpRequest.BodyPublishers;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
|
||||||
import com.commafeed.backend.model.UserSettings;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Slf4j
|
|
||||||
public class NotificationService {
|
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
|
|
||||||
public NotificationService() {
|
|
||||||
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NotificationService(HttpClient httpClient) {
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void notify(UserSettings settings, FeedSubscription subscription, FeedEntry entry) {
|
|
||||||
if (!settings.isNotificationEnabled() || settings.getNotificationType() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String entryTitle = entry.getContent() != null ? entry.getContent().getTitle() : null;
|
|
||||||
String entryUrl = entry.getUrl();
|
|
||||||
String feedTitle = subscription.getTitle();
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(entryTitle)) {
|
|
||||||
entryTitle = "New entry";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (settings.getNotificationType()) {
|
|
||||||
case NTFY -> sendNtfy(settings, feedTitle, entryTitle, entryUrl);
|
|
||||||
case GOTIFY -> sendGotify(settings, feedTitle, entryTitle, entryUrl);
|
|
||||||
case PUSHOVER -> sendPushover(settings, feedTitle, entryTitle, entryUrl);
|
|
||||||
default -> log.warn("unknown notification type: {}", settings.getNotificationType());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("failed to send {} notification for entry '{}' in feed '{}'", settings.getNotificationType(), entryTitle, feedTitle,
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendNtfy(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws Exception {
|
|
||||||
String serverUrl = stripTrailingSlash(settings.getNotificationServerUrl());
|
|
||||||
String topic = settings.getNotificationTopic();
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(topic)) {
|
|
||||||
log.warn("ntfy notification skipped: missing server URL or topic");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(serverUrl + "/" + topic))
|
|
||||||
.timeout(Duration.ofSeconds(10))
|
|
||||||
.header("Title", feedTitle + ": " + entryTitle)
|
|
||||||
.POST(BodyPublishers.ofString(entryTitle));
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(entryUrl)) {
|
|
||||||
builder.header("Click", entryUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(settings.getNotificationToken())) {
|
|
||||||
builder.header("Authorization", "Bearer " + settings.getNotificationToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (response.statusCode() >= 400) {
|
|
||||||
log.error("ntfy notification failed with status {}: {}", response.statusCode(), response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendGotify(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws Exception {
|
|
||||||
String serverUrl = stripTrailingSlash(settings.getNotificationServerUrl());
|
|
||||||
String token = settings.getNotificationToken();
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(token)) {
|
|
||||||
log.warn("gotify notification skipped: missing server URL or token");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String message = entryTitle;
|
|
||||||
if (StringUtils.isNotBlank(entryUrl)) {
|
|
||||||
message += "\n" + entryUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
String json = """
|
|
||||||
{"title":"%s","message":"%s","priority":5,"extras":{"client::notification":{"click":{"url":"%s"}}}}"""
|
|
||||||
.formatted(escapeJson(feedTitle), escapeJson(message), escapeJson(StringUtils.defaultString(entryUrl)));
|
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(serverUrl + "/message"))
|
|
||||||
.timeout(Duration.ofSeconds(10))
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("X-Gotify-Key", token)
|
|
||||||
.POST(BodyPublishers.ofString(json))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (response.statusCode() >= 400) {
|
|
||||||
log.error("gotify notification failed with status {}: {}", response.statusCode(), response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPushover(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws Exception {
|
|
||||||
String token = settings.getNotificationToken();
|
|
||||||
String userKey = settings.getNotificationUserKey();
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(token) || StringUtils.isBlank(userKey)) {
|
|
||||||
log.warn("pushover notification skipped: missing token or user key");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder body = new StringBuilder();
|
|
||||||
body.append("token=").append(urlEncode(token));
|
|
||||||
body.append("&user=").append(urlEncode(userKey));
|
|
||||||
body.append("&title=").append(urlEncode(feedTitle));
|
|
||||||
body.append("&message=").append(urlEncode(entryTitle));
|
|
||||||
if (StringUtils.isNotBlank(entryUrl)) {
|
|
||||||
body.append("&url=").append(urlEncode(entryUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create("https://api.pushover.net/1/messages.json"))
|
|
||||||
.timeout(Duration.ofSeconds(10))
|
|
||||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.POST(BodyPublishers.ofString(body.toString()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (response.statusCode() >= 400) {
|
|
||||||
log.error("pushover notification failed with status {}: {}", response.statusCode(), response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String stripTrailingSlash(String url) {
|
|
||||||
if (url != null && url.endsWith("/")) {
|
|
||||||
return url.substring(0, url.length() - 1);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String urlEncode(String value) {
|
|
||||||
return URLEncoder.encode(value, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String escapeJson(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package com.commafeed.backend.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||||
|
import org.apache.hc.client5.http.config.RequestConfig;
|
||||||
|
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||||
|
import org.apache.hc.core5.http.ContentType;
|
||||||
|
import org.apache.hc.core5.http.NameValuePair;
|
||||||
|
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||||
|
import org.apache.hc.core5.http.message.BasicNameValuePair;
|
||||||
|
import org.apache.hc.core5.util.Timeout;
|
||||||
|
|
||||||
|
import com.codahale.metrics.Meter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
|
import com.commafeed.backend.HttpClientFactory;
|
||||||
|
import com.commafeed.backend.Urls;
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.commafeed.backend.model.UserSettings;
|
||||||
|
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Slf4j
|
||||||
|
public class PushNotificationService {
|
||||||
|
|
||||||
|
private final CloseableHttpClient httpClient;
|
||||||
|
private final Meter meter;
|
||||||
|
private final CommaFeedConfiguration config;
|
||||||
|
|
||||||
|
public PushNotificationService(HttpClientFactory httpClientFactory, MetricRegistry metrics, CommaFeedConfiguration config) {
|
||||||
|
this.httpClient = httpClientFactory.newClient(config.pushNotifications().threads());
|
||||||
|
this.meter = metrics.meter(MetricRegistry.name(getClass(), "notify"));
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notify(UserSettings settings, FeedSubscription subscription, FeedEntry entry) {
|
||||||
|
if (!config.pushNotifications().enabled() || settings.getPushNotificationType() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("sending {} push notification for entry {} in feed {}", settings.getPushNotificationType(), entry.getId(),
|
||||||
|
subscription.getFeed().getId());
|
||||||
|
String entryTitle = entry.getContent() != null ? entry.getContent().getTitle() : null;
|
||||||
|
String entryUrl = entry.getUrl();
|
||||||
|
String feedTitle = subscription.getTitle();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(entryTitle)) {
|
||||||
|
entryTitle = "New entry";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (settings.getPushNotificationType()) {
|
||||||
|
case NTFY -> sendNtfy(settings, feedTitle, entryTitle, entryUrl);
|
||||||
|
case GOTIFY -> sendGotify(settings, feedTitle, entryTitle, entryUrl);
|
||||||
|
case PUSHOVER -> sendPushover(settings, feedTitle, entryTitle, entryUrl);
|
||||||
|
default -> throw new IllegalStateException("unsupported notification type: " + settings.getPushNotificationType());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PushNotificationException("Failed to send external notification", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
meter.mark();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNtfy(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
|
||||||
|
String serverUrl = Urls.removeTrailingSlash(settings.getPushNotificationServerUrl());
|
||||||
|
String topic = settings.getPushNotificationTopic();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(topic)) {
|
||||||
|
log.warn("ntfy notification skipped: missing server URL or topic");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpPost request = new HttpPost(serverUrl + "/" + topic);
|
||||||
|
request.setConfig(RequestConfig.custom().setResponseTimeout(Timeout.of(config.httpClient().responseTimeout())).build());
|
||||||
|
request.addHeader("Title", feedTitle);
|
||||||
|
request.setEntity(new StringEntity(entryTitle, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(entryUrl)) {
|
||||||
|
request.addHeader("Click", entryUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(settings.getPushNotificationUserSecret())) {
|
||||||
|
request.addHeader("Authorization", "Bearer " + settings.getPushNotificationUserSecret());
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.execute(request, response -> {
|
||||||
|
if (response.getCode() >= 400) {
|
||||||
|
throw new PushNotificationException("ntfy notification failed with status " + response.getCode());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendGotify(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
|
||||||
|
String serverUrl = Urls.removeTrailingSlash(settings.getPushNotificationServerUrl());
|
||||||
|
String token = settings.getPushNotificationUserSecret();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(token)) {
|
||||||
|
log.warn("gotify notification skipped: missing server URL or token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject json = new JsonObject();
|
||||||
|
json.put("title", feedTitle);
|
||||||
|
json.put("message", entryTitle);
|
||||||
|
json.put("priority", 5);
|
||||||
|
if (StringUtils.isNotBlank(entryUrl)) {
|
||||||
|
json.put("extras",
|
||||||
|
new JsonObject().put("client::notification", new JsonObject().put("click", new JsonObject().put("url", entryUrl))));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpPost request = new HttpPost(serverUrl + "/message");
|
||||||
|
request.setConfig(RequestConfig.custom().setResponseTimeout(Timeout.of(config.httpClient().responseTimeout())).build());
|
||||||
|
request.addHeader("X-Gotify-Key", token);
|
||||||
|
request.setEntity(new StringEntity(json.toString(), ContentType.APPLICATION_JSON));
|
||||||
|
|
||||||
|
httpClient.execute(request, response -> {
|
||||||
|
if (response.getCode() >= 400) {
|
||||||
|
throw new PushNotificationException("gotify notification failed with status " + response.getCode());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPushover(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
|
||||||
|
String token = settings.getPushNotificationUserSecret();
|
||||||
|
String userKey = settings.getPushNotificationUserId();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(token) || StringUtils.isBlank(userKey)) {
|
||||||
|
log.warn("pushover notification skipped: missing token or user key");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NameValuePair> params = new ArrayList<>();
|
||||||
|
params.add(new BasicNameValuePair("token", token));
|
||||||
|
params.add(new BasicNameValuePair("user", userKey));
|
||||||
|
params.add(new BasicNameValuePair("title", feedTitle));
|
||||||
|
params.add(new BasicNameValuePair("message", entryTitle));
|
||||||
|
if (StringUtils.isNotBlank(entryUrl)) {
|
||||||
|
params.add(new BasicNameValuePair("url", entryUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpPost request = new HttpPost("https://api.pushover.net/1/messages.json");
|
||||||
|
request.setConfig(RequestConfig.custom().setResponseTimeout(Timeout.of(config.httpClient().responseTimeout())).build());
|
||||||
|
request.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
httpClient.execute(request, response -> {
|
||||||
|
if (response.getCode() >= 400) {
|
||||||
|
throw new PushNotificationException("pushover notification failed with status " + response.getCode());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PushNotificationException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = -3392881821584833819L;
|
||||||
|
|
||||||
|
public PushNotificationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PushNotificationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,4 +52,7 @@ public class ServerInfo implements Serializable {
|
|||||||
@Schema(required = true)
|
@Schema(required = true)
|
||||||
private int minimumPasswordLength;
|
private int minimumPasswordLength;
|
||||||
|
|
||||||
|
@Schema(required = true)
|
||||||
|
private boolean pushNotificationsEnabled;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.io.Serializable;
|
|||||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||||
|
|
||||||
import com.commafeed.backend.model.UserSettings.IconDisplayMode;
|
import com.commafeed.backend.model.UserSettings.IconDisplayMode;
|
||||||
|
import com.commafeed.backend.model.UserSettings.PushNotificationType;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||||
import com.commafeed.backend.model.UserSettings.ScrollMode;
|
import com.commafeed.backend.model.UserSettings.ScrollMode;
|
||||||
@@ -81,28 +82,25 @@ public class Settings implements Serializable {
|
|||||||
@Schema(description = "sharing settings", required = true)
|
@Schema(description = "sharing settings", required = true)
|
||||||
private SharingSettings sharingSettings = new SharingSettings();
|
private SharingSettings sharingSettings = new SharingSettings();
|
||||||
|
|
||||||
@Schema(description = "notification settings", required = true)
|
@Schema(description = "push notification settings", required = true)
|
||||||
private NotificationSettings notificationSettings = new NotificationSettings();
|
private PushNotificationSettings pushNotificationSettings = new PushNotificationSettings();
|
||||||
|
|
||||||
@Schema(description = "User notification settings")
|
@Schema(description = "User notification settings")
|
||||||
@Data
|
@Data
|
||||||
public static class NotificationSettings implements Serializable {
|
public static class PushNotificationSettings implements Serializable {
|
||||||
@Schema(required = true)
|
@Schema(description = "notification provider type")
|
||||||
private boolean enabled;
|
private PushNotificationType type;
|
||||||
|
|
||||||
@Schema(description = "notification provider type: ntfy, gotify, or pushover")
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
@Schema(description = "server URL for ntfy or gotify")
|
@Schema(description = "server URL for ntfy or gotify")
|
||||||
private String serverUrl;
|
private String serverUrl;
|
||||||
|
|
||||||
@Schema(description = "API token for gotify or pushover")
|
@Schema(description = "user Id")
|
||||||
private String token;
|
private String userId;
|
||||||
|
|
||||||
@Schema(description = "user key for pushover")
|
@Schema(description = "user secret for authentication with the service")
|
||||||
private String userKey;
|
private String userSecret;
|
||||||
|
|
||||||
@Schema(description = "topic for ntfy")
|
@Schema(description = "topic")
|
||||||
private String topic;
|
private String topic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ public class Subscription implements Serializable {
|
|||||||
@Schema(description = "JEXL legacy filter")
|
@Schema(description = "JEXL legacy filter")
|
||||||
private String filterLegacy;
|
private String filterLegacy;
|
||||||
|
|
||||||
@Schema(description = "whether to send notifications for new entries of this feed", required = true)
|
@Schema(description = "whether to send push notifications for new entries of this feed", required = true)
|
||||||
private boolean notifyOnNewEntries;
|
private boolean pushNotificationsEnabled;
|
||||||
|
|
||||||
public static Subscription build(FeedSubscription subscription, UnreadCount unreadCount) {
|
public static Subscription build(FeedSubscription subscription, UnreadCount unreadCount) {
|
||||||
FeedCategory category = subscription.getCategory();
|
FeedCategory category = subscription.getCategory();
|
||||||
@@ -88,7 +88,7 @@ public class Subscription implements Serializable {
|
|||||||
sub.setCategoryId(category == null ? null : String.valueOf(category.getId()));
|
sub.setCategoryId(category == null ? null : String.valueOf(category.getId()));
|
||||||
sub.setFilter(subscription.getFilter());
|
sub.setFilter(subscription.getFilter());
|
||||||
sub.setFilterLegacy(subscription.getFilterLegacy());
|
sub.setFilterLegacy(subscription.getFilterLegacy());
|
||||||
sub.setNotifyOnNewEntries(subscription.isNotifyOnNewEntries());
|
sub.setPushNotificationsEnabled(subscription.isPushNotificationsEnabled());
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class FeedModificationRequest implements Serializable {
|
|||||||
@Size(max = 4096)
|
@Size(max = 4096)
|
||||||
private String filter;
|
private String filter;
|
||||||
|
|
||||||
@Schema(description = "whether to send notifications for new entries of this feed")
|
@Schema(description = "whether to send push notifications for new entries of this feed")
|
||||||
private Boolean notifyOnNewEntries;
|
private boolean pushNotificationsEnabled;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class SubscribeRequest implements Serializable {
|
|||||||
@Size(max = 128)
|
@Size(max = 128)
|
||||||
private String categoryId;
|
private String categoryId;
|
||||||
|
|
||||||
@Schema(description = "whether to send notifications for new entries of this feed")
|
@Schema(description = "whether to send push notifications for new entries of this feed")
|
||||||
private boolean notifyOnNewEntries = true;
|
private boolean pushNotificationsEnabled;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ public class FeedREST {
|
|||||||
FeedInfo info = fetchFeedInternal(prependHttp(req.getUrl()));
|
FeedInfo info = fetchFeedInternal(prependHttp(req.getUrl()));
|
||||||
User user = authenticationContext.getCurrentUser();
|
User user = authenticationContext.getCurrentUser();
|
||||||
long subscriptionId = feedSubscriptionService.subscribe(user, info.getUrl(), req.getTitle(), category, 0,
|
long subscriptionId = feedSubscriptionService.subscribe(user, info.getUrl(), req.getTitle(), category, 0,
|
||||||
req.isNotifyOnNewEntries());
|
req.isPushNotificationsEnabled());
|
||||||
return Response.ok(subscriptionId).build();
|
return Response.ok(subscriptionId).build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to subscribe to URL {}: {}", req.getUrl(), e.getMessage(), e);
|
log.error("Failed to subscribe to URL {}: {}", req.getUrl(), e.getMessage(), e);
|
||||||
@@ -385,7 +385,7 @@ public class FeedREST {
|
|||||||
Preconditions.checkNotNull(url);
|
Preconditions.checkNotNull(url);
|
||||||
FeedInfo info = fetchFeedInternal(prependHttp(url));
|
FeedInfo info = fetchFeedInternal(prependHttp(url));
|
||||||
User user = authenticationContext.getCurrentUser();
|
User user = authenticationContext.getCurrentUser();
|
||||||
feedSubscriptionService.subscribe(user, info.getUrl(), info.getTitle(), null, 0, true);
|
feedSubscriptionService.subscribe(user, info.getUrl(), info.getTitle(), null, 0, false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("Could not subscribe to url {} : {}", url, e.getMessage());
|
log.info("Could not subscribe to url {} : {}", url, e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -439,9 +439,7 @@ public class FeedREST {
|
|||||||
subscription.setFilterLegacy(null);
|
subscription.setFilterLegacy(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.getNotifyOnNewEntries() != null) {
|
subscription.setPushNotificationsEnabled(req.isPushNotificationsEnabled());
|
||||||
subscription.setNotifyOnNewEntries(req.getNotifyOnNewEntries());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(req.getName())) {
|
if (StringUtils.isNotBlank(req.getName())) {
|
||||||
subscription.setTitle(req.getName());
|
subscription.setTitle(req.getName());
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public class ServerREST {
|
|||||||
infos.setForceRefreshCooldownDuration(config.feedRefresh().forceRefreshCooldownDuration().toMillis());
|
infos.setForceRefreshCooldownDuration(config.feedRefresh().forceRefreshCooldownDuration().toMillis());
|
||||||
infos.setInitialSetupRequired(databaseStartupService.isInitialSetupRequired());
|
infos.setInitialSetupRequired(databaseStartupService.isInitialSetupRequired());
|
||||||
infos.setMinimumPasswordLength(config.users().minimumPasswordLength());
|
infos.setMinimumPasswordLength(config.users().minimumPasswordLength());
|
||||||
|
infos.setPushNotificationsEnabled(config.pushNotifications().enabled());
|
||||||
return infos;
|
return infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import com.commafeed.backend.model.UserRole;
|
|||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
import com.commafeed.backend.model.UserSettings;
|
import com.commafeed.backend.model.UserSettings;
|
||||||
import com.commafeed.backend.model.UserSettings.IconDisplayMode;
|
import com.commafeed.backend.model.UserSettings.IconDisplayMode;
|
||||||
import com.commafeed.backend.model.UserSettings.NotificationType;
|
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||||
import com.commafeed.backend.model.UserSettings.ScrollMode;
|
import com.commafeed.backend.model.UserSettings.ScrollMode;
|
||||||
@@ -127,14 +126,11 @@ public class UserREST {
|
|||||||
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
|
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
|
||||||
s.setPrimaryColor(settings.getPrimaryColor());
|
s.setPrimaryColor(settings.getPrimaryColor());
|
||||||
|
|
||||||
s.getNotificationSettings().setEnabled(settings.isNotificationEnabled());
|
s.getPushNotificationSettings().setType(settings.getPushNotificationType());
|
||||||
if (settings.getNotificationType() != null) {
|
s.getPushNotificationSettings().setServerUrl(settings.getPushNotificationServerUrl());
|
||||||
s.getNotificationSettings().setType(settings.getNotificationType().name().toLowerCase());
|
s.getPushNotificationSettings().setUserId(settings.getPushNotificationUserId());
|
||||||
}
|
s.getPushNotificationSettings().setUserSecret(settings.getPushNotificationUserSecret());
|
||||||
s.getNotificationSettings().setServerUrl(settings.getNotificationServerUrl());
|
s.getPushNotificationSettings().setTopic(settings.getPushNotificationTopic());
|
||||||
s.getNotificationSettings().setToken(settings.getNotificationToken());
|
|
||||||
s.getNotificationSettings().setUserKey(settings.getNotificationUserKey());
|
|
||||||
s.getNotificationSettings().setTopic(settings.getNotificationTopic());
|
|
||||||
} else {
|
} else {
|
||||||
s.setReadingMode(ReadingMode.UNREAD);
|
s.setReadingMode(ReadingMode.UNREAD);
|
||||||
s.setReadingOrder(ReadingOrder.DESC);
|
s.setReadingOrder(ReadingOrder.DESC);
|
||||||
@@ -200,16 +196,11 @@ public class UserREST {
|
|||||||
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
|
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
|
||||||
s.setPrimaryColor(settings.getPrimaryColor());
|
s.setPrimaryColor(settings.getPrimaryColor());
|
||||||
|
|
||||||
s.setNotificationEnabled(settings.getNotificationSettings().isEnabled());
|
s.setPushNotificationType(settings.getPushNotificationSettings().getType());
|
||||||
if (settings.getNotificationSettings().getType() != null) {
|
s.setPushNotificationServerUrl(settings.getPushNotificationSettings().getServerUrl());
|
||||||
s.setNotificationType(NotificationType.valueOf(settings.getNotificationSettings().getType().toUpperCase()));
|
s.setPushNotificationUserId(settings.getPushNotificationSettings().getUserId());
|
||||||
} else {
|
s.setPushNotificationUserSecret(settings.getPushNotificationSettings().getUserSecret());
|
||||||
s.setNotificationType(null);
|
s.setPushNotificationTopic(settings.getPushNotificationSettings().getTopic());
|
||||||
}
|
|
||||||
s.setNotificationServerUrl(settings.getNotificationSettings().getServerUrl());
|
|
||||||
s.setNotificationToken(settings.getNotificationSettings().getToken());
|
|
||||||
s.setNotificationUserKey(settings.getNotificationSettings().getUserKey());
|
|
||||||
s.setNotificationTopic(settings.getNotificationSettings().getTopic());
|
|
||||||
|
|
||||||
s.setEmail(settings.getSharingSettings().isEmail());
|
s.setEmail(settings.getSharingSettings().isEmail());
|
||||||
s.setGmail(settings.getSharingSettings().isGmail());
|
s.setGmail(settings.getSharingSettings().isGmail());
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?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 https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
|
||||||
|
|
||||||
<changeSet id="add-subscription-notify-on-new-entries" author="commafeed">
|
|
||||||
<addColumn tableName="FEEDSUBSCRIPTIONS">
|
|
||||||
<column name="notify_on_new_entries" type="BOOLEAN" valueBoolean="true">
|
|
||||||
<constraints nullable="false" />
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
<changeSet id="add-notification-settings" author="commafeed">
|
|
||||||
<addColumn tableName="USERSETTINGS">
|
|
||||||
<column name="notificationEnabled" type="BOOLEAN" valueBoolean="false">
|
|
||||||
<constraints nullable="false" />
|
|
||||||
</column>
|
|
||||||
<column name="notification_type" type="VARCHAR(16)" />
|
|
||||||
<column name="notification_server_url" type="VARCHAR(1024)" />
|
|
||||||
<column name="notification_token" type="VARCHAR(512)" />
|
|
||||||
<column name="notification_user_key" type="VARCHAR(512)" />
|
|
||||||
<column name="notification_topic" type="VARCHAR(256)" />
|
|
||||||
</addColumn>
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
</databaseChangeLog>
|
|
||||||
@@ -17,5 +17,20 @@
|
|||||||
</update>
|
</update>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="add-push-notification-settings" author="athou">
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="push_notification_type" type="VARCHAR(16)" />
|
||||||
|
<column name="push_notification_server_url" type="VARCHAR(1024)" />
|
||||||
|
<column name="push_notification_user_id" type="VARCHAR(512)" />
|
||||||
|
<column name="push_notification_user_secret" type="VARCHAR(512)" />
|
||||||
|
<column name="push_notification_topic" type="VARCHAR(256)" />
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="FEEDSUBSCRIPTIONS">
|
||||||
|
<column name="push_notifications_enabled" type="BOOLEAN" valueBoolean="false">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
<include file="changelogs/db.changelog-5.8.xml" />
|
<include file="changelogs/db.changelog-5.8.xml" />
|
||||||
<include file="changelogs/db.changelog-5.11.xml" />
|
<include file="changelogs/db.changelog-5.11.xml" />
|
||||||
<include file="changelogs/db.changelog-5.12.xml" />
|
<include file="changelogs/db.changelog-5.12.xml" />
|
||||||
<include file="changelogs/db.changelog-6.1.xml" />
|
|
||||||
<include file="changelogs/db.changelog-7.0.xml" />
|
<include file="changelogs/db.changelog-7.0.xml" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -6,6 +6,7 @@ import java.io.OutputStream;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.NoRouteToHostException;
|
import java.net.NoRouteToHostException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -56,6 +57,7 @@ class HttpGetterTest {
|
|||||||
|
|
||||||
private CommaFeedConfiguration config;
|
private CommaFeedConfiguration config;
|
||||||
|
|
||||||
|
private HttpClientFactory provider;
|
||||||
private HttpGetter getter;
|
private HttpGetter getter;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -78,7 +80,8 @@ class HttpGetterTest {
|
|||||||
Mockito.when(config.httpClient().cache().expiration()).thenReturn(Duration.ofMinutes(1));
|
Mockito.when(config.httpClient().cache().expiration()).thenReturn(Duration.ofMinutes(1));
|
||||||
Mockito.when(config.feedRefresh().httpThreads()).thenReturn(3);
|
Mockito.when(config.feedRefresh().httpThreads()).thenReturn(3);
|
||||||
|
|
||||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
this.provider = new HttpClientFactory(config, Mockito.mock(CommaFeedVersion.class));
|
||||||
|
this.getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@@ -172,7 +175,7 @@ class HttpGetterTest {
|
|||||||
@Test
|
@Test
|
||||||
void dataTimeout() {
|
void dataTimeout() {
|
||||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofMillis(500));
|
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofMillis(500));
|
||||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
this.getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||||
|
|
||||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||||
.respond(HttpResponse.response().withDelay(Delay.milliseconds(1000)));
|
.respond(HttpResponse.response().withDelay(Delay.milliseconds(1000)));
|
||||||
@@ -183,7 +186,7 @@ class HttpGetterTest {
|
|||||||
@Test
|
@Test
|
||||||
void connectTimeout() {
|
void connectTimeout() {
|
||||||
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofMillis(500));
|
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofMillis(500));
|
||||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
this.getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||||
// try to connect to a non-routable address
|
// try to connect to a non-routable address
|
||||||
// https://stackoverflow.com/a/904609
|
// https://stackoverflow.com/a/904609
|
||||||
Exception e = Assertions.assertThrows(Exception.class, () -> getter.get("http://10.255.255.1"));
|
Exception e = Assertions.assertThrows(Exception.class, () -> getter.get("http://10.255.255.1"));
|
||||||
@@ -367,44 +370,44 @@ class HttpGetterTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void init() {
|
void init() {
|
||||||
Mockito.when(config.httpClient().blockLocalAddresses()).thenReturn(true);
|
Mockito.when(config.httpClient().blockLocalAddresses()).thenReturn(true);
|
||||||
getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void localhost() {
|
void localhost() {
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://localhost"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://localhost"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://127.0.0.1"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://127.0.0.1"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://2130706433"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://2130706433"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0x7F.0x00.0x00.0X01"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://0x7F.0x00.0x00.0X01"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void zero() {
|
void zero() {
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0.0.0.0"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://0.0.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void linkLocal() {
|
void linkLocal() {
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.12.34"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://169.254.12.34"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.169.254"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://169.254.169.254"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void multicast() {
|
void multicast() {
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://224.2.3.4"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://224.2.3.4"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://239.255.255.254"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://239.255.255.254"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void privateIpv4Ranges() {
|
void privateIpv4Ranges() {
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://10.0.0.1"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://10.0.0.1"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://172.16.0.1"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://172.16.0.1"));
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://192.168.0.1"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://192.168.0.1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void privateIpv6Ranges() {
|
void privateIpv6Ranges() {
|
||||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://fd12:3456:789a:1::1"));
|
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://[fe80::215:5dff:fe15:102]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
package com.commafeed.backend.feed;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
|
||||||
import com.commafeed.backend.dao.UnitOfWork;
|
|
||||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
|
||||||
import com.commafeed.backend.feed.parser.FeedParserResult.Content;
|
|
||||||
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
|
|
||||||
import com.commafeed.backend.model.Feed;
|
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
|
||||||
import com.commafeed.backend.model.User;
|
|
||||||
import com.commafeed.backend.model.UserSettings;
|
|
||||||
import com.commafeed.backend.service.FeedEntryService;
|
|
||||||
import com.commafeed.backend.service.FeedService;
|
|
||||||
import com.commafeed.backend.service.NotificationService;
|
|
||||||
import com.commafeed.frontend.ws.WebSocketSessions;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
class FeedRefreshUpdaterTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private UnitOfWork unitOfWork;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private FeedService feedService;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private FeedEntryService feedEntryService;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private UserSettingsDAO userSettingsDAO;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private WebSocketSessions webSocketSessions;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private NotificationService notificationService;
|
|
||||||
|
|
||||||
private FeedRefreshUpdater updater;
|
|
||||||
|
|
||||||
private Feed feed;
|
|
||||||
private User user;
|
|
||||||
private FeedSubscription subscription;
|
|
||||||
private Entry entry;
|
|
||||||
private FeedEntry feedEntry;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() throws Exception {
|
|
||||||
MetricRegistry metrics = new MetricRegistry();
|
|
||||||
updater = new FeedRefreshUpdater(unitOfWork, feedService, feedEntryService, metrics, feedSubscriptionDAO, userSettingsDAO,
|
|
||||||
webSocketSessions, notificationService);
|
|
||||||
|
|
||||||
// UnitOfWork passthrough: execute callables and runnables directly
|
|
||||||
Mockito.when(unitOfWork.call(Mockito.any())).thenAnswer(inv -> inv.getArgument(0, Callable.class).call());
|
|
||||||
Mockito.doAnswer(inv -> {
|
|
||||||
inv.getArgument(0, Runnable.class).run();
|
|
||||||
return null;
|
|
||||||
}).when(unitOfWork).run(Mockito.any());
|
|
||||||
|
|
||||||
user = new User();
|
|
||||||
user.setId(1L);
|
|
||||||
|
|
||||||
feed = new Feed();
|
|
||||||
feed.setId(1L);
|
|
||||||
feed.setUrl("https://example.com/feed.xml");
|
|
||||||
|
|
||||||
subscription = new FeedSubscription();
|
|
||||||
subscription.setId(1L);
|
|
||||||
subscription.setTitle("My Feed");
|
|
||||||
subscription.setUser(user);
|
|
||||||
subscription.setNotifyOnNewEntries(true);
|
|
||||||
|
|
||||||
Content content = new Content("Article Title", "content", "author", null, null, null);
|
|
||||||
entry = new Entry("guid-1", "https://example.com/article", Instant.now(), content);
|
|
||||||
|
|
||||||
feedEntry = new FeedEntry();
|
|
||||||
feedEntry.setUrl("https://example.com/article");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateSendsNotificationsForNewEntries() {
|
|
||||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
|
||||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
|
||||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
|
||||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
|
||||||
|
|
||||||
UserSettings settings = new UserSettings();
|
|
||||||
settings.setNotificationEnabled(true);
|
|
||||||
Mockito.when(userSettingsDAO.findByUser(user)).thenReturn(settings);
|
|
||||||
|
|
||||||
updater.update(feed, List.of(entry));
|
|
||||||
|
|
||||||
Mockito.verify(notificationService).notify(settings, subscription, feedEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateDoesNotNotifyWhenSubscriptionNotifyDisabled() {
|
|
||||||
subscription.setNotifyOnNewEntries(false);
|
|
||||||
|
|
||||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
|
||||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
|
||||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
|
||||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
|
||||||
|
|
||||||
updater.update(feed, List.of(entry));
|
|
||||||
|
|
||||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateDoesNotNotifyWhenUserNotificationsDisabled() {
|
|
||||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
|
||||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
|
||||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
|
||||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
|
||||||
|
|
||||||
UserSettings settings = new UserSettings();
|
|
||||||
settings.setNotificationEnabled(false);
|
|
||||||
Mockito.when(userSettingsDAO.findByUser(user)).thenReturn(settings);
|
|
||||||
|
|
||||||
updater.update(feed, List.of(entry));
|
|
||||||
|
|
||||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateDoesNotNotifyWhenNoUserSettings() {
|
|
||||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
|
||||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
|
||||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
|
||||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
|
||||||
|
|
||||||
Mockito.when(userSettingsDAO.findByUser(user)).thenReturn(null);
|
|
||||||
|
|
||||||
updater.update(feed, List.of(entry));
|
|
||||||
|
|
||||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateDoesNotNotifyForExistingEntries() {
|
|
||||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
|
||||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(feedEntry);
|
|
||||||
|
|
||||||
updater.update(feed, List.of(entry));
|
|
||||||
|
|
||||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package com.commafeed.backend.service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.net.http.HttpResponse.BodyHandler;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
|
||||||
import com.commafeed.backend.model.FeedEntryContent;
|
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
|
||||||
import com.commafeed.backend.model.UserSettings;
|
|
||||||
import com.commafeed.backend.model.UserSettings.NotificationType;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
class NotificationServiceTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpClient httpClient;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpResponse<String> httpResponse;
|
|
||||||
|
|
||||||
private NotificationService notificationService;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
notificationService = new NotificationService(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stubHttpClient() throws Exception {
|
|
||||||
Mockito.when(httpResponse.statusCode()).thenReturn(200);
|
|
||||||
Mockito.when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.<BodyHandler<String>> any())).thenReturn(httpResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequest captureRequest() throws Exception {
|
|
||||||
ArgumentCaptor<HttpRequest> captor = ArgumentCaptor.forClass(HttpRequest.class);
|
|
||||||
Mockito.verify(httpClient).send(captor.capture(), Mockito.<BodyHandler<String>> any());
|
|
||||||
return captor.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendNtfyBuildsCorrectRequest() throws Exception {
|
|
||||||
stubHttpClient();
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
|
||||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
|
||||||
settings.setNotificationTopic("my-topic");
|
|
||||||
settings.setNotificationToken("my-token");
|
|
||||||
|
|
||||||
FeedSubscription sub = newSubscription("My Feed");
|
|
||||||
FeedEntry entry = newEntry("New Article", "https://example.com/article");
|
|
||||||
|
|
||||||
notificationService.notify(settings, sub, entry);
|
|
||||||
|
|
||||||
HttpRequest request = captureRequest();
|
|
||||||
Assertions.assertEquals("https://ntfy.example.com/my-topic", request.uri().toString());
|
|
||||||
Assertions.assertEquals("My Feed: New Article", request.headers().firstValue("Title").orElse(null));
|
|
||||||
Assertions.assertEquals("https://example.com/article", request.headers().firstValue("Click").orElse(null));
|
|
||||||
Assertions.assertEquals("Bearer my-token", request.headers().firstValue("Authorization").orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendNtfyOmitsOptionalHeaders() throws Exception {
|
|
||||||
stubHttpClient();
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
|
||||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
|
||||||
settings.setNotificationTopic("my-topic");
|
|
||||||
|
|
||||||
FeedSubscription sub = newSubscription("My Feed");
|
|
||||||
FeedEntry entry = newEntry("Title", "");
|
|
||||||
|
|
||||||
notificationService.notify(settings, sub, entry);
|
|
||||||
|
|
||||||
HttpRequest request = captureRequest();
|
|
||||||
Assertions.assertTrue(request.headers().firstValue("Click").isEmpty());
|
|
||||||
Assertions.assertTrue(request.headers().firstValue("Authorization").isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendNtfySkipsWhenMissingConfig() throws Exception {
|
|
||||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
|
||||||
settings.setNotificationTopic("topic");
|
|
||||||
notificationService.notify(settings, newSubscription("F"), newEntry("T", "U"));
|
|
||||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
|
||||||
|
|
||||||
UserSettings settings2 = newSettings(NotificationType.NTFY);
|
|
||||||
settings2.setNotificationServerUrl("https://ntfy.example.com");
|
|
||||||
notificationService.notify(settings2, newSubscription("F"), newEntry("T", "U"));
|
|
||||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendGotifyBuildsCorrectRequest() throws Exception {
|
|
||||||
stubHttpClient();
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.GOTIFY);
|
|
||||||
settings.setNotificationServerUrl("https://gotify.example.com/");
|
|
||||||
settings.setNotificationToken("app-token");
|
|
||||||
|
|
||||||
FeedSubscription sub = newSubscription("My Feed");
|
|
||||||
FeedEntry entry = newEntry("New Article", "https://example.com/article");
|
|
||||||
|
|
||||||
notificationService.notify(settings, sub, entry);
|
|
||||||
|
|
||||||
HttpRequest request = captureRequest();
|
|
||||||
Assertions.assertEquals("https://gotify.example.com/message", request.uri().toString());
|
|
||||||
Assertions.assertEquals("app-token", request.headers().firstValue("X-Gotify-Key").orElse(null));
|
|
||||||
Assertions.assertEquals("application/json", request.headers().firstValue("Content-Type").orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendGotifySkipsWhenMissingConfig() throws Exception {
|
|
||||||
UserSettings settings = newSettings(NotificationType.GOTIFY);
|
|
||||||
settings.setNotificationToken("token");
|
|
||||||
notificationService.notify(settings, newSubscription("F"), newEntry("T", "U"));
|
|
||||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
|
||||||
|
|
||||||
UserSettings settings2 = newSettings(NotificationType.GOTIFY);
|
|
||||||
settings2.setNotificationServerUrl("https://gotify.example.com");
|
|
||||||
notificationService.notify(settings2, newSubscription("F"), newEntry("T", "U"));
|
|
||||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendPushoverBuildsCorrectRequest() throws Exception {
|
|
||||||
stubHttpClient();
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.PUSHOVER);
|
|
||||||
settings.setNotificationToken("po-token");
|
|
||||||
settings.setNotificationUserKey("po-user");
|
|
||||||
|
|
||||||
FeedSubscription sub = newSubscription("My Feed");
|
|
||||||
FeedEntry entry = newEntry("New Article", "https://example.com/article");
|
|
||||||
|
|
||||||
notificationService.notify(settings, sub, entry);
|
|
||||||
|
|
||||||
HttpRequest request = captureRequest();
|
|
||||||
Assertions.assertEquals("https://api.pushover.net/1/messages.json", request.uri().toString());
|
|
||||||
Assertions.assertEquals("application/x-www-form-urlencoded", request.headers().firstValue("Content-Type").orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendPushoverOmitsUrlWhenBlank() throws Exception {
|
|
||||||
stubHttpClient();
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.PUSHOVER);
|
|
||||||
settings.setNotificationToken("po-token");
|
|
||||||
settings.setNotificationUserKey("po-user");
|
|
||||||
|
|
||||||
FeedSubscription sub = newSubscription("My Feed");
|
|
||||||
FeedEntry entry = newEntry("Title", "");
|
|
||||||
|
|
||||||
notificationService.notify(settings, sub, entry);
|
|
||||||
|
|
||||||
Mockito.verify(httpClient).send(Mockito.any(HttpRequest.class), Mockito.<BodyHandler<String>> any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sendPushoverSkipsWhenMissingConfig() throws Exception {
|
|
||||||
UserSettings settings = newSettings(NotificationType.PUSHOVER);
|
|
||||||
settings.setNotificationUserKey("user");
|
|
||||||
notificationService.notify(settings, newSubscription("F"), newEntry("T", "U"));
|
|
||||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
|
||||||
|
|
||||||
UserSettings settings2 = newSettings(NotificationType.PUSHOVER);
|
|
||||||
settings2.setNotificationToken("token");
|
|
||||||
notificationService.notify(settings2, newSubscription("F"), newEntry("T", "U"));
|
|
||||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void notifyDoesNotPropagateExceptions() throws Exception {
|
|
||||||
Mockito.when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.<BodyHandler<String>> any()))
|
|
||||||
.thenThrow(new IOException("connection failed"));
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
|
||||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
|
||||||
settings.setNotificationTopic("topic");
|
|
||||||
|
|
||||||
Assertions.assertDoesNotThrow(() -> notificationService.notify(settings, newSubscription("Feed"), newEntry("Title", "url")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void notifyUsesNewEntryAsFallbackTitle() throws Exception {
|
|
||||||
stubHttpClient();
|
|
||||||
|
|
||||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
|
||||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
|
||||||
settings.setNotificationTopic("topic");
|
|
||||||
|
|
||||||
FeedSubscription sub = newSubscription("Feed");
|
|
||||||
|
|
||||||
FeedEntry entryNoContent = new FeedEntry();
|
|
||||||
entryNoContent.setUrl("https://example.com");
|
|
||||||
notificationService.notify(settings, sub, entryNoContent);
|
|
||||||
|
|
||||||
HttpRequest request = captureRequest();
|
|
||||||
Assertions.assertEquals("Feed: New entry", request.headers().firstValue("Title").orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserSettings newSettings(NotificationType type) {
|
|
||||||
UserSettings settings = new UserSettings();
|
|
||||||
settings.setNotificationEnabled(true);
|
|
||||||
settings.setNotificationType(type);
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FeedSubscription newSubscription(String title) {
|
|
||||||
FeedSubscription sub = new FeedSubscription();
|
|
||||||
sub.setTitle(title);
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FeedEntry newEntry(String title, String url) {
|
|
||||||
FeedEntryContent content = new FeedEntryContent();
|
|
||||||
content.setTitle(title);
|
|
||||||
FeedEntry entry = new FeedEntry();
|
|
||||||
entry.setContent(content);
|
|
||||||
entry.setUrl(url);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.commafeed.backend.service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockserver.client.MockServerClient;
|
||||||
|
import org.mockserver.junit.jupiter.MockServerExtension;
|
||||||
|
import org.mockserver.model.HttpRequest;
|
||||||
|
import org.mockserver.model.HttpResponse;
|
||||||
|
import org.mockserver.model.JsonBody;
|
||||||
|
import org.mockserver.model.MediaType;
|
||||||
|
|
||||||
|
import com.codahale.metrics.Meter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
import com.commafeed.CommaFeedConfiguration;
|
||||||
|
import com.commafeed.backend.HttpClientFactory;
|
||||||
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.commafeed.backend.model.UserSettings;
|
||||||
|
import com.commafeed.backend.model.UserSettings.PushNotificationType;
|
||||||
|
|
||||||
|
@ExtendWith(MockServerExtension.class)
|
||||||
|
class PushNotificationServiceTest {
|
||||||
|
|
||||||
|
private MockServerClient mockServerClient;
|
||||||
|
private PushNotificationService pushNotificationService;
|
||||||
|
private CommaFeedConfiguration config;
|
||||||
|
private UserSettings userSettings;
|
||||||
|
private FeedSubscription subscription;
|
||||||
|
private FeedEntry entry;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init(MockServerClient mockServerClient) {
|
||||||
|
this.mockServerClient = mockServerClient;
|
||||||
|
this.mockServerClient.reset();
|
||||||
|
|
||||||
|
this.config = Mockito.mock(CommaFeedConfiguration.class, Mockito.RETURNS_DEEP_STUBS);
|
||||||
|
Mockito.when(config.pushNotifications().enabled()).thenReturn(true);
|
||||||
|
Mockito.when(config.pushNotifications().threads()).thenReturn(1);
|
||||||
|
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||||
|
|
||||||
|
HttpClientFactory httpClientFactory = new HttpClientFactory(config, Mockito.mock(com.commafeed.CommaFeedVersion.class));
|
||||||
|
MetricRegistry metricRegistry = Mockito.mock(MetricRegistry.class);
|
||||||
|
Mockito.when(metricRegistry.meter(Mockito.anyString())).thenReturn(Mockito.mock(Meter.class));
|
||||||
|
|
||||||
|
this.pushNotificationService = new PushNotificationService(httpClientFactory, metricRegistry, config);
|
||||||
|
|
||||||
|
this.userSettings = new UserSettings();
|
||||||
|
|
||||||
|
this.subscription = createSubscription("Test Feed");
|
||||||
|
this.entry = createEntry("Test Entry", "http://example.com/entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNtfyNotification() {
|
||||||
|
userSettings.setPushNotificationType(PushNotificationType.NTFY);
|
||||||
|
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
|
||||||
|
userSettings.setPushNotificationTopic("test-topic");
|
||||||
|
userSettings.setPushNotificationUserSecret("test-secret");
|
||||||
|
|
||||||
|
mockServerClient.when(HttpRequest.request()
|
||||||
|
.withMethod("POST")
|
||||||
|
.withPath("/test-topic")
|
||||||
|
.withHeader("Title", "Test Feed")
|
||||||
|
.withHeader("Click", "http://example.com/entry")
|
||||||
|
.withHeader("Authorization", "Bearer test-secret")
|
||||||
|
.withBody("Test Entry")).respond(HttpResponse.response().withStatusCode(200));
|
||||||
|
|
||||||
|
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGotifyNotification() {
|
||||||
|
userSettings.setPushNotificationType(PushNotificationType.GOTIFY);
|
||||||
|
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
|
||||||
|
userSettings.setPushNotificationUserSecret("gotify-token");
|
||||||
|
|
||||||
|
mockServerClient.when(HttpRequest.request()
|
||||||
|
.withMethod("POST")
|
||||||
|
.withPath("/message")
|
||||||
|
.withHeader("X-Gotify-Key", "gotify-token")
|
||||||
|
.withContentType(MediaType.APPLICATION_JSON_UTF_8)
|
||||||
|
.withBody(JsonBody.json("""
|
||||||
|
{
|
||||||
|
"title": "Test Feed",
|
||||||
|
"message": "Test Entry",
|
||||||
|
"priority": 5,
|
||||||
|
"extras": {
|
||||||
|
"client::notification": {
|
||||||
|
"click": {
|
||||||
|
"url": "http://example.com/entry"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""))).respond(HttpResponse.response().withStatusCode(200));
|
||||||
|
|
||||||
|
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPushNotificationDisabled() {
|
||||||
|
Mockito.when(config.pushNotifications().enabled()).thenReturn(false);
|
||||||
|
userSettings.setPushNotificationType(PushNotificationType.NTFY);
|
||||||
|
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
|
||||||
|
userSettings.setPushNotificationTopic("test-topic");
|
||||||
|
|
||||||
|
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
|
||||||
|
mockServerClient.verifyZeroInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FeedSubscription createSubscription(String title) {
|
||||||
|
FeedSubscription subscription = new FeedSubscription();
|
||||||
|
subscription.setTitle(title);
|
||||||
|
subscription.setFeed(new Feed());
|
||||||
|
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FeedEntry createEntry(String title, String url) {
|
||||||
|
FeedEntry entry = new FeedEntry();
|
||||||
|
|
||||||
|
FeedEntryContent content = new FeedEntryContent();
|
||||||
|
content.setTitle(title);
|
||||||
|
|
||||||
|
entry.setContent(content);
|
||||||
|
entry.setUrl(url);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ public abstract class BaseIT {
|
|||||||
|
|
||||||
private static final HttpRequest FEED_REQUEST = HttpRequest.request().withMethod("GET").withPath("/");
|
private static final HttpRequest FEED_REQUEST = HttpRequest.request().withMethod("GET").withPath("/");
|
||||||
|
|
||||||
|
@Getter
|
||||||
private MockServerClient mockServerClient;
|
private MockServerClient mockServerClient;
|
||||||
private Client client;
|
private Client client;
|
||||||
private String feedUrl;
|
private String feedUrl;
|
||||||
@@ -122,10 +123,15 @@ public abstract class BaseIT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Long subscribe(String feedUrl, String categoryId) {
|
protected Long subscribe(String feedUrl, String categoryId) {
|
||||||
|
return subscribe(feedUrl, categoryId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Long subscribe(String feedUrl, String categoryId, boolean pushNotificationsEnabled) {
|
||||||
SubscribeRequest subscribeRequest = new SubscribeRequest();
|
SubscribeRequest subscribeRequest = new SubscribeRequest();
|
||||||
subscribeRequest.setUrl(feedUrl);
|
subscribeRequest.setUrl(feedUrl);
|
||||||
subscribeRequest.setTitle("my title for this feed");
|
subscribeRequest.setTitle("my title for this feed");
|
||||||
subscribeRequest.setCategoryId(categoryId);
|
subscribeRequest.setCategoryId(categoryId);
|
||||||
|
subscribeRequest.setPushNotificationsEnabled(pushNotificationsEnabled);
|
||||||
return RestAssured.given()
|
return RestAssured.given()
|
||||||
.body(subscribeRequest)
|
.body(subscribeRequest)
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.commafeed.integration;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.awaitility.Awaitility;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockserver.model.HttpRequest;
|
||||||
|
import org.mockserver.model.HttpResponse;
|
||||||
|
import org.mockserver.verify.VerificationTimes;
|
||||||
|
|
||||||
|
import com.commafeed.TestConstants;
|
||||||
|
import com.commafeed.backend.model.UserSettings.PushNotificationType;
|
||||||
|
import com.commafeed.frontend.model.Settings;
|
||||||
|
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class PushNotificationIT extends BaseIT {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
initialSetup(TestConstants.ADMIN_USERNAME, TestConstants.ADMIN_PASSWORD);
|
||||||
|
RestAssured.authentication = RestAssured.preemptive().basic(TestConstants.ADMIN_USERNAME, TestConstants.ADMIN_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
RestAssured.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void receivedPushNotifications() {
|
||||||
|
// mock ntfy server
|
||||||
|
HttpRequest ntfyPost = HttpRequest.request().withMethod("POST").withPath("/ntfy/integration-test");
|
||||||
|
getMockServerClient().when(ntfyPost).respond(HttpResponse.response().withStatusCode(200));
|
||||||
|
|
||||||
|
// enable push notifications
|
||||||
|
Settings settings = RestAssured.given().get("rest/user/settings").then().extract().as(Settings.class);
|
||||||
|
settings.getPushNotificationSettings().setType(PushNotificationType.NTFY);
|
||||||
|
settings.getPushNotificationSettings().setServerUrl("http://localhost:" + getMockServerClient().getPort() + "/ntfy");
|
||||||
|
settings.getPushNotificationSettings().setTopic("integration-test");
|
||||||
|
RestAssured.given().body(settings).contentType(ContentType.JSON).post("rest/user/settings").then().statusCode(200);
|
||||||
|
|
||||||
|
// subscribe with push notifications enabled
|
||||||
|
subscribe(getFeedUrl(), null, true);
|
||||||
|
|
||||||
|
// await push notification for the two entries in the feed
|
||||||
|
Awaitility.await()
|
||||||
|
.atMost(Duration.ofSeconds(20))
|
||||||
|
.untilAsserted(() -> getMockServerClient().verify(ntfyPost, VerificationTimes.exactly(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ class ServerIT extends BaseIT {
|
|||||||
Assertions.assertEquals(30000, serverInfos.getTreeReloadInterval());
|
Assertions.assertEquals(30000, serverInfos.getTreeReloadInterval());
|
||||||
Assertions.assertEquals(60000, serverInfos.getForceRefreshCooldownDuration());
|
Assertions.assertEquals(60000, serverInfos.getForceRefreshCooldownDuration());
|
||||||
Assertions.assertEquals(4, serverInfos.getMinimumPasswordLength());
|
Assertions.assertEquals(4, serverInfos.getMinimumPasswordLength());
|
||||||
|
Assertions.assertTrue(serverInfos.isPushNotificationsEnabled());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user