Assorted cleanup, minor bug fixes, styling fixes, eslint rules (#5165
* fix: Logic error in toast fix: Remove useless component * fix: Logout not clearing all stores * Add icons to notification settings * Add eslint rule to enforce spaced comment * Add eslint rule for arrow-body-style * Add eslint rule to enforce self-closing components * Add menu to api key settings Fix: Deleting webhook subscription does not remove from UI Split webhook subscriptions into active and inactive Styling updates
This commit is contained in:
@@ -8,8 +8,6 @@ const extensions = withComments(basicExtensions);
|
||||
const CommentEditor = (
|
||||
props: EditorProps,
|
||||
ref: React.RefObject<SharedEditor>
|
||||
) => {
|
||||
return <Editor extensions={extensions} {...props} ref={ref} />;
|
||||
};
|
||||
) => <Editor extensions={extensions} {...props} ref={ref} />;
|
||||
|
||||
export default React.forwardRef(CommentEditor);
|
||||
|
||||
@@ -114,10 +114,9 @@ function CommentThread({
|
||||
scrollMode: "if-needed",
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
boundary: (parent) => {
|
||||
boundary: (parent) =>
|
||||
// Prevents body and other parent elements from being scrolled
|
||||
return parent.id !== "comments";
|
||||
},
|
||||
parent.id !== "comments",
|
||||
});
|
||||
},
|
||||
isVisible ? 0 : sidebarAppearDuration
|
||||
|
||||
@@ -185,13 +185,14 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
||||
isMounted,
|
||||
]);
|
||||
|
||||
const user = React.useMemo(() => {
|
||||
return {
|
||||
const user = React.useMemo(
|
||||
() => ({
|
||||
id: currentUser.id,
|
||||
name: currentUser.name,
|
||||
color: currentUser.color,
|
||||
};
|
||||
}, [currentUser.id, currentUser.color, currentUser.name]);
|
||||
}),
|
||||
[currentUser.id, currentUser.color, currentUser.name]
|
||||
);
|
||||
|
||||
const extensions = React.useMemo(() => {
|
||||
if (!remoteProvider) {
|
||||
|
||||
@@ -49,13 +49,11 @@ const PublicBreadcrumb: React.FC<Props> = ({
|
||||
() =>
|
||||
pathToDocument(sharedTree, documentId)
|
||||
.slice(0, -1)
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
type: "route",
|
||||
to: sharedDocumentPath(shareId, item.url),
|
||||
};
|
||||
}),
|
||||
.map((item) => ({
|
||||
...item,
|
||||
type: "route",
|
||||
to: sharedDocumentPath(shareId, item.url),
|
||||
})),
|
||||
[sharedTree, shareId, documentId]
|
||||
);
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ export default function DocumentScene(props: Props) {
|
||||
setLastVisitedPath(currentPath);
|
||||
}, [currentPath, setLastVisitedPath]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => ui.clearActiveDocument();
|
||||
}, [ui]);
|
||||
React.useEffect(() => () => ui.clearActiveDocument(), [ui]);
|
||||
|
||||
// the urlId portion of the url does not include the slugified title
|
||||
// we only want to force a re-mount of the document component when the
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WarningIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import NoticeAlert from "~/components/NoticeAlert";
|
||||
import Notice from "~/components/Notice";
|
||||
import useQuery from "~/hooks/useQuery";
|
||||
|
||||
export default function Notices() {
|
||||
@@ -13,7 +14,7 @@ export default function Notices() {
|
||||
}
|
||||
|
||||
return (
|
||||
<NoticeAlert>
|
||||
<Notice icon={<WarningIcon color="currentcolor" />}>
|
||||
{notice === "domain-required" && (
|
||||
<Trans>
|
||||
Unable to sign-in. Please navigate to your team's custom URL, then try
|
||||
@@ -103,6 +104,6 @@ export default function Notices() {
|
||||
team domain.
|
||||
</Trans>
|
||||
)}
|
||||
</NoticeAlert>
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import Heading from "~/components/Heading";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Scene from "~/components/Scene";
|
||||
import Subheading from "~/components/Subheading";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
@@ -57,11 +56,15 @@ function Groups() {
|
||||
Groups can be used to organize and manage the people on your team.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Subheading>{t("All groups")}</Subheading>
|
||||
<PaginatedList
|
||||
items={groups.orderedData}
|
||||
empty={<Empty>{t("No groups have been created yet")}</Empty>}
|
||||
fetch={groups.fetchPage}
|
||||
heading={
|
||||
<h2>
|
||||
<Trans>All</Trans>
|
||||
</h2>
|
||||
}
|
||||
renderItem={(item: Group) => (
|
||||
<GroupListItem
|
||||
key={item.id}
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { debounce } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import { EmailIcon } from "outline-icons";
|
||||
import {
|
||||
AcademicCapIcon,
|
||||
CheckboxIcon,
|
||||
CollectionIcon,
|
||||
CommentIcon,
|
||||
EditIcon,
|
||||
EmailIcon,
|
||||
PublishIcon,
|
||||
StarredIcon,
|
||||
UserIcon,
|
||||
} from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import Flex from "~/components/Flex";
|
||||
import Heading from "~/components/Heading";
|
||||
import Input from "~/components/Input";
|
||||
import Notice from "~/components/Notice";
|
||||
@@ -24,6 +35,7 @@ function Notifications() {
|
||||
const options = [
|
||||
{
|
||||
event: NotificationEventType.PublishDocument,
|
||||
icon: <PublishIcon color="currentColor" />,
|
||||
title: t("Document published"),
|
||||
description: t(
|
||||
"Receive a notification whenever a new document is published"
|
||||
@@ -31,6 +43,7 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
event: NotificationEventType.UpdateDocument,
|
||||
icon: <EditIcon color="currentColor" />,
|
||||
title: t("Document updated"),
|
||||
description: t(
|
||||
"Receive a notification when a document you are subscribed to is edited"
|
||||
@@ -38,6 +51,7 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
event: NotificationEventType.CreateComment,
|
||||
icon: <CommentIcon color="currentColor" />,
|
||||
title: t("Comment posted"),
|
||||
description: t(
|
||||
"Receive a notification when a document you are subscribed to or a thread you participated in receives a comment"
|
||||
@@ -45,6 +59,7 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
event: NotificationEventType.Mentioned,
|
||||
icon: <EmailIcon color="currentColor" />,
|
||||
title: t("Mentioned"),
|
||||
description: t(
|
||||
"Receive a notification when someone mentions you in a document or comment"
|
||||
@@ -52,6 +67,7 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
event: NotificationEventType.CreateCollection,
|
||||
icon: <CollectionIcon color="currentColor" />,
|
||||
title: t("Collection created"),
|
||||
description: t(
|
||||
"Receive a notification whenever a new collection is created"
|
||||
@@ -59,6 +75,7 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
event: NotificationEventType.InviteAccepted,
|
||||
icon: <UserIcon color="currentColor" />,
|
||||
title: t("Invite accepted"),
|
||||
description: t(
|
||||
"Receive a notification when someone you invited creates an account"
|
||||
@@ -66,6 +83,7 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
event: NotificationEventType.ExportCompleted,
|
||||
icon: <CheckboxIcon checked color="currentColor" />,
|
||||
title: t("Export completed"),
|
||||
description: t(
|
||||
"Receive a notification when an export you requested has been completed"
|
||||
@@ -73,12 +91,14 @@ function Notifications() {
|
||||
},
|
||||
{
|
||||
visible: isCloudHosted,
|
||||
icon: <AcademicCapIcon color="currentColor" />,
|
||||
event: NotificationEventType.Onboarding,
|
||||
title: t("Getting started"),
|
||||
description: t("Tips on getting started with features and functionality"),
|
||||
},
|
||||
{
|
||||
visible: isCloudHosted,
|
||||
icon: <StarredIcon color="currentColor" />,
|
||||
event: NotificationEventType.Features,
|
||||
title: t("New features"),
|
||||
description: t("Receive an email when new features of note are added"),
|
||||
@@ -138,7 +158,11 @@ function Notifications() {
|
||||
return (
|
||||
<SettingRow
|
||||
visible={option.visible}
|
||||
label={option.title}
|
||||
label={
|
||||
<Flex align="center" gap={4}>
|
||||
{option.icon} {option.title}
|
||||
</Flex>
|
||||
}
|
||||
name={option.event}
|
||||
description={option.description}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,6 @@ import Heading from "~/components/Heading";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Scene from "~/components/Scene";
|
||||
import Subheading from "~/components/Subheading";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
@@ -59,7 +58,7 @@ function Tokens() {
|
||||
<PaginatedList
|
||||
fetch={apiKeys.fetchPage}
|
||||
items={apiKeys.orderedData}
|
||||
heading={<Subheading sticky>{t("Tokens")}</Subheading>}
|
||||
heading={<h2>{t("Active")}</h2>}
|
||||
renderItem={(token: ApiKey) => (
|
||||
<TokenListItem key={token.id} token={token} />
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,7 @@ function Zapier() {
|
||||
type="module"
|
||||
src="https://cdn.zapier.com/packages/partner-sdk/v0/zapier-elements/zapier-elements.esm.js"
|
||||
key="zapier-js"
|
||||
></script>
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.zapier.com/packages/partner-sdk/v0/zapier-elements/zapier-elements.css"
|
||||
|
||||
@@ -4,10 +4,10 @@ import { useTranslation } from "react-i18next";
|
||||
import ApiKey from "~/models/ApiKey";
|
||||
import Button from "~/components/Button";
|
||||
import CopyToClipboard from "~/components/CopyToClipboard";
|
||||
import Flex from "~/components/Flex";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import TokenRevokeDialog from "./TokenRevokeDialog";
|
||||
import ApiKeyMenu from "~/menus/ApiKeyMenu";
|
||||
|
||||
type Props = {
|
||||
token: ApiKey;
|
||||
@@ -16,7 +16,6 @@ type Props = {
|
||||
const TokenListItem = ({ token }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
const { dialogs } = useStores();
|
||||
const [linkCopied, setLinkCopied] = React.useState<boolean>(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -34,32 +33,20 @@ const TokenListItem = ({ token }: Props) => {
|
||||
});
|
||||
}, [showToast, t]);
|
||||
|
||||
const showRevokeConfirmation = React.useCallback(() => {
|
||||
dialogs.openModal({
|
||||
title: t("Revoke token"),
|
||||
isCentered: true,
|
||||
content: (
|
||||
<TokenRevokeDialog onSubmit={dialogs.closeAllModals} token={token} />
|
||||
),
|
||||
});
|
||||
}, [t, dialogs, token]);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={token.id}
|
||||
title={token.name}
|
||||
subtitle={<code>{token.secret}</code>}
|
||||
subtitle={<code>{token.secret.slice(0, 15)}…</code>}
|
||||
actions={
|
||||
<>
|
||||
<Flex align="center" gap={8}>
|
||||
<CopyToClipboard text={token.secret} onCopy={handleCopy}>
|
||||
<Button type="button" icon={<CopyIcon />} neutral borderOnHover>
|
||||
{linkCopied ? t("Copied") : t("Copy")}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
<Button onClick={showRevokeConfirmation} neutral>
|
||||
Revoke
|
||||
</Button>
|
||||
</>
|
||||
<ApiKeyMenu token={token} />
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { observer } from "mobx-react";
|
||||
import { EditIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import User from "~/models/User";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import Badge from "~/components/Badge";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedDocumentList from "~/components/PaginatedDocumentList";
|
||||
import Subheading from "~/components/Subheading";
|
||||
import Text from "~/components/Text";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { settingsPath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
onRequestClose: () => void;
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
function UserProfile(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { documents } = useStores();
|
||||
const currentUser = useCurrentUser();
|
||||
const history = useHistory();
|
||||
const { user, ...rest } = props;
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
const isCurrentUser = currentUser.id === user.id;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<Flex align="center">
|
||||
<Avatar model={user} size={38} alt={t("Profile picture")} />
|
||||
<span> {user.name}</span>
|
||||
</Flex>
|
||||
}
|
||||
{...rest}
|
||||
>
|
||||
<Flex column>
|
||||
<Meta>
|
||||
{isCurrentUser
|
||||
? t("You joined")
|
||||
: user.lastActiveAt
|
||||
? t("Joined")
|
||||
: t("Invited")}{" "}
|
||||
{t("{{ time }} ago.", {
|
||||
time: formatDistanceToNow(Date.parse(user.createdAt)),
|
||||
})}
|
||||
{user.isAdmin && (
|
||||
<StyledBadge primary={user.isAdmin}>{t("Admin")}</StyledBadge>
|
||||
)}
|
||||
{user.isSuspended && <StyledBadge>{t("Suspended")}</StyledBadge>}
|
||||
{isCurrentUser && (
|
||||
<Edit>
|
||||
<Button
|
||||
onClick={() => history.push(settingsPath())}
|
||||
icon={<EditIcon />}
|
||||
neutral
|
||||
>
|
||||
{t("Edit Profile")}
|
||||
</Button>
|
||||
</Edit>
|
||||
)}
|
||||
</Meta>
|
||||
<PaginatedDocumentList
|
||||
documents={documents.createdByUser(user.id)}
|
||||
fetch={documents.fetchOwned}
|
||||
options={{
|
||||
user: user.id,
|
||||
}}
|
||||
heading={<Subheading>{t("Recently updated")}</Subheading>}
|
||||
empty={
|
||||
<Text type="secondary">
|
||||
{t("{{ userName }} hasn’t updated any documents yet.", {
|
||||
userName: user.name,
|
||||
})}
|
||||
</Text>
|
||||
}
|
||||
showCollection
|
||||
/>
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const Edit = styled.span`
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
right: 0;
|
||||
`;
|
||||
|
||||
const StyledBadge = styled(Badge)`
|
||||
position: relative;
|
||||
top: -2px;
|
||||
`;
|
||||
|
||||
const Meta = styled(Text)`
|
||||
margin-top: -12px;
|
||||
`;
|
||||
|
||||
export default observer(UserProfile);
|
||||
Reference in New Issue
Block a user