mirror of
https://github.com/multipleof4/ntfy.git
synced 2026-10-08 21:05:21 +00:00
PWA reload icon, lazy loading: EmojiPicker and MarkdownContent
This commit is contained in:
@@ -11,11 +11,20 @@ if [ -z "$1" ]; then
|
|||||||
echo "Example:"
|
echo "Example:"
|
||||||
echo " $0 emoji-converted.json"
|
echo " $0 emoji-converted.json"
|
||||||
echo " $0 $ROOTDIR/web/src/app/emojis.js"
|
echo " $0 $ROOTDIR/web/src/app/emojis.js"
|
||||||
|
echo " $0 $ROOTDIR/web/src/app/emojisMapped.js"
|
||||||
echo " $0 $ROOTDIR/docs/emojis.md"
|
echo " $0 $ROOTDIR/docs/emojis.md"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$1" == *.js ]]; then
|
if [[ "$1" == *emojisMapped.js ]]; then
|
||||||
|
# Small alias -> emoji lookup used to render tags as emojis. Precomputed so the full
|
||||||
|
# emoji dataset (emojis.js) stays out of the main web bundle.
|
||||||
|
echo -n "// This file is generated by scripts/emoji-convert.sh -- alias to emoji lookup
|
||||||
|
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
||||||
|
export default " > "$1"
|
||||||
|
cat "$SCRIPTDIR/emoji.json" | jq -jc '[.[] | .aliases[] as $a | {key: $a, value: .emoji}] | from_entries' >> "$1"
|
||||||
|
echo ";" >> "$1"
|
||||||
|
elif [[ "$1" == *.js ]]; then
|
||||||
echo -n "// This file is generated by scripts/emoji-convert.sh to reduce the size
|
echo -n "// This file is generated by scripts/emoji-convert.sh to reduce the size
|
||||||
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
||||||
export const rawEmojis = " > "$1"
|
export const rawEmojis = " > "$1"
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
src/app/emojis.js
|
src/app/emojis.js
|
||||||
|
src/app/emojisMapped.js
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
public/static/langs/
|
public/static/langs/
|
||||||
src/app/emojis.js
|
src/app/emojis.js
|
||||||
|
src/app/emojisMapped.js
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
"action_bar_profile_logout": "Logout",
|
"action_bar_profile_logout": "Logout",
|
||||||
"action_bar_sign_in": "Sign in",
|
"action_bar_sign_in": "Sign in",
|
||||||
"action_bar_sign_up": "Sign up",
|
"action_bar_sign_up": "Sign up",
|
||||||
|
"action_bar_reload": "Reload app",
|
||||||
"message_bar_type_message": "Type a message here",
|
"message_bar_type_message": "Type a message here",
|
||||||
"message_bar_error_publishing": "Error publishing notification",
|
"message_bar_error_publishing": "Error publishing notification",
|
||||||
"message_bar_show_dialog": "Show publish dialog",
|
"message_bar_show_dialog": "Show publish dialog",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -48,7 +48,8 @@ import RefreshIcon from "@mui/icons-material/Refresh";
|
|||||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||||
import CelebrationIcon from "@mui/icons-material/Celebration";
|
import CelebrationIcon from "@mui/icons-material/Celebration";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { ContentCopy, Public } from "@mui/icons-material";
|
import ContentCopy from "@mui/icons-material/ContentCopy";
|
||||||
|
import Public from "@mui/icons-material/Public";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import { copyToClipboard, formatBytes, formatShortDate, formatShortDateTime, formatShortDuration, openUrl } from "../app/utils";
|
import { copyToClipboard, formatBytes, formatShortDate, formatShortDateTime, formatShortDuration, openUrl } from "../app/utils";
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import { useLocation, useNavigate } from "react-router-dom";
|
|||||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||||
import NotificationsIcon from "@mui/icons-material/Notifications";
|
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
import NotificationsOffIcon from "@mui/icons-material/NotificationsOff";
|
import NotificationsOffIcon from "@mui/icons-material/NotificationsOff";
|
||||||
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
||||||
import { Logout, Person, Settings } from "@mui/icons-material";
|
import Logout from "@mui/icons-material/Logout";
|
||||||
|
import Person from "@mui/icons-material/Person";
|
||||||
|
import Settings from "@mui/icons-material/Settings";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
import logo from "../img/ntfy.svg";
|
import logo from "../img/ntfy.svg";
|
||||||
import subscriptionManager from "../app/SubscriptionManager";
|
import subscriptionManager from "../app/SubscriptionManager";
|
||||||
@@ -88,6 +91,7 @@ const ActionBar = (props) => {
|
|||||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{isLaunchedPWA && <ReloadIcon />}
|
||||||
{props.selected && <SettingsIcons subscription={props.selected} onUnsubscribe={props.onUnsubscribe} />}
|
{props.selected && <SettingsIcons subscription={props.selected} onUnsubscribe={props.onUnsubscribe} />}
|
||||||
<ProfileIcon />
|
<ProfileIcon />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
@@ -124,6 +128,31 @@ const SettingsIcons = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ReloadIcon hard-refreshes the app. A plain reload would just serve the precached PWA shell,
|
||||||
|
// so we first purge the service worker caches to force fresh assets from the network.
|
||||||
|
const ReloadIcon = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleReload = async () => {
|
||||||
|
try {
|
||||||
|
if ("caches" in window) {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
await Promise.all(keys.map((key) => caches.delete(key)));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[ActionBar] Error clearing caches during reload", e);
|
||||||
|
} finally {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton color="inherit" size="large" edge="end" onClick={handleReload} aria-label={t("action_bar_reload")}>
|
||||||
|
<RefreshIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ProfileIcon = () => {
|
const ProfileIcon = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { Typography, Box, TextField, ClickAwayListener, Fade, InputAdornment, styled, IconButton, Popper } from "@mui/material";
|
import { Typography, Box, TextField, ClickAwayListener, Fade, InputAdornment, styled, IconButton, Popper } from "@mui/material";
|
||||||
import { Close } from "@mui/icons-material";
|
import Close from "@mui/icons-material/Close";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { splitNoEmpty } from "../app/utils";
|
import { splitNoEmpty } from "../app/utils";
|
||||||
import { rawEmojis } from "../app/emojis";
|
import { rawEmojis } from "../app/emojis";
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { Typography, TextField, Button, Box, IconButton, InputAdornment } from "
|
|||||||
import WarningAmberIcon from "@mui/icons-material/WarningAmber";
|
import WarningAmberIcon from "@mui/icons-material/WarningAmber";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
import Visibility from "@mui/icons-material/Visibility";
|
||||||
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
import accountApi from "../app/AccountApi";
|
import accountApi from "../app/AccountApi";
|
||||||
import AvatarBox from "./AvatarBox";
|
import AvatarBox from "./AvatarBox";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRemark } from "react-remark";
|
||||||
|
import styled from "@emotion/styled";
|
||||||
|
|
||||||
|
const MarkdownContainer = styled("div")`
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
pre,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
blockquote {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
pre {
|
||||||
|
border-radius: 3px;
|
||||||
|
background: ${(props) => (props.theme.palette.mode === "light" ? "#f5f5f5" : "#333")};
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: scroll;
|
||||||
|
padding: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
blockquote {
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Renders text/markdown notification bodies. react-remark (and its remark/unified
|
||||||
|
// dependency stack) is heavy, so this component lives in its own module and is loaded
|
||||||
|
// lazily by Notifications.jsx -- it only ships when a markdown message is actually shown.
|
||||||
|
const MarkdownContent = ({ content }) => {
|
||||||
|
const [reactContent, setMarkdownSource] = useRemark();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMarkdownSource(content);
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
return <MarkdownContainer>{reactContent}</MarkdownContainer>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarkdownContent;
|
||||||
@@ -27,7 +27,10 @@ import Person from "@mui/icons-material/Person";
|
|||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material";
|
import ChatBubble from "@mui/icons-material/ChatBubble";
|
||||||
|
import MoreVert from "@mui/icons-material/MoreVert";
|
||||||
|
import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined";
|
||||||
|
import Send from "@mui/icons-material/Send";
|
||||||
import ArticleIcon from "@mui/icons-material/Article";
|
import ArticleIcon from "@mui/icons-material/Article";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import CelebrationIcon from "@mui/icons-material/Celebration";
|
import CelebrationIcon from "@mui/icons-material/Celebration";
|
||||||
|
|||||||
@@ -17,15 +17,13 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { Suspense, lazy, useEffect, useState } from "react";
|
||||||
import CheckIcon from "@mui/icons-material/Check";
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { useOutletContext } from "react-router-dom";
|
import { useOutletContext } from "react-router-dom";
|
||||||
import { useRemark } from "react-remark";
|
|
||||||
import styled from "@emotion/styled";
|
|
||||||
import {
|
import {
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
formatBytes,
|
formatBytes,
|
||||||
@@ -172,64 +170,19 @@ const autolink = (s) => {
|
|||||||
return <>{parts}</>;
|
return <>{parts}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MarkdownContainer = styled("div")`
|
// Loaded lazily so the heavy react-remark/unified markdown stack is only fetched when a
|
||||||
line-height: 1;
|
// text/markdown notification is actually rendered (see MarkdownContent.jsx).
|
||||||
|
const MarkdownContent = lazy(() => import("./MarkdownContent"));
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
p,
|
|
||||||
pre,
|
|
||||||
ul,
|
|
||||||
ol,
|
|
||||||
blockquote {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
pre {
|
|
||||||
border-radius: 3px;
|
|
||||||
background: ${(props) => (props.theme.palette.mode === "light" ? "#f5f5f5" : "#333")};
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
overflow-x: scroll;
|
|
||||||
padding: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol,
|
|
||||||
blockquote {
|
|
||||||
padding-inline: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MarkdownContent = ({ content }) => {
|
|
||||||
const [reactContent, setMarkdownSource] = useRemark();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMarkdownSource(content);
|
|
||||||
}, [content]);
|
|
||||||
|
|
||||||
return <MarkdownContainer>{reactContent}</MarkdownContainer>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NotificationBody = ({ notification }) => {
|
const NotificationBody = ({ notification }) => {
|
||||||
const displayAsMarkdown = notification.content_type === "text/markdown";
|
const displayAsMarkdown = notification.content_type === "text/markdown";
|
||||||
const formatted = formatMessage(notification);
|
const formatted = formatMessage(notification);
|
||||||
if (displayAsMarkdown) {
|
if (displayAsMarkdown) {
|
||||||
return <MarkdownContent content={formatted} />;
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<MarkdownContent content={formatted} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return autolink(formatted);
|
return autolink(formatted);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import CloseIcon from "@mui/icons-material/Close";
|
|||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Info } from "@mui/icons-material";
|
import Info from "@mui/icons-material/Info";
|
||||||
import { useOutletContext } from "react-router-dom";
|
import { useOutletContext } from "react-router-dom";
|
||||||
import userManager from "../app/UserManager";
|
import userManager from "../app/UserManager";
|
||||||
import { playSound, shortUrl, shuffle, sounds, validUrl } from "../app/utils";
|
import { playSound, shortUrl, shuffle, sounds, validUrl } from "../app/utils";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { Suspense, lazy, useContext, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Chip,
|
Chip,
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon";
|
import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon";
|
||||||
import { Close } from "@mui/icons-material";
|
import Close from "@mui/icons-material/Close";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import priority1 from "../img/priority-1.svg";
|
import priority1 from "../img/priority-1.svg";
|
||||||
import priority2 from "../img/priority-2.svg";
|
import priority2 from "../img/priority-2.svg";
|
||||||
@@ -35,13 +35,16 @@ import AttachmentIcon from "./AttachmentIcon";
|
|||||||
import DialogFooter from "./DialogFooter";
|
import DialogFooter from "./DialogFooter";
|
||||||
import api from "../app/Api";
|
import api from "../app/Api";
|
||||||
import userManager from "../app/UserManager";
|
import userManager from "../app/UserManager";
|
||||||
import EmojiPicker from "./EmojiPicker";
|
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import accountApi from "../app/AccountApi";
|
import accountApi from "../app/AccountApi";
|
||||||
import { UnauthorizedError } from "../app/errors";
|
import { UnauthorizedError } from "../app/errors";
|
||||||
import AccountContext from "./AccountContext";
|
import AccountContext from "./AccountContext";
|
||||||
|
|
||||||
|
// Loaded lazily so the full emoji dataset (~300 KB) is only fetched when the publish
|
||||||
|
// dialog is opened, not in the initial app bundle (see EmojiPicker.jsx).
|
||||||
|
const EmojiPicker = lazy(() => import("./EmojiPicker"));
|
||||||
|
|
||||||
const PublishDialog = (props) => {
|
const PublishDialog = (props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -398,7 +401,9 @@ const PublishDialog = (props) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
<EmojiPicker anchorEl={emojiPickerAnchorEl} onEmojiPick={handleEmojiPick} onClose={handleEmojiClose} />
|
<Suspense fallback={null}>
|
||||||
|
<EmojiPicker anchorEl={emojiPickerAnchorEl} onEmojiPick={handleEmojiPick} onClose={handleEmojiClose} />
|
||||||
|
</Suspense>
|
||||||
<DialogIconButton disabled={disabled} onClick={handleEmojiClick} aria-label={t("publish_dialog_emoji_picker_show")}>
|
<DialogIconButton disabled={disabled} onClick={handleEmojiClick} aria-label={t("publish_dialog_emoji_picker_show")}>
|
||||||
<InsertEmoticonIcon />
|
<InsertEmoticonIcon />
|
||||||
</DialogIconButton>
|
</DialogIconButton>
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Check, DeleteForever } from "@mui/icons-material";
|
import Check from "@mui/icons-material/Check";
|
||||||
|
import DeleteForever from "@mui/icons-material/DeleteForever";
|
||||||
import { validTopic } from "../app/utils";
|
import { validTopic } from "../app/utils";
|
||||||
import DialogFooter from "./DialogFooter";
|
import DialogFooter from "./DialogFooter";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Lock, Public } from "@mui/icons-material";
|
import Lock from "@mui/icons-material/Lock";
|
||||||
|
import Public from "@mui/icons-material/Public";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
export const PermissionReadWrite = React.forwardRef((props, ref) => <PermissionInternal icon={Public} ref={ref} {...props} />);
|
export const PermissionReadWrite = React.forwardRef((props, ref) => <PermissionInternal icon={Public} ref={ref} {...props} />);
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { TextField, Button, Box, Typography, InputAdornment, IconButton } from "
|
|||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import WarningAmberIcon from "@mui/icons-material/WarningAmber";
|
import WarningAmberIcon from "@mui/icons-material/WarningAmber";
|
||||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
import Visibility from "@mui/icons-material/Visibility";
|
||||||
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
import accountApi from "../app/AccountApi";
|
import accountApi from "../app/AccountApi";
|
||||||
import AvatarBox from "./AvatarBox";
|
import AvatarBox from "./AvatarBox";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
|
|||||||
@@ -19,18 +19,16 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {
|
import Clear from "@mui/icons-material/Clear";
|
||||||
Clear,
|
import ClearAll from "@mui/icons-material/ClearAll";
|
||||||
ClearAll,
|
import Edit from "@mui/icons-material/Edit";
|
||||||
Edit,
|
import EnhancedEncryption from "@mui/icons-material/EnhancedEncryption";
|
||||||
EnhancedEncryption,
|
import Lock from "@mui/icons-material/Lock";
|
||||||
Lock,
|
import LockOpen from "@mui/icons-material/LockOpen";
|
||||||
LockOpen,
|
import Notifications from "@mui/icons-material/Notifications";
|
||||||
Notifications,
|
import NotificationsOff from "@mui/icons-material/NotificationsOff";
|
||||||
NotificationsOff,
|
import RemoveCircle from "@mui/icons-material/RemoveCircle";
|
||||||
RemoveCircle,
|
import Send from "@mui/icons-material/Send";
|
||||||
Send,
|
|
||||||
} from "@mui/icons-material";
|
|
||||||
import subscriptionManager from "../app/SubscriptionManager";
|
import subscriptionManager from "../app/SubscriptionManager";
|
||||||
import DialogFooter from "./DialogFooter";
|
import DialogFooter from "./DialogFooter";
|
||||||
import accountApi, { Role } from "../app/AccountApi";
|
import accountApi, { Role } from "../app/AccountApi";
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Check, Close } from "@mui/icons-material";
|
import Check from "@mui/icons-material/Check";
|
||||||
|
import Close from "@mui/icons-material/Close";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { UnauthorizedError } from "../app/errors";
|
import { UnauthorizedError } from "../app/errors";
|
||||||
import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils";
|
import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils";
|
||||||
|
|||||||
Reference in New Issue
Block a user