mirror of
https://github.com/outline/outline.git
synced 2025-03-29 22:32:51 +00:00
Reduce size of Insights display toggle
This commit is contained in:
@ -26,6 +26,7 @@ import {
|
||||
CommentIcon,
|
||||
GlobeIcon,
|
||||
CopyIcon,
|
||||
EyeIcon,
|
||||
} from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
@ -899,6 +900,37 @@ export const openDocumentInsights = createAction({
|
||||
},
|
||||
});
|
||||
|
||||
export const toggleViewerInsights = createAction({
|
||||
name: ({ t, stores, activeDocumentId }) => {
|
||||
const document = activeDocumentId
|
||||
? stores.documents.get(activeDocumentId)
|
||||
: undefined;
|
||||
return document?.insightsEnabled
|
||||
? t("Disable viewer insights")
|
||||
: t("Enable viewer insights");
|
||||
},
|
||||
analyticsName: "Toggle viewer insights",
|
||||
section: DocumentSection,
|
||||
icon: <EyeIcon />,
|
||||
visible: ({ activeDocumentId, stores }) => {
|
||||
const can = stores.policies.abilities(activeDocumentId ?? "");
|
||||
return can.updateInsights;
|
||||
},
|
||||
perform: async ({ activeDocumentId, stores }) => {
|
||||
if (!activeDocumentId) {
|
||||
return;
|
||||
}
|
||||
const document = stores.documents.get(activeDocumentId);
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
await document.save({
|
||||
insightsEnabled: !document.insightsEnabled,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const rootDocumentActions = [
|
||||
openDocument,
|
||||
archiveDocument,
|
||||
|
48
app/menus/InsightsMenu.tsx
Normal file
48
app/menus/InsightsMenu.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { t } from "i18next";
|
||||
import { MoreIcon } from "outline-icons";
|
||||
import React from "react";
|
||||
import { MenuButton, useMenuState } from "reakit/Menu";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import ContextMenu from "~/components/ContextMenu";
|
||||
import Template from "~/components/ContextMenu/Template";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import { actionToMenuItem } from "~/actions";
|
||||
import { toggleViewerInsights } from "~/actions/definitions/documents";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { hover } from "~/styles";
|
||||
import { MenuItem } from "~/types";
|
||||
|
||||
const InsightsMenu: React.FC = () => {
|
||||
const menuRef = React.useRef<HTMLDivElement>(null);
|
||||
const menu = useMenuState();
|
||||
const context = useActionContext();
|
||||
const items: MenuItem[] = [actionToMenuItem(toggleViewerInsights, context)];
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuButton {...menu}>
|
||||
{(props) => (
|
||||
<Button {...props}>
|
||||
<MoreIcon />
|
||||
</Button>
|
||||
)}
|
||||
</MenuButton>
|
||||
<ContextMenu {...menu} menuRef={menuRef} aria-label={t("Notification")}>
|
||||
<Template {...menu} items={items} />
|
||||
</ContextMenu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Button = styled(NudeButton)`
|
||||
color: ${s("textSecondary")};
|
||||
|
||||
&:${hover},
|
||||
&:active {
|
||||
color: ${s("text")};
|
||||
background: ${s("sidebarControlHoverBackground")};
|
||||
}
|
||||
`;
|
||||
|
||||
export default InsightsMenu;
|
@ -13,21 +13,19 @@ import DocumentViews from "~/components/DocumentViews";
|
||||
import Flex from "~/components/Flex";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Switch from "~/components/Switch";
|
||||
import Text from "~/components/Text";
|
||||
import Time from "~/components/Time";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useTextSelection from "~/hooks/useTextSelection";
|
||||
import InsightsMenu from "~/menus/InsightsMenu";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
import Sidebar from "./SidebarLayout";
|
||||
|
||||
function Insights() {
|
||||
const { views, documents } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const user = useCurrentUser();
|
||||
const match = useRouteMatch<{ documentSlug: string }>();
|
||||
const history = useHistory();
|
||||
const selectedText = useTextSelection();
|
||||
@ -111,114 +109,99 @@ function Insights() {
|
||||
</List>
|
||||
</Text>
|
||||
</Content>
|
||||
{document.insightsEnabled && (
|
||||
<>
|
||||
<Content column>
|
||||
<Heading>{t("Contributors")}</Heading>
|
||||
<Text as="p" type="secondary" size="small">
|
||||
{t(`Created`)}{" "}
|
||||
<Time dateTime={document.createdAt} addSuffix />.
|
||||
<br />
|
||||
{t(`Last updated`)}{" "}
|
||||
<Time dateTime={document.updatedAt} addSuffix />.
|
||||
</Text>
|
||||
<ListSpacing>
|
||||
{document.sourceMetadata?.createdByName && (
|
||||
<ListItem
|
||||
title={document.sourceMetadata?.createdByName}
|
||||
image={
|
||||
<Avatar
|
||||
model={{
|
||||
color: stringToColor(
|
||||
document.sourceMetadata.createdByName
|
||||
),
|
||||
avatarUrl: null,
|
||||
initial: document.sourceMetadata.createdByName[0],
|
||||
}}
|
||||
size={32}
|
||||
/>
|
||||
}
|
||||
subtitle={t("Creator")}
|
||||
border={false}
|
||||
small
|
||||
|
||||
<Content column>
|
||||
<Heading>{t("Contributors")}</Heading>
|
||||
<Text as="p" type="secondary" size="small">
|
||||
{t(`Created`)} <Time dateTime={document.createdAt} addSuffix />.
|
||||
<br />
|
||||
{t(`Last updated`)}{" "}
|
||||
<Time dateTime={document.updatedAt} addSuffix />.
|
||||
</Text>
|
||||
<ListSpacing>
|
||||
{document.sourceMetadata?.createdByName && (
|
||||
<ListItem
|
||||
title={document.sourceMetadata?.createdByName}
|
||||
image={
|
||||
<Avatar
|
||||
model={{
|
||||
color: stringToColor(
|
||||
document.sourceMetadata.createdByName
|
||||
),
|
||||
avatarUrl: null,
|
||||
initial: document.sourceMetadata.createdByName[0],
|
||||
}}
|
||||
size={32}
|
||||
/>
|
||||
)}
|
||||
<PaginatedList
|
||||
aria-label={t("Contributors")}
|
||||
items={document.collaborators}
|
||||
renderItem={(model: User) => (
|
||||
<ListItem
|
||||
key={model.id}
|
||||
title={model.name}
|
||||
image={<Avatar model={model} size={32} />}
|
||||
subtitle={
|
||||
model.id === document.createdBy?.id
|
||||
? document.sourceMetadata?.createdByName
|
||||
? t("Imported")
|
||||
: t("Creator")
|
||||
: model.id === document.updatedBy?.id
|
||||
? t("Last edited")
|
||||
: t("Previously edited")
|
||||
}
|
||||
border={false}
|
||||
small
|
||||
/>
|
||||
)}
|
||||
}
|
||||
subtitle={t("Creator")}
|
||||
border={false}
|
||||
small
|
||||
/>
|
||||
)}
|
||||
<PaginatedList
|
||||
aria-label={t("Contributors")}
|
||||
items={document.collaborators}
|
||||
renderItem={(model: User) => (
|
||||
<ListItem
|
||||
key={model.id}
|
||||
title={model.name}
|
||||
image={<Avatar model={model} size={32} />}
|
||||
subtitle={
|
||||
model.id === document.createdBy?.id
|
||||
? document.sourceMetadata?.createdByName
|
||||
? t("Imported")
|
||||
: t("Creator")
|
||||
: model.id === document.updatedBy?.id
|
||||
? t("Last edited")
|
||||
: t("Previously edited")
|
||||
}
|
||||
border={false}
|
||||
small
|
||||
/>
|
||||
</ListSpacing>
|
||||
</Content>
|
||||
<Content column>
|
||||
<Heading>{t("Views")}</Heading>
|
||||
<Text as="p" type="secondary" size="small">
|
||||
{documentViews.length <= 1
|
||||
? t("No one else has viewed yet")
|
||||
: t(
|
||||
`Viewed {{ count }} times by {{ teamMembers }} people`,
|
||||
{
|
||||
count: documentViews.reduce(
|
||||
(memo, view) => memo + view.count,
|
||||
0
|
||||
),
|
||||
teamMembers: documentViews.length,
|
||||
}
|
||||
)}
|
||||
.
|
||||
</Text>
|
||||
{documentViews.length > 1 && (
|
||||
<ListSpacing>
|
||||
<DocumentViews document={document} isOpen />
|
||||
</ListSpacing>
|
||||
)}
|
||||
</Content>
|
||||
</>
|
||||
/>
|
||||
</ListSpacing>
|
||||
</Content>
|
||||
{(document.insightsEnabled || can.updateInsights) && (
|
||||
<Content column>
|
||||
<Heading>
|
||||
<Flex justify="space-between">
|
||||
{t("Viewed by")}
|
||||
{can.updateInsights && <InsightsMenu />}
|
||||
</Flex>
|
||||
</Heading>
|
||||
{document.insightsEnabled ? (
|
||||
<>
|
||||
<Text as="p" type="secondary" size="small">
|
||||
{documentViews.length <= 1
|
||||
? t("No one else has viewed yet")
|
||||
: t(
|
||||
`Viewed {{ count }} times by {{ teamMembers }} people`,
|
||||
{
|
||||
count: documentViews.reduce(
|
||||
(memo, view) => memo + view.count,
|
||||
0
|
||||
),
|
||||
teamMembers: documentViews.length,
|
||||
}
|
||||
)}
|
||||
.
|
||||
</Text>
|
||||
{documentViews.length > 1 && (
|
||||
<ListSpacing>
|
||||
<DocumentViews document={document} isOpen />
|
||||
</ListSpacing>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Text as="p" type="secondary" size="small">
|
||||
{t("Viewer insights are disabled.")}
|
||||
</Text>
|
||||
)}
|
||||
</Content>
|
||||
)}
|
||||
</div>
|
||||
{can.updateInsights && (
|
||||
<Manage>
|
||||
<Flex column>
|
||||
<Text as="p" size="small" weight="bold">
|
||||
{t("Viewer insights")}
|
||||
</Text>
|
||||
<Text as="p" type="secondary" size="small">
|
||||
{user.isAdmin
|
||||
? t(
|
||||
"As an admin you can manage if team members can see who has viewed this document"
|
||||
)
|
||||
: t(
|
||||
"As the doc owner you can manage if team members can see who has viewed this document"
|
||||
)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Switch
|
||||
checked={document.insightsEnabled}
|
||||
onChange={async (ev) => {
|
||||
await document.save({
|
||||
insightsEnabled: ev.currentTarget.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Manage>
|
||||
)}
|
||||
</Flex>
|
||||
) : null}
|
||||
</Sidebar>
|
||||
@ -251,16 +234,6 @@ function countWords(text: string): number {
|
||||
return t ? t.replace(/-/g, " ").split(/\s+/g).length : 0;
|
||||
}
|
||||
|
||||
const Manage = styled(Flex)`
|
||||
background: ${s("background")};
|
||||
border: 1px solid ${s("inputBorder")};
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 8px;
|
||||
margin: 16px;
|
||||
padding: 16px 16px 0;
|
||||
justify-self: flex-end;
|
||||
`;
|
||||
|
||||
const ListSpacing = styled("div")`
|
||||
margin-top: -0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
|
@ -69,6 +69,8 @@
|
||||
"Comments": "Comments",
|
||||
"History": "History",
|
||||
"Insights": "Insights",
|
||||
"Disable viewer insights": "Disable viewer insights",
|
||||
"Enable viewer insights": "Enable viewer insights",
|
||||
"Home": "Home",
|
||||
"Drafts": "Drafts",
|
||||
"Trash": "Trash",
|
||||
@ -583,13 +585,10 @@
|
||||
"Creator": "Creator",
|
||||
"Last edited": "Last edited",
|
||||
"Previously edited": "Previously edited",
|
||||
"Views": "Views",
|
||||
"No one else has viewed yet": "No one else has viewed yet",
|
||||
"Viewed {{ count }} times by {{ teamMembers }} people": "Viewed {{ count }} time by {{ teamMembers }} people",
|
||||
"Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people",
|
||||
"Viewer insights": "Viewer insights",
|
||||
"As an admin you can manage if team members can see who has viewed this document": "As an admin you can manage if team members can see who has viewed this document",
|
||||
"As the doc owner you can manage if team members can see who has viewed this document": "As the doc owner you can manage if team members can see who has viewed this document",
|
||||
"Viewer insights are disabled.": "Viewer insights are disabled.",
|
||||
"Sorry, the last change could not be persisted – please reload the page": "Sorry, the last change could not be persisted – please reload the page",
|
||||
"{{ count }} days": "{{ count }} day",
|
||||
"{{ count }} days_plural": "{{ count }} days",
|
||||
@ -800,6 +799,7 @@
|
||||
"Shared nested": "Shared nested",
|
||||
"Nested documents are publicly available": "Nested documents are publicly available",
|
||||
"Domain": "Domain",
|
||||
"Views": "Views",
|
||||
"Everyone": "Everyone",
|
||||
"Admins": "Admins",
|
||||
"Settings saved": "Settings saved",
|
||||
|
Reference in New Issue
Block a user