mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-11-23 11:49:19 +01:00
Merge branch 'main' into web-improvements
This commit is contained in:
commit
2697600111
13 changed files with 256 additions and 379 deletions
|
@ -1222,8 +1222,13 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||||
|
|
||||||
### ntfy server v2.6.0 (UNRELEASED)
|
### ntfy server v2.6.0 (UNRELEASED)
|
||||||
|
|
||||||
**Bug fixes + maintenance:**
|
**Bug fixes:**
|
||||||
|
|
||||||
* Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting)
|
* Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting)
|
||||||
|
|
||||||
|
**Maintenance:**
|
||||||
|
|
||||||
* Improved GitHub Actions flow ([#745](https://github.com/binwiederhier/ntfy/pull/745), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
* Improved GitHub Actions flow ([#745](https://github.com/binwiederhier/ntfy/pull/745), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||||
* Web: Add JS formatter "prettier" ([#746](https://github.com/binwiederhier/ntfy/pull/746), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
* Web: Add JS formatter "prettier" ([#746](https://github.com/binwiederhier/ntfy/pull/746), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||||
|
* Web: Add eslint with eslint-config-airbnb ([#748](https://github.com/binwiederhier/ntfy/pull/748), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||||
|
* Web: Switch to Vite ([#749](https://github.com/binwiederhier/ntfy/pull/749), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||||
|
|
|
@ -20,6 +20,12 @@
|
||||||
"react/destructuring-assignment": "off",
|
"react/destructuring-assignment": "off",
|
||||||
"react/jsx-no-useless-fragment": "off",
|
"react/jsx-no-useless-fragment": "off",
|
||||||
"react/jsx-props-no-spreading": "off",
|
"react/jsx-props-no-spreading": "off",
|
||||||
|
"react/jsx-no-duplicate-props": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoreCase": false // For <TextField>'s [iI]nputProps
|
||||||
|
}
|
||||||
|
],
|
||||||
"react/function-component-definition": [
|
"react/function-component-definition": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|
500
web/package-lock.json
generated
500
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,6 +11,8 @@
|
||||||
"lint": "eslint --report-unused-disable-directives --ext .js,.jsx ./src/"
|
"lint": "eslint --report-unused-disable-directives --ext .js,.jsx ./src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.11.0",
|
||||||
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.4.2",
|
"@mui/icons-material": "^5.4.2",
|
||||||
"@mui/material": "latest",
|
"@mui/material": "latest",
|
||||||
"dexie": "^3.2.1",
|
"dexie": "^3.2.1",
|
||||||
|
|
|
@ -352,5 +352,24 @@
|
||||||
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "économisez jusqu'à {{discount}}%",
|
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "économisez jusqu'à {{discount}}%",
|
||||||
"account_upgrade_dialog_tier_price_per_month": "mois",
|
"account_upgrade_dialog_tier_price_per_month": "mois",
|
||||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} prélevé annuellement. Économisez {{save}}.",
|
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} prélevé annuellement. Économisez {{save}}.",
|
||||||
"account_upgrade_dialog_billing_contact_email": "Pour des questions concernant la facturation, merci de nous <Link>contacter</Link> directement."
|
"account_upgrade_dialog_billing_contact_email": "Pour des questions concernant la facturation, merci de nous <Link>contacter</Link> directement.",
|
||||||
|
"publish_dialog_call_label": "Appel téléphonique",
|
||||||
|
"account_basics_phone_numbers_title": "Numéros de téléphone",
|
||||||
|
"account_basics_phone_numbers_dialog_description": "Pour utiliser la fonctionnalité de notification par appels, vous devez ajouter et vérifier au moins un numéro de téléphone. La vérification peut se faire par SMS ou appel téléphonique.",
|
||||||
|
"account_basics_phone_numbers_description": "Pour des notifications par appel téléphoniques",
|
||||||
|
"account_basics_phone_numbers_no_phone_numbers_yet": "Pas encore de numéros de téléphone",
|
||||||
|
"account_basics_phone_numbers_copied_to_clipboard": "Numéro de téléphone copié dans le presse-papier",
|
||||||
|
"account_basics_phone_numbers_dialog_title": "Ajouter un numéro de téléphone",
|
||||||
|
"account_basics_phone_numbers_dialog_number_label": "Numéro de téléphone",
|
||||||
|
"account_basics_phone_numbers_dialog_number_placeholder": "Ex : +33701020304",
|
||||||
|
"account_basics_phone_numbers_dialog_verify_button_sms": "Envoyer un SMS",
|
||||||
|
"account_basics_phone_numbers_dialog_verify_button_call": "Appelez moi",
|
||||||
|
"account_basics_phone_numbers_dialog_code_label": "Code de vérification",
|
||||||
|
"account_basics_phone_numbers_dialog_code_placeholder": "Ex : 123456",
|
||||||
|
"account_basics_phone_numbers_dialog_check_verification_button": "Code de confirmarion",
|
||||||
|
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||||
|
"account_basics_phone_numbers_dialog_channel_call": "Appel",
|
||||||
|
"account_usage_calls_none": "Aucun appels téléphoniques ne peut être fait avec ce compte",
|
||||||
|
"publish_dialog_call_reset": "Supprimer les appels téléphoniques",
|
||||||
|
"publish_dialog_chip_call_label": "Appel téléphonique"
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,5 +295,62 @@
|
||||||
"account_usage_messages_title": "Опубліковані повідомлення",
|
"account_usage_messages_title": "Опубліковані повідомлення",
|
||||||
"account_usage_emails_title": "Надіслані електронні листи",
|
"account_usage_emails_title": "Надіслані електронні листи",
|
||||||
"account_usage_reservations_title": "Зарезервовані теми",
|
"account_usage_reservations_title": "Зарезервовані теми",
|
||||||
"account_usage_reservations_none": "Для цього облікового запису немає зарезервованих тем"
|
"account_usage_reservations_none": "Для цього облікового запису немає зарезервованих тем",
|
||||||
|
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} на файл",
|
||||||
|
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} загальне сховище",
|
||||||
|
"account_upgrade_dialog_tier_current_label": "Поточний",
|
||||||
|
"account_upgrade_dialog_tier_selected_label": "Вибране",
|
||||||
|
"account_upgrade_dialog_cancel_warning": "Це <strong> скасує вашу підписку</strong> і знизить версію вашого облікового запису {{date}}. У цю дату резервування тем, а також повідомлення, кешовані на сервері <strong>, буде видалено</strong>.",
|
||||||
|
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} зарезервовані теми",
|
||||||
|
"account_upgrade_dialog_tier_features_no_reservations": "Немає зарезервованих тем",
|
||||||
|
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} повідомлень в день",
|
||||||
|
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} електронний лист в день",
|
||||||
|
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} електронних листів в день",
|
||||||
|
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} телефонний дзвінок в день",
|
||||||
|
"account_upgrade_dialog_tier_features_calls_other": "{{дзвінки}} телефонних дзвінків в день",
|
||||||
|
"account_upgrade_dialog_tier_features_no_calls": "Без телефонних дзвінків",
|
||||||
|
"account_upgrade_dialog_tier_price_per_month": "місяць",
|
||||||
|
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} на рік. Рахунок виставляється щомісяця.",
|
||||||
|
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} виставляється щорічно. Збережіть {{save}}.",
|
||||||
|
"account_upgrade_dialog_billing_contact_email": "Якщо у вас виникли запитання щодо оплати, <Link>зв’яжіться з нами</Link> безпосередньо.",
|
||||||
|
"account_upgrade_dialog_billing_contact_website": "Якщо у вас виникли запитання щодо оплати, відвідайте наш <Link>веб-сайт</Link>.",
|
||||||
|
"account_upgrade_dialog_button_cancel_subscription": "Скасувати підписку",
|
||||||
|
"account_upgrade_dialog_button_update_subscription": "Оновити підписку",
|
||||||
|
"account_tokens_title": "Токени доступу",
|
||||||
|
"account_tokens_table_expires_header": "Термін дії закінчується",
|
||||||
|
"account_tokens_description": "Використовуйте токени доступу при публікації та підписці через ntfy API, щоб не надсилати свої облікові дані. Ознайомтеся з <Link>документацією</Link>, щоб дізнатися більше.",
|
||||||
|
"account_tokens_table_token_header": "Токен",
|
||||||
|
"account_tokens_table_never_expires": "Ніколи не закінчується",
|
||||||
|
"account_tokens_table_label_header": "Мітка",
|
||||||
|
"account_tokens_table_current_session": "Поточний сеанс браузера",
|
||||||
|
"account_tokens_table_last_access_header": "Останній доступ",
|
||||||
|
"account_tokens_table_copied_to_clipboard": "Токен доступу скопійовано",
|
||||||
|
"account_tokens_table_cannot_delete_or_edit": "Неможливо редагувати або видалити токен поточного сеансу",
|
||||||
|
"account_tokens_table_create_token_button": "Створити токен доступу",
|
||||||
|
"account_tokens_table_last_origin_tooltip": "З IP-адреси {{ip}} натисніть для пошуку",
|
||||||
|
"account_tokens_dialog_title_create": "Створити токен доступу",
|
||||||
|
"account_tokens_dialog_button_cancel": "Скасувати",
|
||||||
|
"account_tokens_dialog_title_edit": "Редагувати токен доступу",
|
||||||
|
"account_tokens_dialog_title_delete": "Видалити токен доступу",
|
||||||
|
"account_tokens_dialog_label": "Мітка, наприклад, сповіщення Radarr",
|
||||||
|
"account_tokens_dialog_button_create": "Створити токен",
|
||||||
|
"account_tokens_dialog_button_update": "Оновити токен",
|
||||||
|
"account_tokens_dialog_expires_label": "Термін дії токену доступу закінчується через",
|
||||||
|
"account_tokens_dialog_expires_x_hours": "Термін дії токена закінчується через {{hours}} годин",
|
||||||
|
"account_tokens_dialog_expires_x_days": "Термін дії токена закінчується через {{days}} днів",
|
||||||
|
"account_tokens_delete_dialog_description": "Перш ніж видалити токен доступу, переконайтеся, що жодна програма або скрипт не використовує його. <strong>Ця дія не може бути скасована</strong>.",
|
||||||
|
"prefs_users_description_no_sync": "Користувачі та паролі не синхронізуються з вашим акаунтом.",
|
||||||
|
"prefs_users_table_cannot_delete_or_edit": "Неможливо видалити або відредагувати користувача, який увійшов у систему",
|
||||||
|
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} зарезервована тема",
|
||||||
|
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} повідомлення в день",
|
||||||
|
"account_tokens_dialog_expires_unchanged": "Залишити термін придатності без змін",
|
||||||
|
"account_tokens_dialog_expires_never": "Термін дії токена ніколи не закінчується",
|
||||||
|
"account_tokens_delete_dialog_title": "Видалити токен доступу",
|
||||||
|
"account_tokens_delete_dialog_submit_button": "Видалити токен назавжди",
|
||||||
|
"account_upgrade_dialog_proration_info": "<strong>Пропорція</strong>: При переході з одного тарифного плану на інший різниця в ціні буде <strong>списана негайно</strong>. При переході на нижчий рівень залишок коштів буде використано для оплати майбутніх розрахункових періодів.",
|
||||||
|
"account_upgrade_dialog_reservations_warning_one": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, <strong>будь ласка, видаліть принаймні одне резервування</strong>. Ви можете видалити резервування в <Link>Налаштуваннях</Link>.",
|
||||||
|
"account_upgrade_dialog_reservations_warning_other": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, <strong>будь ласка, видаліть принаймні {{count}} резервувань</strong>. Ви можете видалити резервування в <Link>Налаштуваннях</Link>.",
|
||||||
|
"account_upgrade_dialog_button_cancel": "Скасувати",
|
||||||
|
"account_upgrade_dialog_button_redirect_signup": "Зареєструватися зараз",
|
||||||
|
"account_upgrade_dialog_button_pay_now": "Оплатити зараз і підписатися"
|
||||||
}
|
}
|
||||||
|
|
|
@ -994,6 +994,7 @@ const TokenDialog = (props) => {
|
||||||
|
|
||||||
const TokenDeleteDialog = (props) => {
|
const TokenDeleteDialog = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -1003,6 +1004,8 @@ const TokenDeleteDialog = (props) => {
|
||||||
console.log(`[Account] Error deleting token`, e);
|
console.log(`[Account] Error deleting token`, e);
|
||||||
if (e instanceof UnauthorizedError) {
|
if (e instanceof UnauthorizedError) {
|
||||||
session.resetAndRedirect(routes.login);
|
session.resetAndRedirect(routes.login);
|
||||||
|
} else {
|
||||||
|
setError(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1015,7 +1018,7 @@ const TokenDeleteDialog = (props) => {
|
||||||
<Trans i18nKey="account_tokens_delete_dialog_description" />
|
<Trans i18nKey="account_tokens_delete_dialog_description" />
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogFooter status>
|
<DialogFooter status={error}>
|
||||||
<Button onClick={props.onClose}>{t("common_cancel")}</Button>
|
<Button onClick={props.onClose}>{t("common_cancel")}</Button>
|
||||||
<Button onClick={handleSubmit} color="error">
|
<Button onClick={handleSubmit} color="error">
|
||||||
{t("account_tokens_delete_dialog_submit_button")}
|
{t("account_tokens_delete_dialog_submit_button")}
|
||||||
|
|
|
@ -27,14 +27,13 @@ export const AccountContext = createContext(null);
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [account, setAccount] = useState(null);
|
const [account, setAccount] = useState(null);
|
||||||
|
const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]);
|
||||||
const contextValue = useMemo(() => ({ account, setAccount }), [account, setAccount]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<AccountContext.Provider value={contextValue}>
|
<AccountContext.Provider value={accountMemo}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
|
@ -74,6 +74,8 @@ const EmojiPicker = (props) => {
|
||||||
inputProps={{
|
inputProps={{
|
||||||
role: "searchbox",
|
role: "searchbox",
|
||||||
"aria-label": t("emoji_picker_search_placeholder"),
|
"aria-label": t("emoji_picker_search_placeholder"),
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end" sx={{ display: search ? "" : "none" }}>
|
<InputAdornment position="end" sx={{ display: search ? "" : "none" }}>
|
||||||
<IconButton size="small" onClick={handleSearchClear} edge="end" aria-label={t("emoji_picker_search_clear")}>
|
<IconButton size="small" onClick={handleSearchClear} edge="end" aria-label={t("emoji_picker_search_clear")}>
|
||||||
|
|
|
@ -45,9 +45,8 @@ class ErrorBoundaryImpl extends React.Component {
|
||||||
// Fetch additional info and a better stack trace
|
// Fetch additional info and a better stack trace
|
||||||
StackTrace.fromError(error).then((stack) => {
|
StackTrace.fromError(error).then((stack) => {
|
||||||
console.error("[ErrorBoundary] Stacktrace fetched", stack);
|
console.error("[ErrorBoundary] Stacktrace fetched", stack);
|
||||||
const niceStack = `${error.toString()}\n${stack
|
const stackString = stack.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n");
|
||||||
.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`)
|
const niceStack = `${error.toString()}\n${stackString}`;
|
||||||
.join("\n")}`;
|
|
||||||
this.setState({ niceStack });
|
this.setState({ niceStack });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -383,23 +383,23 @@ const PublishDialog = (props) => {
|
||||||
"aria-label": t("publish_dialog_priority_label"),
|
"aria-label": t("publish_dialog_priority_label"),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[5, 4, 3, 2, 1].map((priorityMenuItem) => (
|
{[5, 4, 3, 2, 1].map((p) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={`priorityMenuItem${priorityMenuItem}`}
|
key={`priorityMenuItem${p}`}
|
||||||
value={priorityMenuItem}
|
value={p}
|
||||||
aria-label={t("notifications_priority_x", {
|
aria-label={t("notifications_priority_x", {
|
||||||
priority: priorityMenuItem,
|
priority: p,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
<img
|
<img
|
||||||
src={priorities[priorityMenuItem].file}
|
src={priorities[p].file}
|
||||||
style={{ marginRight: "8px" }}
|
style={{ marginRight: "8px" }}
|
||||||
alt={t("notifications_priority_x", {
|
alt={t("notifications_priority_x", {
|
||||||
priority: priorityMenuItem,
|
priority: p,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<div>{priorities[priorityMenuItem].label}</div>
|
<div>{priorities[p].label}</div>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
@ -832,7 +832,10 @@ const ExpandingTextField = (props) => {
|
||||||
variant="standard"
|
variant="standard"
|
||||||
sx={{ width: `${textWidth}px`, borderBottom: "none" }}
|
sx={{ width: `${textWidth}px`, borderBottom: "none" }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: { fontSize: theme.typography[props.variant].fontSize, paddingBottom: 0, paddingTop: 0 },
|
style: { fontSize: theme.typography[props.variant].fontSize },
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
style: { paddingBottom: 0, paddingTop: 0 },
|
||||||
"aria-label": props.placeholder,
|
"aria-label": props.placeholder,
|
||||||
}}
|
}}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
|
|
|
@ -247,6 +247,8 @@ const DisplayNameDialog = (props) => {
|
||||||
inputProps={{
|
inputProps={{
|
||||||
maxLength: 64,
|
maxLength: 64,
|
||||||
"aria-label": t("display_name_dialog_placeholder"),
|
"aria-label": t("display_name_dialog_placeholder"),
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton onClick={() => setDisplayName("")} edge="end">
|
<IconButton onClick={() => setDisplayName("")} edge="end">
|
||||||
|
|
|
@ -5,7 +5,7 @@ import react from "@vitejs/plugin-react";
|
||||||
export default defineConfig(() => ({
|
export default defineConfig(() => ({
|
||||||
build: {
|
build: {
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
assetsDir: "static/media"
|
assetsDir: "static/media",
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
|
Loading…
Reference in a new issue