mirror of
https://github.com/binwiederhier/ntfy.git
synced 2025-05-05 08:21:29 +02:00
Width, again
This commit is contained in:
parent
ca5d736a71
commit
c87549e71a
20 changed files with 194 additions and 37 deletions
|
@ -1,2 +1,3 @@
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
public/static/langs/
|
public/static/langs/
|
||||||
|
|
|
@ -45,6 +45,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 160
|
"printWidth": 140
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,8 @@
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
ntfy web requires JavaScript, but you can also use the
|
ntfy web requires JavaScript, but you can also use the
|
||||||
<a href="https://ntfy.sh/docs/subscribe/cli/">CLI</a> or <a href="https://ntfy.sh/docs/subscribe/phone/">Android/iOS app</a> to subscribe.
|
<a href="https://ntfy.sh/docs/subscribe/cli/">CLI</a> or <a href="https://ntfy.sh/docs/subscribe/phone/">Android/iOS app</a> to
|
||||||
|
subscribe.
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script src="%PUBLIC_URL%/config.js"></script>
|
<script src="%PUBLIC_URL%/config.js"></script>
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import { fetchLinesIterator, maybeWithAuth, topicShortUrl, topicUrl, topicUrlAuth, topicUrlJsonPoll, topicUrlJsonPollWithSince } from "./utils";
|
import {
|
||||||
|
fetchLinesIterator,
|
||||||
|
maybeWithAuth,
|
||||||
|
topicShortUrl,
|
||||||
|
topicUrl,
|
||||||
|
topicUrlAuth,
|
||||||
|
topicUrlJsonPoll,
|
||||||
|
topicUrlJsonPollWithSince,
|
||||||
|
} from "./utils";
|
||||||
import userManager from "./UserManager";
|
import userManager from "./UserManager";
|
||||||
import { fetchOrThrow } from "./errors";
|
import { fetchOrThrow } from "./errors";
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,9 @@ class Connection {
|
||||||
};
|
};
|
||||||
this.ws.onclose = (event) => {
|
this.ws.onclose = (event) => {
|
||||||
if (event.wasClean) {
|
if (event.wasClean) {
|
||||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
|
console.log(
|
||||||
|
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`
|
||||||
|
);
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
} else {
|
} else {
|
||||||
const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)];
|
const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)];
|
||||||
|
|
|
@ -74,7 +74,9 @@ class ConnectionManager {
|
||||||
);
|
);
|
||||||
this.connections.set(connectionId, connection);
|
this.connections.set(connectionId, connection);
|
||||||
console.log(
|
console.log(
|
||||||
`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${user ? user.username : "anonymous"})`
|
`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${
|
||||||
|
user ? user.username : "anonymous"
|
||||||
|
})`
|
||||||
);
|
);
|
||||||
connection.start();
|
connection.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import config from "./config";
|
||||||
import { Base64 } from "js-base64";
|
import { Base64 } from "js-base64";
|
||||||
|
|
||||||
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
||||||
export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://");
|
export const topicUrlWs = (baseUrl, topic) =>
|
||||||
|
`${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://");
|
||||||
export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
|
export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
|
||||||
export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`;
|
export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`;
|
||||||
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
|
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
|
||||||
|
|
|
@ -211,7 +211,10 @@ const ChangePasswordDialog = (props) => {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogFooter status={error}>
|
<DialogFooter status={error}>
|
||||||
<Button onClick={props.onClose}>{t("common_cancel")}</Button>
|
<Button onClick={props.onClose}>{t("common_cancel")}</Button>
|
||||||
<Button onClick={handleDialogSubmit} disabled={newPassword.length === 0 || currentPassword.length === 0 || newPassword !== confirmPassword}>
|
<Button
|
||||||
|
onClick={handleDialogSubmit}
|
||||||
|
disabled={newPassword.length === 0 || currentPassword.length === 0 || newPassword !== confirmPassword}
|
||||||
|
>
|
||||||
{t("account_basics_password_dialog_button_submit")}
|
{t("account_basics_password_dialog_button_submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
@ -288,7 +291,13 @@ const AccountType = () => {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{config.enable_payments && account.role === Role.USER && !account.billing?.subscription && (
|
{config.enable_payments && account.role === Role.USER && !account.billing?.subscription && (
|
||||||
<Button variant="outlined" size="small" startIcon={<CelebrationIcon sx={{ color: "#55b86e" }} />} onClick={handleUpgradeClick} sx={{ ml: 1 }}>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
startIcon={<CelebrationIcon sx={{ color: "#55b86e" }} />}
|
||||||
|
onClick={handleUpgradeClick}
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
>
|
||||||
{t("account_basics_tier_upgrade_button")}
|
{t("account_basics_tier_upgrade_button")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -303,7 +312,11 @@ const AccountType = () => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{config.enable_payments && (
|
{config.enable_payments && (
|
||||||
<UpgradeDialog key={`upgradeDialogFromAccount${upgradeDialogKey}`} open={upgradeDialogOpen} onCancel={() => setUpgradeDialogOpen(false)} />
|
<UpgradeDialog
|
||||||
|
key={`upgradeDialogFromAccount${upgradeDialogKey}`}
|
||||||
|
open={upgradeDialogOpen}
|
||||||
|
onCancel={() => setUpgradeDialogOpen(false)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{account.billing?.status === SubscriptionStatus.PAST_DUE && (
|
{account.billing?.status === SubscriptionStatus.PAST_DUE && (
|
||||||
|
@ -574,7 +587,11 @@ const Stats = () => {
|
||||||
</div>
|
</div>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={account.role === Role.USER && account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100}
|
value={
|
||||||
|
account.role === Role.USER && account.limits.reservations > 0
|
||||||
|
? normalize(account.stats.reservations, account.limits.reservations)
|
||||||
|
: 100
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Pref>
|
</Pref>
|
||||||
)}
|
)}
|
||||||
|
@ -602,7 +619,10 @@ const Stats = () => {
|
||||||
: t("account_usage_unlimited")}
|
: t("account_usage_unlimited")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<LinearProgress variant="determinate" value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100} />
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100}
|
||||||
|
/>
|
||||||
</Pref>
|
</Pref>
|
||||||
{config.enable_emails && (
|
{config.enable_emails && (
|
||||||
<Pref
|
<Pref
|
||||||
|
@ -629,7 +649,10 @@ const Stats = () => {
|
||||||
: t("account_usage_unlimited")}
|
: t("account_usage_unlimited")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<LinearProgress variant="determinate" value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100} />
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100}
|
||||||
|
/>
|
||||||
</Pref>
|
</Pref>
|
||||||
)}
|
)}
|
||||||
{config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && (
|
{config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && (
|
||||||
|
@ -833,7 +856,12 @@ const TokensTable = (props) => {
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{tokens.map((token) => (
|
{tokens.map((token) => (
|
||||||
<TableRow key={token.token} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
<TableRow key={token.token} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
||||||
<TableCell component="th" scope="row" sx={{ paddingLeft: 0, whiteSpace: "nowrap" }} aria-label={t("account_tokens_table_token_header")}>
|
<TableCell
|
||||||
|
component="th"
|
||||||
|
scope="row"
|
||||||
|
sx={{ paddingLeft: 0, whiteSpace: "nowrap" }}
|
||||||
|
aria-label={t("account_tokens_table_token_header")}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<span style={{ fontFamily: "Monospace", fontSize: "0.9rem" }}>{token.token.slice(0, 12)}</span>
|
<span style={{ fontFamily: "Monospace", fontSize: "0.9rem" }}>{token.token.slice(0, 12)}</span>
|
||||||
...
|
...
|
||||||
|
@ -893,7 +921,12 @@ const TokensTable = (props) => {
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Snackbar open={snackOpen} autoHideDuration={3000} onClose={() => setSnackOpen(false)} message={t("account_tokens_table_copied_to_clipboard")} />
|
<Snackbar
|
||||||
|
open={snackOpen}
|
||||||
|
autoHideDuration={3000}
|
||||||
|
onClose={() => setSnackOpen(false)}
|
||||||
|
message={t("account_tokens_table_copied_to_clipboard")}
|
||||||
|
/>
|
||||||
</Portal>
|
</Portal>
|
||||||
<TokenDialog key={`tokenDialogEdit${upsertDialogKey}`} open={upsertDialogOpen} token={selectedToken} onClose={handleDialogClose} />
|
<TokenDialog key={`tokenDialogEdit${upsertDialogKey}`} open={upsertDialogOpen} token={selectedToken} onClose={handleDialogClose} />
|
||||||
<TokenDeleteDialog open={deleteDialogOpen} token={selectedToken} onClose={handleDialogClose} />
|
<TokenDeleteDialog open={deleteDialogOpen} token={selectedToken} onClose={handleDialogClose} />
|
||||||
|
@ -958,7 +991,9 @@ const TokenDialog = (props) => {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogFooter status={error}>
|
<DialogFooter status={error}>
|
||||||
<Button onClick={props.onClose}>{t("account_tokens_dialog_button_cancel")}</Button>
|
<Button onClick={props.onClose}>{t("account_tokens_dialog_button_cancel")}</Button>
|
||||||
<Button onClick={handleSubmit}>{editMode ? t("account_tokens_dialog_button_update") : t("account_tokens_dialog_button_create")}</Button>
|
<Button onClick={handleSubmit}>
|
||||||
|
{editMode ? t("account_tokens_dialog_button_update") : t("account_tokens_dialog_button_create")}
|
||||||
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -98,7 +98,13 @@ const SettingsIcons = (props) => {
|
||||||
<IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} aria-label={t("action_bar_toggle_mute")}>
|
<IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} aria-label={t("action_bar_toggle_mute")}>
|
||||||
{subscription.mutedUntil ? <NotificationsOffIcon /> : <NotificationsIcon />}
|
{subscription.mutedUntil ? <NotificationsOffIcon /> : <NotificationsIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton color="inherit" size="large" edge="end" onClick={(ev) => setAnchorEl(ev.currentTarget)} aria-label={t("action_bar_toggle_action_menu")}>
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="large"
|
||||||
|
edge="end"
|
||||||
|
onClick={(ev) => setAnchorEl(ev.currentTarget)}
|
||||||
|
aria-label={t("action_bar_toggle_action_menu")}
|
||||||
|
>
|
||||||
<MoreVertIcon />
|
<MoreVertIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<SubscriptionPopup subscription={subscription} anchor={anchorEl} placement="right" onClose={() => setAnchorEl(null)} />
|
<SubscriptionPopup subscription={subscription} anchor={anchorEl} placement="right" onClose={() => setAnchorEl(null)} />
|
||||||
|
|
|
@ -99,7 +99,13 @@ const EmojiPicker = (props) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.keys(emojisByCategory).map((category) => (
|
{Object.keys(emojisByCategory).map((category) => (
|
||||||
<Category key={category} title={category} emojis={emojisByCategory[category]} search={searchFields} onPick={props.onEmojiPick} />
|
<Category
|
||||||
|
key={category}
|
||||||
|
title={category}
|
||||||
|
emojis={emojisByCategory[category]}
|
||||||
|
search={searchFields}
|
||||||
|
onPick={props.onEmojiPick}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -46,7 +46,9 @@ 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.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n");
|
const niceStack =
|
||||||
|
`${error.toString()}\n` +
|
||||||
|
stack.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n");
|
||||||
this.setState({ niceStack });
|
this.setState({ niceStack });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,9 @@ const Messaging = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{subscription && <MessageBar subscription={subscription} message={message} onMessageChange={setMessage} onOpenDialogClick={handleOpenDialogClick} />}
|
{subscription && (
|
||||||
|
<MessageBar subscription={subscription} message={message} onMessageChange={setMessage} onOpenDialogClick={handleOpenDialogClick} />
|
||||||
|
)}
|
||||||
<PublishDialog
|
<PublishDialog
|
||||||
key={`publishDialog${dialogKey}`} // Resets dialog when canceled/closed
|
key={`publishDialog${dialogKey}`} // Resets dialog when canceled/closed
|
||||||
openMode={dialogOpenMode}
|
openMode={dialogOpenMode}
|
||||||
|
@ -95,7 +97,12 @@ const MessageBar = (props) => {
|
||||||
<SendIcon />
|
<SendIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Snackbar open={snackOpen} autoHideDuration={3000} onClose={() => setSnackOpen(false)} message={t("message_bar_error_publishing")} />
|
<Snackbar
|
||||||
|
open={snackOpen}
|
||||||
|
autoHideDuration={3000}
|
||||||
|
onClose={() => setSnackOpen(false)}
|
||||||
|
message={t("message_bar_error_publishing")}
|
||||||
|
/>
|
||||||
</Portal>
|
</Portal>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
|
|
@ -108,7 +108,8 @@ const NavList = (props) => {
|
||||||
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
|
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
|
||||||
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
||||||
const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted;
|
const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted;
|
||||||
const navListPadding = showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ? "0" : "";
|
const navListPadding =
|
||||||
|
showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ? "0" : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -115,7 +115,12 @@ const NotificationList = (props) => {
|
||||||
{notifications.slice(0, count).map((notification) => (
|
{notifications.slice(0, count).map((notification) => (
|
||||||
<NotificationItem key={notification.id} notification={notification} onShowSnack={() => setSnackOpen(true)} />
|
<NotificationItem key={notification.id} notification={notification} onShowSnack={() => setSnackOpen(true)} />
|
||||||
))}
|
))}
|
||||||
<Snackbar open={snackOpen} autoHideDuration={3000} onClose={() => setSnackOpen(false)} message={t("notifications_copied_to_clipboard")} />
|
<Snackbar
|
||||||
|
open={snackOpen}
|
||||||
|
autoHideDuration={3000}
|
||||||
|
onClose={() => setSnackOpen(false)}
|
||||||
|
message={t("notifications_copied_to_clipboard")}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
|
@ -156,7 +161,11 @@ const NotificationItem = (props) => {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{notification.new === 1 && (
|
{notification.new === 1 && (
|
||||||
<Tooltip title={t("notifications_mark_read")} enterDelay={500}>
|
<Tooltip title={t("notifications_mark_read")} enterDelay={500}>
|
||||||
<IconButton onClick={handleMarkRead} sx={{ float: "right", marginRight: -0.5, marginTop: -1 }} aria-label={t("notifications_mark_read")}>
|
<IconButton
|
||||||
|
onClick={handleMarkRead}
|
||||||
|
sx={{ float: "right", marginRight: -0.5, marginTop: -1 }}
|
||||||
|
aria-label={t("notifications_mark_read")}
|
||||||
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -251,7 +251,14 @@ const Users = () => {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
<Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button>
|
<Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button>
|
||||||
<UserDialog key={`userAddDialog${dialogKey}`} open={dialogOpen} user={null} users={users} onCancel={handleDialogCancel} onSubmit={handleDialogSubmit} />
|
<UserDialog
|
||||||
|
key={`userAddDialog${dialogKey}`}
|
||||||
|
open={dialogOpen}
|
||||||
|
user={null}
|
||||||
|
users={users}
|
||||||
|
onCancel={handleDialogCancel}
|
||||||
|
onSubmit={handleDialogSubmit}
|
||||||
|
/>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -449,7 +456,26 @@ const Language = () => {
|
||||||
|
|
||||||
// Country flags are displayed using emoji. Emoji rendering is handled by platform fonts.
|
// Country flags are displayed using emoji. Emoji rendering is handled by platform fonts.
|
||||||
// Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows.
|
// Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows.
|
||||||
const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇵🇱", "🇺🇦", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇳🇱", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3);
|
const randomFlags = shuffle([
|
||||||
|
"🇬🇧",
|
||||||
|
"🇺🇸",
|
||||||
|
"🇪🇸",
|
||||||
|
"🇫🇷",
|
||||||
|
"🇧🇬",
|
||||||
|
"🇨🇿",
|
||||||
|
"🇩🇪",
|
||||||
|
"🇵🇱",
|
||||||
|
"🇺🇦",
|
||||||
|
"🇨🇳",
|
||||||
|
"🇮🇹",
|
||||||
|
"🇭🇺",
|
||||||
|
"🇧🇷",
|
||||||
|
"🇳🇱",
|
||||||
|
"🇮🇩",
|
||||||
|
"🇯🇵",
|
||||||
|
"🇷🇺",
|
||||||
|
"🇹🇷",
|
||||||
|
]).slice(0, 3);
|
||||||
const showFlags = !navigator.userAgent.includes("Windows");
|
const showFlags = !navigator.userAgent.includes("Windows");
|
||||||
let title = t("prefs_appearance_language_title");
|
let title = t("prefs_appearance_language_title");
|
||||||
if (showFlags) {
|
if (showFlags) {
|
||||||
|
@ -531,7 +557,12 @@ const Reservations = () => {
|
||||||
<Button onClick={handleAddClick} disabled={limitReached}>
|
<Button onClick={handleAddClick} disabled={limitReached}>
|
||||||
{t("prefs_reservations_add_button")}
|
{t("prefs_reservations_add_button")}
|
||||||
</Button>
|
</Button>
|
||||||
<ReserveAddDialog key={`reservationAddDialog${dialogKey}`} open={dialogOpen} reservations={reservations} onClose={() => setDialogOpen(false)} />
|
<ReserveAddDialog
|
||||||
|
key={`reservationAddDialog${dialogKey}`}
|
||||||
|
open={dialogOpen}
|
||||||
|
reservations={reservations}
|
||||||
|
onClose={() => setDialogOpen(false)}
|
||||||
|
/>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -545,7 +576,9 @@ const ReservationsTable = (props) => {
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const { subscriptions } = useOutletContext();
|
const { subscriptions } = useOutletContext();
|
||||||
const localSubscriptions =
|
const localSubscriptions =
|
||||||
subscriptions?.length > 0 ? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s }))) : {};
|
subscriptions?.length > 0
|
||||||
|
? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s })))
|
||||||
|
: {};
|
||||||
|
|
||||||
const handleEditClick = (reservation) => {
|
const handleEditClick = (reservation) => {
|
||||||
setDialogKey((prev) => prev + 1);
|
setDialogKey((prev) => prev + 1);
|
||||||
|
|
|
@ -783,7 +783,12 @@ const AttachmentBox = (props) => {
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<DialogIconButton disabled={props.disabled} onClick={props.onClose} sx={{ marginLeft: "6px" }} aria-label={t("publish_dialog_attached_file_remove")}>
|
<DialogIconButton
|
||||||
|
disabled={props.disabled}
|
||||||
|
onClick={props.onClose}
|
||||||
|
sx={{ marginLeft: "6px" }}
|
||||||
|
aria-label={t("publish_dialog_attached_file_remove")}
|
||||||
|
>
|
||||||
<Close />
|
<Close />
|
||||||
</DialogIconButton>
|
</DialogIconButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -806,7 +811,13 @@ const ExpandingTextField = (props) => {
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography ref={invisibleFieldRef} component="span" variant={props.variant} aria-hidden={true} sx={{ position: "absolute", left: "-200%" }}>
|
<Typography
|
||||||
|
ref={invisibleFieldRef}
|
||||||
|
component="span"
|
||||||
|
variant={props.variant}
|
||||||
|
aria-hidden={true}
|
||||||
|
sx={{ position: "absolute", left: "-200%" }}
|
||||||
|
>
|
||||||
{props.value}
|
{props.value}
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
@ -121,7 +121,13 @@ const Signup = () => {
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" fullWidth variant="contained" disabled={username === "" || password === "" || password !== confirm} sx={{ mt: 2, mb: 2 }}>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
disabled={username === "" || password === "" || password !== confirm}
|
||||||
|
sx={{ mt: 2, mb: 2 }}
|
||||||
|
>
|
||||||
{t("signup_form_button_submit")}
|
{t("signup_form_button_submit")}
|
||||||
</Button>
|
</Button>
|
||||||
{error && (
|
{error && (
|
||||||
|
|
|
@ -68,7 +68,9 @@ const SubscribePage = (props) => {
|
||||||
const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url;
|
const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url;
|
||||||
const topic = props.topic;
|
const topic = props.topic;
|
||||||
const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic));
|
const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic));
|
||||||
const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter((s) => s !== config.base_url);
|
const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter(
|
||||||
|
(s) => s !== config.base_url
|
||||||
|
);
|
||||||
const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account);
|
const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account);
|
||||||
const reserveTopicEnabled =
|
const reserveTopicEnabled =
|
||||||
session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0));
|
session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0));
|
||||||
|
@ -212,7 +214,12 @@ const SubscribePage = (props) => {
|
||||||
inputValue={props.baseUrl}
|
inputValue={props.baseUrl}
|
||||||
onInputChange={updateBaseUrl}
|
onInputChange={updateBaseUrl}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField {...params} placeholder={config.base_url} variant="standard" aria-label={t("subscribe_dialog_subscribe_base_url_label")} />
|
<TextField
|
||||||
|
{...params}
|
||||||
|
placeholder={config.base_url}
|
||||||
|
variant="standard"
|
||||||
|
aria-label={t("subscribe_dialog_subscribe_base_url_label")}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -40,7 +40,10 @@ export const SubscriptionPopup = (props) => {
|
||||||
|
|
||||||
const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0;
|
const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0;
|
||||||
const showReservationAddDisabled =
|
const showReservationAddDisabled =
|
||||||
!showReservationAdd && config.enable_reservations && !subscription?.reservation && (config.enable_payments || account?.stats.reservations_remaining === 0);
|
!showReservationAdd &&
|
||||||
|
config.enable_reservations &&
|
||||||
|
!subscription?.reservation &&
|
||||||
|
(config.enable_payments || account?.stats.reservations_remaining === 0);
|
||||||
const showReservationEdit = config.enable_reservations && !!subscription?.reservation;
|
const showReservationEdit = config.enable_reservations && !!subscription?.reservation;
|
||||||
const showReservationDelete = config.enable_reservations && !!subscription?.reservation;
|
const showReservationDelete = config.enable_reservations && !!subscription?.reservation;
|
||||||
|
|
||||||
|
@ -161,10 +164,20 @@ export const SubscriptionPopup = (props) => {
|
||||||
<MenuItem onClick={handleUnsubscribe}>{t("action_bar_unsubscribe")}</MenuItem>
|
<MenuItem onClick={handleUnsubscribe}>{t("action_bar_unsubscribe")}</MenuItem>
|
||||||
</PopupMenu>
|
</PopupMenu>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Snackbar open={showPublishError} autoHideDuration={3000} onClose={() => setShowPublishError(false)} message={t("message_bar_error_publishing")} />
|
<Snackbar
|
||||||
|
open={showPublishError}
|
||||||
|
autoHideDuration={3000}
|
||||||
|
onClose={() => setShowPublishError(false)}
|
||||||
|
message={t("message_bar_error_publishing")}
|
||||||
|
/>
|
||||||
<DisplayNameDialog open={displayNameDialogOpen} subscription={subscription} onClose={() => setDisplayNameDialogOpen(false)} />
|
<DisplayNameDialog open={displayNameDialogOpen} subscription={subscription} onClose={() => setDisplayNameDialogOpen(false)} />
|
||||||
{showReservationAdd && (
|
{showReservationAdd && (
|
||||||
<ReserveAddDialog open={reserveAddDialogOpen} topic={subscription.topic} reservations={reservations} onClose={() => setReserveAddDialogOpen(false)} />
|
<ReserveAddDialog
|
||||||
|
open={reserveAddDialogOpen}
|
||||||
|
topic={subscription.topic}
|
||||||
|
reservations={reservations}
|
||||||
|
onClose={() => setReserveAddDialogOpen(false)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{showReservationEdit && (
|
{showReservationEdit && (
|
||||||
<ReserveEditDialog
|
<ReserveEditDialog
|
||||||
|
@ -175,7 +188,11 @@ export const SubscriptionPopup = (props) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showReservationDelete && (
|
{showReservationDelete && (
|
||||||
<ReserveDeleteDialog open={reserveDeleteDialogOpen} topic={subscription.topic} onClose={() => setReserveDeleteDialogOpen(false)} />
|
<ReserveDeleteDialog
|
||||||
|
open={reserveDeleteDialogOpen}
|
||||||
|
topic={subscription.topic}
|
||||||
|
onClose={() => setReserveDeleteDialogOpen(false)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Portal>
|
</Portal>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -363,7 +363,9 @@ const TierCard = (props) => {
|
||||||
</Feature>
|
</Feature>
|
||||||
)}
|
)}
|
||||||
<Feature>
|
<Feature>
|
||||||
{t("account_upgrade_dialog_tier_features_attachment_file_size", { filesize: formatBytes(tier.limits.attachment_file_size, 0) })}
|
{t("account_upgrade_dialog_tier_features_attachment_file_size", {
|
||||||
|
filesize: formatBytes(tier.limits.attachment_file_size, 0),
|
||||||
|
})}
|
||||||
</Feature>
|
</Feature>
|
||||||
{tier.limits.reservations === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_reservations")}</NoFeature>}
|
{tier.limits.reservations === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_reservations")}</NoFeature>}
|
||||||
{tier.limits.calls === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_calls")}</NoFeature>}
|
{tier.limits.calls === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_calls")}</NoFeature>}
|
||||||
|
|
Loading…
Add table
Reference in a new issue