feat(ui): telegram user subscriptions
This commit is contained in:
committed by
Umputun
parent
366cc19c1b
commit
0ed7452e77
@@ -78,6 +78,34 @@ export const uploadImage = (image: File): Promise<Image> => {
|
||||
|
||||
/* Subscription methods */
|
||||
|
||||
/**
|
||||
* Start process of telegram subscription to updates
|
||||
*/
|
||||
export const telegramSubscribe = (): Promise<{
|
||||
bot: string;
|
||||
token: string;
|
||||
}> => apiFetcher.get('/telegram/subscribe');
|
||||
|
||||
/**
|
||||
* Start process of telegram subscription to updates
|
||||
* Example of error response: {"code":0,"details":"can't set telegram for user","error":"request is not verified yet"}
|
||||
* Example of success response: {"address":"223211010","updated":true}
|
||||
*/
|
||||
export const telegramCurrentSubscribtion = ({
|
||||
token,
|
||||
}: {
|
||||
token: string;
|
||||
}): Promise<{
|
||||
address: string;
|
||||
updated: boolean;
|
||||
}> => apiFetcher.get('/telegram/subscribe', { tkn: token });
|
||||
|
||||
/**
|
||||
* Start process of telegram subscription to updates
|
||||
* Example of success response: {"deleted":true}
|
||||
*/
|
||||
export const telegramUnsubcribe = (): Promise<{ deleted: boolean }> => apiFetcher.delete('/telegram');
|
||||
|
||||
/**
|
||||
* Start process of email subscription to updates
|
||||
* @param emailAddress email for subscription
|
||||
|
||||
@@ -18,6 +18,7 @@ function includes<T extends U, U>(coll: ReadonlyArray<T>, el: U): el is T {
|
||||
export const rawParams = parseQuery();
|
||||
export const maxShownComments = parseNumber(rawParams.max_shown_comments) ?? MAX_SHOWN_ROOT_COMMENTS;
|
||||
export const isEmailSubscription = rawParams.show_email_subscription !== 'false';
|
||||
export const isTelegramSubscription = rawParams.show_telegram_subscription !== 'false';
|
||||
export const isRssSubscription =
|
||||
rawParams.show_rss_subscription === undefined || rawParams.show_rss_subscription !== 'false';
|
||||
export const theme = (rawParams.theme = includes(THEMES, rawParams.theme) ? rawParams.theme : THEMES[0]);
|
||||
|
||||
@@ -57,34 +57,6 @@ export const messages = defineMessages<string>({
|
||||
id: 'auth.submit',
|
||||
defaultMessage: 'Submit',
|
||||
},
|
||||
telegramLink: {
|
||||
id: 'auth.telegram-link',
|
||||
defaultMessage: 'by the link',
|
||||
},
|
||||
telegramCheck: {
|
||||
id: 'auth.telegram-check',
|
||||
defaultMessage: 'Check',
|
||||
},
|
||||
telegramQR: {
|
||||
id: 'auth.telegram-qr',
|
||||
defaultMessage: 'Telegram QR-code',
|
||||
},
|
||||
telegramMessage1: {
|
||||
id: 'auth.telegram-message-1',
|
||||
defaultMessage: 'Open Telegram',
|
||||
},
|
||||
telegramOptionalQR: {
|
||||
id: 'auth.telegram-optional-qr',
|
||||
defaultMessage: 'or by scanning the QR code',
|
||||
},
|
||||
telegramMessage2: {
|
||||
id: 'auth.telegram-message-2',
|
||||
defaultMessage: 'and click “Start” there.',
|
||||
},
|
||||
telegramMessage3: {
|
||||
id: 'auth.telegram-message-3',
|
||||
defaultMessage: 'Afterwards, click “Check” below.',
|
||||
},
|
||||
openProfile: {
|
||||
id: 'auth.open-profile',
|
||||
defaultMessage: 'Open My Profile',
|
||||
|
||||
@@ -195,15 +195,3 @@
|
||||
color: var(--error-color);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.telegramQR {
|
||||
background-color: rgb(var(--white-color));
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 12px auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.telegram {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useState, useRef } from 'preact/hooks';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { BASE_URL, API_BASE } from 'common/constants.config';
|
||||
import { setUser } from 'store/user/actions';
|
||||
import { Input } from 'components/input';
|
||||
import { TelegramLink } from 'components/telegram/telegram-link';
|
||||
import { CrossIcon } from 'components/icons/cross';
|
||||
import { TextareaAutosize } from 'components/textarea-autosize';
|
||||
import { Spinner } from 'components/spinner/spinner';
|
||||
@@ -234,33 +234,12 @@ export function Auth() {
|
||||
<CrossIcon />
|
||||
</button>
|
||||
</div>
|
||||
<p className={clsx('telegram', styles.telegram)}>
|
||||
{intl.formatMessage(messages.telegramMessage1)}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`https://t.me/${telegramParamsRef.current.bot}/?start=${telegramParamsRef.current.token}`}
|
||||
>
|
||||
{intl.formatMessage(messages.telegramLink)}
|
||||
</a>
|
||||
{window.screen.width >= 768 && ` ${intl.formatMessage(messages.telegramOptionalQR)}`}{' '}
|
||||
{intl.formatMessage(messages.telegramMessage2)}
|
||||
<br />
|
||||
{intl.formatMessage(messages.telegramMessage3)}
|
||||
</p>
|
||||
{window.screen.width >= 768 && (
|
||||
<img
|
||||
src={`${BASE_URL}${API_BASE}/qr/telegram?url=https://t.me/${telegramParamsRef.current.bot}/?start=${telegramParamsRef.current.token}`}
|
||||
height="200"
|
||||
width="200"
|
||||
className={clsx('telegram-qr', styles.telegramQR)}
|
||||
alt={intl.formatMessage(messages.telegramQR)}
|
||||
/>
|
||||
)}
|
||||
{errorMessage && <div className={clsx('auth-error', styles.error)}>{errorMessage}</div>}
|
||||
<Button key="submit" className="auth-submit" type="submit" onClick={handleTelegramSubmit}>
|
||||
{intl.formatMessage(messages.telegramCheck)}
|
||||
</Button>
|
||||
<TelegramLink
|
||||
onSubmit={handleTelegramSubmit}
|
||||
errorMessage={errorMessage}
|
||||
bot={telegramParamsRef.current.bot}
|
||||
token={telegramParamsRef.current.token}
|
||||
/>
|
||||
</>
|
||||
) : view === 'token' ? (
|
||||
<>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import { h, JSX } from 'preact';
|
||||
import { forwardRef } from 'preact/compat';
|
||||
import b, { Mods, Mix } from 'bem-react-helper';
|
||||
@@ -11,11 +12,17 @@ export type ButtonProps = Omit<JSX.HTMLAttributes, 'size' | 'className'> & {
|
||||
mods?: Mods;
|
||||
mix?: Mix;
|
||||
type?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ children, theme, mods, mix, kind, type = 'button', size, ...props }, ref) => (
|
||||
<button className={b('button', { mods: { kind, size, theme }, mix }, { ...mods })} type={type} {...props} ref={ref}>
|
||||
({ children, theme, mods, mix, kind, type = 'button', size, className, ...props }, ref) => (
|
||||
<button
|
||||
className={clsx(b('button', { mods: { kind, size, theme }, mix }, { ...mods }), className)}
|
||||
type={type}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
width: 250px;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.preloader {
|
||||
margin: 0 auto;
|
||||
filter: invert(1);
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { fireEvent, screen, waitForElementToBeRemoved } from '@testing-library/preact';
|
||||
|
||||
import { render } from 'tests/utils';
|
||||
import * as api from 'common/api';
|
||||
import { user, anonymousUser } from '__stubs__/user';
|
||||
import { RequestError } from 'utils/errorUtils';
|
||||
import { sleep } from 'utils/sleep';
|
||||
|
||||
import { SubscribeByTelegram } from '.';
|
||||
import { StoreState } from 'store';
|
||||
|
||||
const initialStore = {
|
||||
user,
|
||||
theme: 'light',
|
||||
} as const;
|
||||
|
||||
describe('<SubscribeByTelegram />', () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(api, 'telegramSubscribe')
|
||||
.mockClear()
|
||||
.mockImplementation(async () => {
|
||||
await sleep(10);
|
||||
return { bot: 'foo_bot', token: 'foo_token' };
|
||||
});
|
||||
jest
|
||||
.spyOn(api, 'telegramCurrentSubscribtion')
|
||||
.mockClear()
|
||||
.mockImplementation(async () => {
|
||||
await sleep(10);
|
||||
return { address: '223211010', updated: true };
|
||||
});
|
||||
jest
|
||||
.spyOn(api, 'telegramUnsubcribe')
|
||||
.mockClear()
|
||||
.mockImplementation(async () => {
|
||||
await sleep(10);
|
||||
return { deleted: true };
|
||||
});
|
||||
sessionStorage.clear();
|
||||
});
|
||||
const createWrapper = (store: Partial<StoreState> = initialStore) => render(<SubscribeByTelegram />, store);
|
||||
|
||||
it('should be rendered with disabled email button when user is anonymous', () => {
|
||||
createWrapper({ ...initialStore, user: anonymousUser });
|
||||
|
||||
expect(screen.getByTitle('Available only for registered users')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be rendered with enabled email button when user is logged in', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(screen.getByTitle('Subscribe by Telegram')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should show correct telegram link', async () => {
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
expect(await screen.findByText(/by the link/)).toHaveAttribute('href', 'https://t.me/foo_bot/?start=foo_token');
|
||||
});
|
||||
|
||||
it('should not do same API call to /subscribe twice during same session', async () => {
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
fireEvent.click(button); // close modal
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(api.telegramSubscribe).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should subscribe', async () => {
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
const checkButton = await screen.findByText('Check');
|
||||
fireEvent.click(checkButton);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
expect(api.telegramCurrentSubscribtion).toBeCalledTimes(1);
|
||||
expect(screen.getByText(/You have been subscribed/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should subscribe and then unsubscribe', async () => {
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
const checkButton = await screen.findByText('Check');
|
||||
fireEvent.click(checkButton);
|
||||
|
||||
const unsubscribeButton = await screen.findByText('Unsubscribe');
|
||||
fireEvent.click(unsubscribeButton);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
expect(api.telegramUnsubcribe).toBeCalledTimes(1);
|
||||
expect(screen.getByText(/You have been unsubscribed/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should subscribe, close window and then unsubscribe', async () => {
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
const checkButton = await screen.findByText('Check');
|
||||
fireEvent.click(checkButton);
|
||||
|
||||
await screen.findByText('Unsubscribe'); // wait
|
||||
fireEvent.click(button); // close
|
||||
fireEvent.click(button);
|
||||
|
||||
fireEvent.click(await screen.findByText('Unsubscribe'));
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
expect(api.telegramUnsubcribe).toBeCalledTimes(1);
|
||||
expect(screen.getByText(/You have been unsubscribed/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should subscribe, unsubscribe and resubscribe', async () => {
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
const checkButton = await screen.findByText('Check');
|
||||
fireEvent.click(checkButton);
|
||||
|
||||
const unsubscribeButton = await screen.findByText('Unsubscribe');
|
||||
fireEvent.click(unsubscribeButton);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
fireEvent.click(await screen.findByText('Resubscribe'));
|
||||
fireEvent.click(await screen.findByText('Check'));
|
||||
expect(api.telegramCurrentSubscribtion).toBeCalledTimes(2);
|
||||
expect(await screen.findByText(/You have been subscribed/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show subscribed interface if user is already subscribed', async () => {
|
||||
jest.spyOn(api, 'telegramSubscribe').mockImplementation(async () => {
|
||||
await sleep(10);
|
||||
const e = new RequestError('already subscribed', 17);
|
||||
throw e;
|
||||
});
|
||||
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
expect(screen.getByText(/You have been subscribed/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show subscribed interface if user is already subscribed with 409 status code', async () => {
|
||||
jest.spyOn(api, 'telegramSubscribe').mockImplementation(async () => {
|
||||
await sleep(10);
|
||||
const e = new RequestError('Conflict.', 409);
|
||||
throw e;
|
||||
});
|
||||
|
||||
createWrapper();
|
||||
const button = screen.getByTitle('Subscribe by Telegram');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
await screen.findByLabelText('Loading...');
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...'));
|
||||
|
||||
expect(screen.getByText(/You have been subscribed/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
import clsx from 'clsx';
|
||||
import { h, FunctionComponent, Fragment } from 'preact';
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { User } from 'common/types';
|
||||
import { StoreState } from 'store';
|
||||
import { FetcherError, RequestError, extractErrorMessageFromResponse } from 'utils/errorUtils';
|
||||
import { useTheme } from 'hooks/useTheme';
|
||||
import { useSessionStorage } from 'hooks/useSessionState';
|
||||
import { telegramSubscribe, telegramCurrentSubscribtion, telegramUnsubcribe } from 'common/api';
|
||||
import { Dropdown } from 'components/dropdown';
|
||||
import { isUserAnonymous } from 'utils/isUserAnonymous';
|
||||
import { TelegramLink } from 'components/telegram/telegram-link';
|
||||
import { Preloader } from 'components/preloader';
|
||||
import { Button } from 'components/button';
|
||||
|
||||
import styles from './comment-form__subscribe-by-telegram.module.css';
|
||||
|
||||
const messages = defineMessages({
|
||||
haveSubscribed: {
|
||||
id: 'subscribeByTelegram.have-been-subscribed',
|
||||
defaultMessage: 'You have been subscribed on updates by telegram',
|
||||
},
|
||||
haveUnsubscribed: {
|
||||
id: 'subscribeByTelegram.have-been-unsubscribed',
|
||||
defaultMessage: 'You have been unsubscribed by telegram to updates',
|
||||
},
|
||||
unsubscribe: {
|
||||
id: 'subscribeByTelegram.unsubscribe',
|
||||
defaultMessage: 'Unsubscribe',
|
||||
},
|
||||
resubscribe: {
|
||||
id: 'subscribeByTelegram.resubscribe',
|
||||
defaultMessage: 'Resubscribe',
|
||||
},
|
||||
subscribeByTelegram: {
|
||||
id: 'subscribeByTelegram.subscribe-by-telegram',
|
||||
defaultMessage: 'Subscribe by Telegram',
|
||||
},
|
||||
onlyRegisteredUsers: {
|
||||
id: 'subscribeByTelegram.only-registered-users',
|
||||
defaultMessage: 'Available only for registered users',
|
||||
},
|
||||
telegram: {
|
||||
id: 'subscribeByTelegram.telegram',
|
||||
defaultMessage: 'Telegram',
|
||||
},
|
||||
});
|
||||
|
||||
type STEP = 'initial' | 'subscribed' | 'unsubscribed';
|
||||
|
||||
export const SubscribeByTelegramForm: FunctionComponent = () => {
|
||||
const intl = useIntl();
|
||||
const [step, setStep] = useSessionStorage<STEP>('telegram-subscription-step', 'initial');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [telegram, setTelegram] = useSessionStorage<{ token: string; bot: string }>('telegram-subscription-telegram');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTelegram = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { token, bot } = await telegramSubscribe();
|
||||
setTelegram({ token, bot });
|
||||
} catch (e) {
|
||||
if ((e as RequestError).error === 'already subscribed') {
|
||||
setStep('subscribed');
|
||||
return;
|
||||
}
|
||||
if ((e as RequestError).code === 409) {
|
||||
setStep('subscribed');
|
||||
return;
|
||||
}
|
||||
setError(extractErrorMessageFromResponse(e as FetcherError, intl));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (telegram) {
|
||||
return;
|
||||
}
|
||||
fetchTelegram();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [telegram]);
|
||||
|
||||
const fetchTelegramSubscription = async (token: string) => {
|
||||
try {
|
||||
setTimeout(() => {
|
||||
// to prevent dropdown closing
|
||||
setLoading(true);
|
||||
}, 0);
|
||||
const { updated } = await telegramCurrentSubscribtion({ token });
|
||||
if (!updated) {
|
||||
return;
|
||||
}
|
||||
setStep('subscribed');
|
||||
} catch (e) {
|
||||
setError(extractErrorMessageFromResponse(e as FetcherError, intl));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTelegramCheck = () => {
|
||||
if (!telegram) {
|
||||
return;
|
||||
}
|
||||
fetchTelegramSubscription(telegram.token);
|
||||
};
|
||||
|
||||
const handleTelegramUnsubscribe = async () => {
|
||||
try {
|
||||
setTimeout(() => {
|
||||
// to prevent dropdown closing
|
||||
setLoading(true);
|
||||
}, 0);
|
||||
await telegramUnsubcribe();
|
||||
setStep('unsubscribed');
|
||||
} catch (e) {
|
||||
setError(extractErrorMessageFromResponse(e as FetcherError, intl));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTelegramResubscribe = async () => {
|
||||
setStep('initial');
|
||||
};
|
||||
return (
|
||||
<div className={clsx(styles.root)}>
|
||||
{loading && <Preloader className={clsx(styles.preloader)} />}
|
||||
{!loading && telegram && step === 'initial' && (
|
||||
<TelegramLink errorMessage={error} onSubmit={handleTelegramCheck} bot={telegram.bot} token={telegram.token} />
|
||||
)}
|
||||
{!loading && step === 'subscribed' && (
|
||||
<>
|
||||
<p>{intl.formatMessage(messages.haveSubscribed)}</p>
|
||||
<Button kind="primary" size="large" onClick={handleTelegramUnsubscribe}>
|
||||
{intl.formatMessage(messages.unsubscribe)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!loading && step === 'unsubscribed' && (
|
||||
<>
|
||||
<p>{intl.formatMessage(messages.haveUnsubscribed)}</p>
|
||||
<Button kind="primary" size="large" onClick={handleTelegramResubscribe}>
|
||||
{intl.formatMessage(messages.resubscribe)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubscribeByTelegram: FunctionComponent = () => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const user = useSelector<StoreState, User | null>(({ user }) => user);
|
||||
const isAnonymous = isUserAnonymous(user);
|
||||
const buttonTitle = intl.formatMessage(isAnonymous ? messages.onlyRegisteredUsers : messages.subscribeByTelegram);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
title={intl.formatMessage(messages.telegram)}
|
||||
theme={theme}
|
||||
disabled={isAnonymous}
|
||||
buttonTitle={buttonTitle}
|
||||
>
|
||||
<SubscribeByTelegramForm />
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SubscribeByTelegram } from './comment-form__subscribe-by-telegram';
|
||||
@@ -122,10 +122,42 @@ describe('<CommentForm />', () => {
|
||||
expect(screen.getByText(/Subscribe by/)).toBeVisible();
|
||||
expect(screen.getByTitle('Subscribe by RSS')).toBeVisible();
|
||||
});
|
||||
it('renders Telegram subscription button', () => {
|
||||
setup({ user }, { simple_view: true, telegram_notifications: true });
|
||||
expect(screen.getByText(/Subscribe by/)).toBeVisible();
|
||||
expect(screen.getByTitle('Subscribe by Telegram')).toBeVisible();
|
||||
});
|
||||
it('renders OR if telegram and RSS are enabled', () => {
|
||||
setup({ user }, { simple_view: true, telegram_notifications: true, email_notifications: false });
|
||||
// I can not use testing-library to check 2 elements with OR exists, because both of them are in the same DOM element
|
||||
const container = screen.getByText(/ or/);
|
||||
const regex = /\bor\b/g;
|
||||
const matchCount = (container.textContent?.match(regex) || []).length;
|
||||
|
||||
expect(matchCount).toBe(1);
|
||||
});
|
||||
it('renders 2 OR if telegram and RSS and email are enabled', () => {
|
||||
setup({ user }, { simple_view: true, telegram_notifications: true, email_notifications: true });
|
||||
const container = screen.getByText(/ or/);
|
||||
const regex = /\bor\b/g;
|
||||
const matchCount = (container.textContent?.match(regex) || []).length;
|
||||
|
||||
expect(matchCount).toBe(2);
|
||||
});
|
||||
});
|
||||
it('renders without email subscription button when email_notifications disabled', () => {
|
||||
setup({ user }, { email_notifications: false });
|
||||
expect(screen.queryByText('Subscribe by RSS')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTitle('Subscribe by Email')).not.toBeInTheDocument();
|
||||
});
|
||||
it('renders Telegram subscription button', () => {
|
||||
setup({ user }, { telegram_notifications: true });
|
||||
expect(screen.getByText(/Subscribe by/)).toBeVisible();
|
||||
expect(screen.getByTitle('Subscribe by Telegram')).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders without Telegram subscription button if telegram_notifications is false', () => {
|
||||
setup({ user }, { telegram_notifications: false });
|
||||
expect(screen.queryByTitle('Subscribe by Telegram')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TextareaAutosize } from 'components/textarea-autosize';
|
||||
import { Auth } from 'components/auth';
|
||||
|
||||
import { SubscribeByEmail } from './__subscribe-by-email';
|
||||
import { SubscribeByTelegram } from './__subscribe-by-telegram';
|
||||
import { SubscribeByRSS } from './__subscribe-by-rss';
|
||||
|
||||
import { MarkdownToolbar } from './markdown-toolbar';
|
||||
@@ -376,9 +377,11 @@ export class CommentForm extends Component<Props, State> {
|
||||
renderSubscribeButtons = () => {
|
||||
const isEmailNotifications = StaticStore.config.email_notifications;
|
||||
const isEmailSubscription = isEmailNotifications && settings.isEmailSubscription;
|
||||
const { isRssSubscription } = settings;
|
||||
const isTelegramNotificationsEnabledOnBackend = StaticStore.config.telegram_notifications;
|
||||
const isTelegramSubscription = isTelegramNotificationsEnabledOnBackend && settings.isTelegramSubscription;
|
||||
|
||||
if (!isRssSubscription && !isEmailSubscription) {
|
||||
const { isRssSubscription } = settings;
|
||||
if (!isRssSubscription && !isEmailSubscription && !isTelegramSubscription) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -393,6 +396,13 @@ export class CommentForm extends Component<Props, State> {
|
||||
</>
|
||||
)}
|
||||
{isEmailSubscription && <SubscribeByEmail />}
|
||||
{(isRssSubscription && isTelegramSubscription) || (isEmailSubscription && isTelegramSubscription) ? (
|
||||
<>
|
||||
{' '}
|
||||
<FormattedMessage id="commentForm.subscribe-or" defaultMessage="or" />{' '}
|
||||
</>
|
||||
) : null}
|
||||
{isTelegramSubscription && <SubscribeByTelegram />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
.root_theme_dark {
|
||||
color: var(--color20);
|
||||
|
||||
& a {
|
||||
color: var(--color20);
|
||||
}
|
||||
|
||||
& a:hover {
|
||||
color: var(--color6);
|
||||
}
|
||||
|
||||
& a:visited {
|
||||
color: var(--color39);
|
||||
}
|
||||
|
||||
& a:active {
|
||||
color: var(--color39);
|
||||
}
|
||||
|
||||
& .root__copyright {
|
||||
color: var(--color1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.telegram {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.telegramQR {
|
||||
background-color: rgb(var(--white-color));
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 12px auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { screen } from '@testing-library/preact';
|
||||
|
||||
import { render } from 'tests/utils';
|
||||
|
||||
import { TelegramLink } from './telegram-link';
|
||||
|
||||
const handleSubmitSpy = jest.fn();
|
||||
|
||||
const defaultProps = {
|
||||
bot: 'remark42_bot',
|
||||
token: '718b5a43fb18136beb273e9261dd895578d00f63',
|
||||
onSubmit: handleSubmitSpy,
|
||||
};
|
||||
|
||||
describe('<TelegramLink/>', () => {
|
||||
let widthBackup: number;
|
||||
beforeAll(() => {
|
||||
widthBackup = window.screen.width;
|
||||
});
|
||||
afterAll(() => {
|
||||
Object.defineProperties(window.screen, {
|
||||
width: {
|
||||
writable: true,
|
||||
value: widthBackup!,
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should render text', () => {
|
||||
render(<TelegramLink {...defaultProps} />);
|
||||
expect(screen.getByText(/Open Telegram/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/by the link/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/and click “Start” there./)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Afterwards, click “Check” below./)).toBeInTheDocument();
|
||||
});
|
||||
it('should show QR code on desktop', () => {
|
||||
Object.defineProperties(window.screen, {
|
||||
width: {
|
||||
writable: true,
|
||||
value: 1000,
|
||||
},
|
||||
});
|
||||
render(<TelegramLink {...defaultProps} />);
|
||||
expect(screen.getByText(/or by scanning the QR code/)).toBeInTheDocument();
|
||||
expect(screen.getByAltText(/Telegram QR-code/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show contain correct QR src', () => {
|
||||
Object.defineProperties(window.screen, {
|
||||
width: {
|
||||
writable: true,
|
||||
value: 1000,
|
||||
},
|
||||
});
|
||||
render(<TelegramLink {...defaultProps} />);
|
||||
expect(screen.getByAltText(/Telegram QR-code/)).toHaveAttribute(
|
||||
'src',
|
||||
`http://test.com/api/v1/qr/telegram?url=https://t.me/${defaultProps.bot}/?start=${defaultProps.token}`
|
||||
);
|
||||
});
|
||||
it('should NOT show QR code on mobile', () => {
|
||||
Object.defineProperties(window.screen, {
|
||||
width: {
|
||||
writable: true,
|
||||
value: 500,
|
||||
},
|
||||
});
|
||||
render(<TelegramLink {...defaultProps} />);
|
||||
expect(screen.queryByAltText(/Telegram QR-code/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show error if any', () => {
|
||||
render(<TelegramLink {...defaultProps} errorMessage="Foo Error" />);
|
||||
expect(screen.getByText('Foo Error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import clsx from 'clsx';
|
||||
import { h, Fragment, FunctionComponent } from 'preact';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { messages } from './telegram.messages';
|
||||
import { BASE_URL, API_BASE } from 'common/constants.config';
|
||||
import { Button } from 'components/button';
|
||||
|
||||
import styles from './telegram-link.module.css';
|
||||
|
||||
export type TelegramLinkProps = {
|
||||
bot: string;
|
||||
token: string;
|
||||
onSubmit: (evt: Event) => void;
|
||||
errorMessage?: string | null;
|
||||
};
|
||||
|
||||
export const TelegramLink: FunctionComponent<TelegramLinkProps> = ({ bot, token, errorMessage, onSubmit }) => {
|
||||
const intl = useIntl();
|
||||
const telegramLink = `https://t.me/${bot}/?start=${token}`;
|
||||
return (
|
||||
<>
|
||||
<p className={clsx('telegram', styles.telegram)}>
|
||||
{intl.formatMessage(messages.telegramMessage1)}{' '}
|
||||
<a target="_blank" rel="noopener noreferrer" href={telegramLink}>
|
||||
{intl.formatMessage(messages.telegramLink)}
|
||||
</a>
|
||||
{window.screen.width >= 768 && ` ${intl.formatMessage(messages.telegramOptionalQR)}`}{' '}
|
||||
{intl.formatMessage(messages.telegramMessage2)}
|
||||
<br />
|
||||
{intl.formatMessage(messages.telegramMessage3)}
|
||||
</p>
|
||||
{window.screen.width >= 768 && (
|
||||
<img
|
||||
src={`${BASE_URL}${API_BASE}/qr/telegram?url=${telegramLink}`}
|
||||
height="200"
|
||||
width="200"
|
||||
className={clsx('telegram-qr', styles.telegramQR)}
|
||||
alt={intl.formatMessage(messages.telegramQR)}
|
||||
/>
|
||||
)}
|
||||
{errorMessage && <div className={clsx('auth-error', styles.error)}>{errorMessage}</div>}
|
||||
<Button
|
||||
key="submit"
|
||||
kind="primary"
|
||||
size="large"
|
||||
className={clsx('auth-submit', styles.button)}
|
||||
type="submit"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{intl.formatMessage(messages.telegramCheck)}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export const messages = defineMessages<string>({
|
||||
telegramMessage1: {
|
||||
id: 'auth.telegram-message-1',
|
||||
defaultMessage: 'Open Telegram',
|
||||
},
|
||||
telegramLink: {
|
||||
id: 'auth.telegram-link',
|
||||
defaultMessage: 'by the link',
|
||||
},
|
||||
telegramMessage2: {
|
||||
id: 'auth.telegram-message-2',
|
||||
defaultMessage: 'and click “Start” there.',
|
||||
},
|
||||
telegramMessage3: {
|
||||
id: 'auth.telegram-message-3',
|
||||
defaultMessage: 'Afterwards, click “Check” below.',
|
||||
},
|
||||
telegramOptionalQR: {
|
||||
id: 'auth.telegram-optional-qr',
|
||||
defaultMessage: 'or by scanning the QR code',
|
||||
},
|
||||
telegramQR: {
|
||||
id: 'auth.telegram-qr',
|
||||
defaultMessage: 'Telegram QR-code',
|
||||
},
|
||||
telegramCheck: {
|
||||
id: 'auth.telegram-check',
|
||||
defaultMessage: 'Check',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { renderHook } from '@testing-library/preact-hooks';
|
||||
|
||||
import { useSessionStorage } from './useSessionState';
|
||||
|
||||
describe('useSessionStorage', () => {
|
||||
it('should return a value and a setter', () => {
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current!).toHaveLength(2);
|
||||
expect(result.current![0]).toBe(0);
|
||||
expect(result.current![1]).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('should return the initial value', () => {
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current![0]).toBe(0);
|
||||
});
|
||||
|
||||
it('should return the stored value', () => {
|
||||
sessionStorage.setItem('test', JSON.stringify(1));
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current![0]).toBe(1);
|
||||
});
|
||||
|
||||
it('should return the stored value if it is falsy', () => {
|
||||
sessionStorage.setItem('test', JSON.stringify(false));
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current![0]).toBe(false);
|
||||
});
|
||||
|
||||
it('should return the initial value if the stored value is not valid JSON', () => {
|
||||
sessionStorage.setItem('test', 'not valid JSON');
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current![0]).toBe(0);
|
||||
});
|
||||
|
||||
it('should return null if the stored value is null', () => {
|
||||
// @ts-ignore
|
||||
sessionStorage.setItem('test', null);
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current![0]).toBe(null);
|
||||
});
|
||||
|
||||
it('should return the initial value if the stored value is undefined', () => {
|
||||
// @ts-ignore
|
||||
sessionStorage.setItem('test', undefined);
|
||||
const { result } = renderHook(() => useSessionStorage('test', 0));
|
||||
expect(result.current![0]).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useState, StateUpdater } from 'preact/hooks';
|
||||
|
||||
function useSessionStorage<T>(key: string, initialValue?: T): [T, StateUpdater<T>] {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
const item = sessionStorage.getItem(key);
|
||||
if (item === null) {
|
||||
return initialValue;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(item);
|
||||
} catch {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
const setValue: typeof setStoredValue = (value) => {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
};
|
||||
return [storedValue, setValue];
|
||||
}
|
||||
|
||||
export { useSessionStorage };
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "المستخدم محظور",
|
||||
"errors.8": "لا يمكن نشر تعليقات على هذه الصفحة. التعليقات مؤرشفة للقراءة فقط.",
|
||||
"errors.9": "فشل تعديل التعليق. فضلاً، عاود المحاولة.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "فشل الاتصال. فضلاً تأكد من اتصالك بالإنترنت.",
|
||||
"errors.forbidden": "ممنوع.",
|
||||
"errors.not-authorized": "غير مخوّل بالدخول.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "الموقع",
|
||||
"subscribeByRSS.thread": "سلسلة ردود",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "انسخ والصق الرمز المميز من البريد الإلكتروني",
|
||||
"token.expired": "الرمز انتهت صلاحيته",
|
||||
"token.invalid": "الرمز غير صالح",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Карыстальнік заблакаваны.",
|
||||
"errors.8": "Карыстальнік заблакаваны.",
|
||||
"errors.9": "Не атрымалася захаваць змяненні. Калі ласка, паспрабуйце яшчэ раз крыху пазней.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Няма адказу сервера. Праверце сваё злучэнне з інтэрнэтам або паспрабуйце яшчэ раз крыху пазней.r",
|
||||
"errors.forbidden": "Забаронена.",
|
||||
"errors.not-authorized": "Не аўтарызаваны.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Сайт",
|
||||
"subscribeByRSS.thread": "Гутарка",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Скапіруйце і ўстаўце токен з электроннага ліста",
|
||||
"token.expired": "Час дзеяння токена сышоў",
|
||||
"token.invalid": "Токен несапраўдны",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Потребителя бе блокиран.",
|
||||
"errors.8": "Потребителя бе блокиран.",
|
||||
"errors.9": "Промяната на коментара е неуспешна. Моля, опитайте отново по-късно.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Неуспешно извличане. Моля, проверете вашата интернет връзка или опитайте отново по-късно",
|
||||
"errors.forbidden": "Забранено.",
|
||||
"errors.not-authorized": "Неоторизиран.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Сайт",
|
||||
"subscribeByRSS.thread": "Нишка",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Копирайте и поставете токена от имейла",
|
||||
"token.expired": "Изтекъл токен",
|
||||
"token.invalid": "Токенът е невалиден",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "O usuário foi bloqueado.",
|
||||
"errors.8": "O usuário foi bloqueado.",
|
||||
"errors.9": "A alteração do comentário falhou. Tente novamente um pouco mais tarde.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Falha ao buscar. Verifique sua conexão com a Internet ou tente novamente mais tarde",
|
||||
"errors.forbidden": "Proibido.",
|
||||
"errors.not-authorized": "Não autorizado.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Site",
|
||||
"subscribeByRSS.thread": "Tópico",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Copie e cole o token do e-mail",
|
||||
"token.expired": "O token expirou",
|
||||
"token.invalid": "Token inválido",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Uživatel byl zablokován..",
|
||||
"errors.8": "Nelze přidávat komentáře, protože jsou pouze pro čtení",
|
||||
"errors.9": "Editace komentáře se nezdařila. Zkuste to prosím později.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Nepodařilo se načíst. Zkontrolujte prosím své internetové připojení nebo to zkuste později.",
|
||||
"errors.forbidden": "Zakázáno.",
|
||||
"errors.not-authorized": "Nejste přihlášen.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Web",
|
||||
"subscribeByRSS.thread": "Vlákno",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Zkopírujte a vložte token z e-mailu",
|
||||
"token.expired": "Token expiroval",
|
||||
"token.invalid": "Token je neplatný",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Benutzer wurde gesperrt.",
|
||||
"errors.8": "Benutzer wurde gesperrt.",
|
||||
"errors.9": "Änderungen konnten nicht gespeichert werden. Bitte versuchen Sie es später noch einmal.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Abruf fehlgeschlagen. Bitte prüfen Sie Ihre Internetverbindung oder versuchen Sie es etwas später erneut",
|
||||
"errors.forbidden": "Verboten.",
|
||||
"errors.not-authorized": "Nicht autorisiert.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Site",
|
||||
"subscribeByRSS.thread": "Thread",
|
||||
"subscribeByRSS.title": "RSS-Feed",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Token aus der E-Mail kopieren und einfügen",
|
||||
"token.expired": "Token ist abgelaufen",
|
||||
"token.invalid": "Token ist ungültig",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "User has been blocked.",
|
||||
"errors.8": "Can't post comments on this page. Comments are read only.",
|
||||
"errors.9": "Comment changing failed. Please try again a bit later.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Failed to fetch. Please check your internet connection or try again a bit later",
|
||||
"errors.forbidden": "Forbidden.",
|
||||
"errors.not-authorized": "Not authorized.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Site",
|
||||
"subscribeByRSS.thread": "Thread",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Copy and paste the token from the email",
|
||||
"token.expired": "Token is expired",
|
||||
"token.invalid": "Token is invalid",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "El usuario ha sido bloqueado.",
|
||||
"errors.8": "El usuario ha sido bloqueado.",
|
||||
"errors.9": "No se ha podido cambiar el comentario. Por favor vuelve a intentar más tarde.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "No se ha podido obtener. Por favor revisa tu conexión a internet o vuelve a intentar más tarde",
|
||||
"errors.forbidden": "Prohibido.",
|
||||
"errors.not-authorized": "No autorizado.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Sitio",
|
||||
"subscribeByRSS.thread": "Hilo",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Copie y pegue el token del mensaje del correo electrónico",
|
||||
"token.expired": "Token expirado",
|
||||
"token.invalid": "El token no es válido",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Käyttäjä on estetty.",
|
||||
"errors.8": "Käyttäjä on estetty.",
|
||||
"errors.9": "Kommentin vaihtaminen epäonnistui. Yritä uudelleen myöhemmin.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Nouto epäonnistui. Tarkista internet-yhteytesi tai yritä uudelleen myöhemmin.",
|
||||
"errors.forbidden": "Kieletty.",
|
||||
"errors.not-authorized": "Ei sallittu.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Sivusto",
|
||||
"subscribeByRSS.thread": "Teema",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Kopioi ja liitä tunniste sähköpostista",
|
||||
"token.expired": "Tunnus on vanhentunut",
|
||||
"token.invalid": "Token is invalid",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "L'utilisateur a été bloqué.",
|
||||
"errors.8": "L'utilisateur a été bloqué.",
|
||||
"errors.9": "La modification du commentaire a échoué. Veuillez réessayer un peu plus tard.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Impossible de récupérer les donnés. Veuillez vérifier votre connexion Internet ou réessayez plus tard",
|
||||
"errors.forbidden": "Interdit.",
|
||||
"errors.not-authorized": "Non autorisé.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Site",
|
||||
"subscribeByRSS.thread": "Fil d'actualités",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Copiez et collez le jeton depuis l'e-mail",
|
||||
"token.expired": "Le jeton a expiré",
|
||||
"token.invalid": "Le jeton n'est pas valide",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "L'utente è stato bloccato.",
|
||||
"errors.8": "Impossibile commentare su questa pagina, i commenti sono in sola lettura.",
|
||||
"errors.9": "Modifica del commento non riuscita. Riprova tra poco.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Aggiornamento fallito. Verifica la tua connessione a internet o riprova più tardi.",
|
||||
"errors.forbidden": "Vietato.",
|
||||
"errors.not-authorized": "Non autorizzato.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Sito",
|
||||
"subscribeByRSS.thread": "Argomento",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Copiare e incollare il token dall'e-mail",
|
||||
"token.expired": "Il token è scaduto",
|
||||
"token.invalid": "Il token non è valido",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "ユーザーはブロックされました。",
|
||||
"errors.8": "ユーザーはブロックされました。",
|
||||
"errors.9": "コメントの変更に失敗しました。しばらくしてからもう一度お試しください。",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "取得できませんでした。インターネット接続を確認するか、しばらくしてからもう一度お試しください。",
|
||||
"errors.forbidden": "禁止されています.",
|
||||
"errors.not-authorized": "認証されていません。",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "サイト",
|
||||
"subscribeByRSS.thread": "スレッド",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "メールからトークンをコピーして貼り付ける",
|
||||
"token.expired": "トークンの有効期限が切れています",
|
||||
"token.invalid": "トークンが無効です",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "사용자가 차단되었습니다.",
|
||||
"errors.8": "사용자가 차단되었습니다.",
|
||||
"errors.9": "댓글 변경이 실패했습니다. 잠시 후 다시 시도해 주세요.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "가져오기가 실패했습니다. 인터넷 연결을 확인하거나 잠시 후 다시 시도해 주세요.",
|
||||
"errors.forbidden": "허용되지 않습니다.",
|
||||
"errors.not-authorized": "권한이 없습니다.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "사이트",
|
||||
"subscribeByRSS.thread": "스레드",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "이메일에서 토큰을 복사 및 붙여넣기",
|
||||
"token.expired": "토큰이 만료되었습니다",
|
||||
"token.invalid": "토큰이 유효하지 않습니다",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Użytkownik został zablokowany.",
|
||||
"errors.8": "Użytkownik został zablokowany.",
|
||||
"errors.9": "Edycja komentarza nie powiodła się. Spróbuj ponownie później.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Nie udało się pobrać. Sprawdź swoje połączenie internetowe i spróbuj ponownie później",
|
||||
"errors.forbidden": "Zabronione.",
|
||||
"errors.not-authorized": "Nie jesteś upoważniony.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Strona",
|
||||
"subscribeByRSS.thread": "Wątek",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Skopiuj i wklej token z wiadomości e-mail",
|
||||
"token.expired": "Wygasły token",
|
||||
"token.invalid": "Token jest nieprawidłowy.",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Пользователь был заблокирован.",
|
||||
"errors.8": "Пользователь был заблокирован.",
|
||||
"errors.9": "Не удалось сохранить изменения. Попробуйте еще раз позже.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Сервер не отвечает. Проверьте подключение к Интернету или попробуйте позже.",
|
||||
"errors.forbidden": "Запрещено.",
|
||||
"errors.not-authorized": "Не авторизован.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Сайт",
|
||||
"subscribeByRSS.thread": "Ветка",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Скопируйте токен из электронного письма",
|
||||
"token.expired": "Время действия токена истекло",
|
||||
"token.invalid": "Токен недействителен",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "ผู้ใช้ถูกบล็อก",
|
||||
"errors.8": "ไม่สามารถแสดงความคิดเห็นในหน้านี้ สำหรับอ่านเท่านั้น",
|
||||
"errors.9": "การเปลี่ยนความคิดเห็นล้มเหลว โปรดลองอีกครั้งในภายหลัง",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "ดึงข้อมูลไม่สำเร็จ โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณหรือลองอีกครั้งในภายหลัง",
|
||||
"errors.forbidden": "ปฏิเสธ",
|
||||
"errors.not-authorized": "ไม่มีสิทธิ์เข้าถึง",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "เว็บไซต์",
|
||||
"subscribeByRSS.thread": "เทรด",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "คัดลอกและวางโทเค็นจากอีเมล",
|
||||
"token.expired": "โทเค็นหมดอายุ",
|
||||
"token.invalid": "โทเค็นไม่ถูกต้อง",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Kullanıcı engellendi.",
|
||||
"errors.8": "Kullanıcı engellendi.",
|
||||
"errors.9": "Yorum değiştirilemedi. Lütfen daha sonra tekrar deneyin.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "İçerik yüklenemedi. Lütfen internet bağlantınızı kontrol edin veya daha sonra tekrar deneyin.",
|
||||
"errors.forbidden": "İzin yok.",
|
||||
"errors.not-authorized": "Yetki yetersiz.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Site",
|
||||
"subscribeByRSS.thread": "Başlık",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "E-postadaki belirteci kopyalayıp yapıştırın",
|
||||
"token.expired": "Belirtecin süresi doldu",
|
||||
"token.invalid": "Belirteç geçersiz",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Користувач був заблокований.",
|
||||
"errors.8": "Користувач був заблокований.",
|
||||
"errors.9": "Не вдалося зберегти зміни.Спробуйте ще раз пізніше.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Немає відповіді від сервера. Перевірте ваше з’єднання з інтернетом або спробуйте пізніше.",
|
||||
"errors.forbidden": "Заборонено.",
|
||||
"errors.not-authorized": "Не авторизований.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Сайт",
|
||||
"subscribeByRSS.thread": "Гілка",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Скопіюйте та вставте токен з листа",
|
||||
"token.expired": "Час дії токена минув",
|
||||
"token.invalid": "Токен недійсний",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "Người dùng đã bị chặn.",
|
||||
"errors.8": "Người dùng đã bị chặn.",
|
||||
"errors.9": "Thay đổi bình luận không thành công. Một chút sau rồi thử lại nhé.",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "Nhận dữ liệu thất bại. Vui lòng kiểm tra lại đường truyền hoặc thử lại sau.",
|
||||
"errors.forbidden": "Bị cấm.",
|
||||
"errors.not-authorized": "Không được phép.",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "Trang",
|
||||
"subscribeByRSS.thread": "Chủ đề",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "Sao chép và dán token từ email",
|
||||
"token.expired": "token đã hết hạn",
|
||||
"token.invalid": "Token không hợp lệ",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "使用者已被封鎖",
|
||||
"errors.8": "留言功能為唯獨模式,無法在此頁面新增留言。",
|
||||
"errors.9": "留言更改失敗,請稍後再嘗試。",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "請求失敗,請確認您的網路連線功能後再重新嘗試。",
|
||||
"errors.forbidden": "禁止的。",
|
||||
"errors.not-authorized": "未授權的。",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "網站",
|
||||
"subscribeByRSS.thread": "Thread",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "從電子郵件中復制並粘貼 token",
|
||||
"token.expired": "Token 已過期",
|
||||
"token.invalid": "無效的 Token",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"errors.7": "用户已被封禁。",
|
||||
"errors.8": "用户已被封禁。",
|
||||
"errors.9": "评论更改失败,请稍后再试。",
|
||||
"errors.conflict": "Conflict.",
|
||||
"errors.failed-fetch": "请求失败,请检查您的网络连接或稍候重试。",
|
||||
"errors.forbidden": "禁止",
|
||||
"errors.not-authorized": "未经授权。",
|
||||
@@ -152,6 +153,13 @@
|
||||
"subscribeByRSS.site": "网站",
|
||||
"subscribeByRSS.thread": "线程",
|
||||
"subscribeByRSS.title": "RSS",
|
||||
"subscribeByTelegram.have-been-subscribed": "You have been subscribed on updates by telegram",
|
||||
"subscribeByTelegram.have-been-unsubscribed": "You have been unsubscribed by telegram to updates",
|
||||
"subscribeByTelegram.only-registered-users": "Available only for registered users",
|
||||
"subscribeByTelegram.resubscribe": "Resubscribe",
|
||||
"subscribeByTelegram.subscribe-by-telegram": "Subscribe by Telegram",
|
||||
"subscribeByTelegram.telegram": "Telegram",
|
||||
"subscribeByTelegram.unsubscribe": "Unsubscribe",
|
||||
"token": "从电子邮件中复制并粘贴令牌",
|
||||
"token.expired": "令牌已过期",
|
||||
"token.invalid": "令牌无效",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
--color38: #ef0000;
|
||||
--color27: #f98989;
|
||||
--color26: #ffd7d7;
|
||||
--color39: #6d4b8d;
|
||||
--color30: 204, 6, 6;
|
||||
--color12: 37, 158, 6;
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ type RemarkConfig = {
|
||||
// if you set this param in 'false' you will get notifications email notifications as admin but your users
|
||||
// won't have interface for subscription
|
||||
show_email_subscription?: boolean;
|
||||
// Optional, 'true' by default. Enables telegram subscription feature in interface when enable it from backend side,
|
||||
// if you set this param in 'false' you will get telegram notifications as admin but your users
|
||||
// won't have interface for subscription
|
||||
show_telegram_subscription?: boolean;
|
||||
// Optional, 'true' by default. Enables RSS subscription feature in interface.
|
||||
show_rss_subscription?: boolean;
|
||||
// Optional, 'false' by default. Overrides the parameter from the backend minimized UI with basic info only.
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"@swc/jest": "^0.2.21",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/preact": "^3.2.2",
|
||||
"@testing-library/preact-hooks": "^1.1.0",
|
||||
"@types/enzyme": "^3.10.12",
|
||||
"@types/eslint": "^8.4.5",
|
||||
"@types/jest": "^28.1.4",
|
||||
|
||||
Generated
+12
@@ -31,6 +31,7 @@ importers:
|
||||
'@swc/jest': ^0.2.21
|
||||
'@testing-library/jest-dom': ^5.16.4
|
||||
'@testing-library/preact': ^3.2.2
|
||||
'@testing-library/preact-hooks': ^1.1.0
|
||||
'@types/enzyme': ^3.10.12
|
||||
'@types/eslint': ^8.4.5
|
||||
'@types/jest': ^28.1.4
|
||||
@@ -145,6 +146,7 @@ importers:
|
||||
'@swc/jest': 0.2.21_@swc+core@1.2.205
|
||||
'@testing-library/jest-dom': 5.16.4
|
||||
'@testing-library/preact': 3.2.2_preact@10.6.2
|
||||
'@testing-library/preact-hooks': 1.1.0_3jy4ehlhhntshptwdxmaawp6ue
|
||||
'@types/enzyme': 3.10.12
|
||||
'@types/eslint': 8.4.5
|
||||
'@types/jest': 28.1.5
|
||||
@@ -2623,6 +2625,16 @@ packages:
|
||||
redent: 3.0.0
|
||||
dev: true
|
||||
|
||||
/@testing-library/preact-hooks/1.1.0_3jy4ehlhhntshptwdxmaawp6ue:
|
||||
resolution: {integrity: sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ==}
|
||||
peerDependencies:
|
||||
'@testing-library/preact': ^2.0.0
|
||||
preact: ^10.4.8
|
||||
dependencies:
|
||||
'@testing-library/preact': 3.2.2_preact@10.6.2
|
||||
preact: 10.6.2
|
||||
dev: true
|
||||
|
||||
/@testing-library/preact/3.2.2_preact@10.6.2:
|
||||
resolution: {integrity: sha512-mMPEp/9TOOqf3QqDHY02ieGFfRbi8fAxZvRifn+vOzrdNcCR1zchwPA6BvqXG3wAweRan4QJioYgEc1cePeC3g==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
Reference in New Issue
Block a user