Merge branch 'main' into 303-update-notifications

This commit is contained in:
binwiederhier
2026-01-05 15:34:42 -05:00
80 changed files with 2948 additions and 1146 deletions

View File

@@ -45,7 +45,7 @@ import CloseIcon from "@mui/icons-material/Close";
import { ContentCopy, Public } from "@mui/icons-material";
import AddIcon from "@mui/icons-material/Add";
import routes from "./routes";
import { formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils";
import { copyToClipboard, formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils";
import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi";
import { Pref, PrefGroup } from "./Pref";
import db from "../app/db";
@@ -100,15 +100,13 @@ const Username = () => {
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
<div aria-labelledby={labelId}>
{session.username()}
{account?.role === Role.ADMIN ? (
{account?.role === Role.ADMIN && (
<>
{" "}
<Tooltip title={t("account_basics_username_admin_tooltip")}>
<span style={{ cursor: "default" }}>👑</span>
</Tooltip>
</>
) : (
""
)}
</div>
</Pref>
@@ -119,6 +117,7 @@ const ChangePassword = () => {
const { t } = useTranslation();
const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
const { account } = useContext(AccountContext);
const labelId = "prefChangePassword";
const handleDialogOpen = () => {
@@ -136,9 +135,19 @@ const ChangePassword = () => {
<Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
</Typography>
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
<EditIcon />
</IconButton>
{!account?.provisioned ? (
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
<EditIcon />
</IconButton>
) : (
<Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
<span>
<IconButton disabled>
<EditIcon />
</IconButton>
</span>
</Tooltip>
)}
</div>
<ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
</Pref>
@@ -361,7 +370,7 @@ const PhoneNumbers = () => {
};
const handleCopy = (phoneNumber) => {
navigator.clipboard.writeText(phoneNumber);
copyToClipboard(phoneNumber);
setSnackOpen(true);
};
@@ -832,7 +841,7 @@ const TokensTable = (props) => {
};
const handleCopy = async (token) => {
await navigator.clipboard.writeText(token);
copyToClipboard(token);
setSnackOpen(true);
};
@@ -888,7 +897,7 @@ const TokensTable = (props) => {
</div>
</TableCell>
<TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
{token.token !== session.token() && (
{token.token !== session.token() && !token.provisioned && (
<>
<IconButton onClick={() => handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}>
<EditIcon />
@@ -910,6 +919,18 @@ const TokensTable = (props) => {
</span>
</Tooltip>
)}
{token.provisioned && (
<Tooltip title={t("account_tokens_table_cannot_delete_or_edit_provisioned_token")}>
<span>
<IconButton disabled>
<EditIcon />
</IconButton>
<IconButton disabled>
<CloseIcon />
</IconButton>
</span>
</Tooltip>
)}
</TableCell>
</TableRow>
))}
@@ -1048,6 +1069,7 @@ const DeleteAccount = () => {
const { t } = useTranslation();
const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
const { account } = useContext(AccountContext);
const handleDialogOpen = () => {
setDialogKey((prev) => prev + 1);
@@ -1061,9 +1083,19 @@ const DeleteAccount = () => {
return (
<Pref title={t("account_delete_title")} description={t("account_delete_description")}>
<div>
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
{t("account_delete_title")}
</Button>
{!account?.provisioned ? (
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
{t("account_delete_title")}
</Button>
) : (
<Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
<span>
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} disabled>
{t("account_delete_title")}
</Button>
</span>
</Tooltip>
)}
</div>
<DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
</Pref>

View File

@@ -23,6 +23,7 @@ import Account from "./Account";
import initI18n from "../app/i18n"; // Translations!
import prefs, { THEME } from "../app/Prefs";
import RTLCacheProvider from "./RTLCacheProvider";
import session from "../app/Session";
initI18n();
@@ -45,7 +46,6 @@ const darkModeEnabled = (prefersDarkMode, themePreference) => {
const App = () => {
const { i18n } = useTranslation();
const languageDir = i18n.dir();
const [account, setAccount] = useState(null);
const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]);
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
@@ -60,6 +60,12 @@ const App = () => {
document.dir = languageDir;
}, [i18n.language, languageDir]);
useEffect(() => {
if (!session.exists() && config.require_login && window.location.pathname !== routes.login) {
window.location.href = routes.login;
}
}, []);
return (
<Suspense fallback={<Loader />}>
<RTLCacheProvider>

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import StackTrace from "stacktrace-js";
import { CircularProgress, Link, Button } from "@mui/material";
import { Trans, withTranslation } from "react-i18next";
import { copyToClipboard } from "../app/utils";
class ErrorBoundaryImpl extends React.Component {
constructor(props) {
@@ -64,7 +65,7 @@ class ErrorBoundaryImpl extends React.Component {
stack += `${this.state.niceStack}\n\n`;
}
stack += `${this.state.originalStack}\n`;
navigator.clipboard.writeText(stack);
copyToClipboard(stack);
}
renderUnsupportedIndexedDB() {

View File

@@ -26,7 +26,16 @@ import { Trans, useTranslation } from "react-i18next";
import { useOutletContext } from "react-router-dom";
import { useRemark } from "react-remark";
import styled from "@emotion/styled";
import { formatBytes, formatShortDateTime, maybeActionErrors, openUrl, shortUrl, topicShortUrl, unmatchedTags } from "../app/utils";
import {
copyToClipboard,
formatBytes,
formatShortDateTime,
maybeActionErrors,
openUrl,
shortUrl,
topicShortUrl,
unmatchedTags,
} from "../app/utils";
import { formatMessage, formatTitle, isImage } from "../app/notificationUtils";
import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles";
import subscriptionManager from "../app/SubscriptionManager";
@@ -249,7 +258,7 @@ const NotificationItem = (props) => {
});
};
const handleCopy = (s) => {
navigator.clipboard.writeText(s);
copyToClipboard(s);
props.onShowSnack();
};
const expired = attachment && attachment.expires && attachment.expires < Date.now() / 1000;

View File

@@ -17,6 +17,13 @@ const baseThemeOptions = {
},
},
},
MuiCardActions: {
styleOverrides: {
root: {
overflowX: "auto",
},
},
},
},
};