Reduce size of Insights display toggle
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
|||||||
CommentIcon,
|
CommentIcon,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
|
EyeIcon,
|
||||||
} from "outline-icons";
|
} from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { toast } from "sonner";
|
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 = [
|
export const rootDocumentActions = [
|
||||||
openDocument,
|
openDocument,
|
||||||
archiveDocument,
|
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 Flex from "~/components/Flex";
|
||||||
import ListItem from "~/components/List/Item";
|
import ListItem from "~/components/List/Item";
|
||||||
import PaginatedList from "~/components/PaginatedList";
|
import PaginatedList from "~/components/PaginatedList";
|
||||||
import Switch from "~/components/Switch";
|
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import Time from "~/components/Time";
|
import Time from "~/components/Time";
|
||||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
|
||||||
import useKeyDown from "~/hooks/useKeyDown";
|
import useKeyDown from "~/hooks/useKeyDown";
|
||||||
import usePolicy from "~/hooks/usePolicy";
|
import usePolicy from "~/hooks/usePolicy";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import useTextSelection from "~/hooks/useTextSelection";
|
import useTextSelection from "~/hooks/useTextSelection";
|
||||||
|
import InsightsMenu from "~/menus/InsightsMenu";
|
||||||
import { documentPath } from "~/utils/routeHelpers";
|
import { documentPath } from "~/utils/routeHelpers";
|
||||||
import Sidebar from "./SidebarLayout";
|
import Sidebar from "./SidebarLayout";
|
||||||
|
|
||||||
function Insights() {
|
function Insights() {
|
||||||
const { views, documents } = useStores();
|
const { views, documents } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useCurrentUser();
|
|
||||||
const match = useRouteMatch<{ documentSlug: string }>();
|
const match = useRouteMatch<{ documentSlug: string }>();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const selectedText = useTextSelection();
|
const selectedText = useTextSelection();
|
||||||
@@ -111,114 +109,99 @@ function Insights() {
|
|||||||
</List>
|
</List>
|
||||||
</Text>
|
</Text>
|
||||||
</Content>
|
</Content>
|
||||||
{document.insightsEnabled && (
|
|
||||||
<>
|
<Content column>
|
||||||
<Content column>
|
<Heading>{t("Contributors")}</Heading>
|
||||||
<Heading>{t("Contributors")}</Heading>
|
<Text as="p" type="secondary" size="small">
|
||||||
<Text as="p" type="secondary" size="small">
|
{t(`Created`)} <Time dateTime={document.createdAt} addSuffix />.
|
||||||
{t(`Created`)}{" "}
|
<br />
|
||||||
<Time dateTime={document.createdAt} addSuffix />.
|
{t(`Last updated`)}{" "}
|
||||||
<br />
|
<Time dateTime={document.updatedAt} addSuffix />.
|
||||||
{t(`Last updated`)}{" "}
|
</Text>
|
||||||
<Time dateTime={document.updatedAt} addSuffix />.
|
<ListSpacing>
|
||||||
</Text>
|
{document.sourceMetadata?.createdByName && (
|
||||||
<ListSpacing>
|
<ListItem
|
||||||
{document.sourceMetadata?.createdByName && (
|
title={document.sourceMetadata?.createdByName}
|
||||||
<ListItem
|
image={
|
||||||
title={document.sourceMetadata?.createdByName}
|
<Avatar
|
||||||
image={
|
model={{
|
||||||
<Avatar
|
color: stringToColor(
|
||||||
model={{
|
document.sourceMetadata.createdByName
|
||||||
color: stringToColor(
|
),
|
||||||
document.sourceMetadata.createdByName
|
avatarUrl: null,
|
||||||
),
|
initial: document.sourceMetadata.createdByName[0],
|
||||||
avatarUrl: null,
|
}}
|
||||||
initial: document.sourceMetadata.createdByName[0],
|
size={32}
|
||||||
}}
|
|
||||||
size={32}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
subtitle={t("Creator")}
|
|
||||||
border={false}
|
|
||||||
small
|
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
<PaginatedList
|
subtitle={t("Creator")}
|
||||||
aria-label={t("Contributors")}
|
border={false}
|
||||||
items={document.collaborators}
|
small
|
||||||
renderItem={(model: User) => (
|
/>
|
||||||
<ListItem
|
)}
|
||||||
key={model.id}
|
<PaginatedList
|
||||||
title={model.name}
|
aria-label={t("Contributors")}
|
||||||
image={<Avatar model={model} size={32} />}
|
items={document.collaborators}
|
||||||
subtitle={
|
renderItem={(model: User) => (
|
||||||
model.id === document.createdBy?.id
|
<ListItem
|
||||||
? document.sourceMetadata?.createdByName
|
key={model.id}
|
||||||
? t("Imported")
|
title={model.name}
|
||||||
: t("Creator")
|
image={<Avatar model={model} size={32} />}
|
||||||
: model.id === document.updatedBy?.id
|
subtitle={
|
||||||
? t("Last edited")
|
model.id === document.createdBy?.id
|
||||||
: t("Previously edited")
|
? document.sourceMetadata?.createdByName
|
||||||
}
|
? t("Imported")
|
||||||
border={false}
|
: t("Creator")
|
||||||
small
|
: 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>
|
</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>
|
</Flex>
|
||||||
) : null}
|
) : null}
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
@@ -251,16 +234,6 @@ function countWords(text: string): number {
|
|||||||
return t ? t.replace(/-/g, " ").split(/\s+/g).length : 0;
|
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")`
|
const ListSpacing = styled("div")`
|
||||||
margin-top: -0.5em;
|
margin-top: -0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|||||||
@@ -69,6 +69,8 @@
|
|||||||
"Comments": "Comments",
|
"Comments": "Comments",
|
||||||
"History": "History",
|
"History": "History",
|
||||||
"Insights": "Insights",
|
"Insights": "Insights",
|
||||||
|
"Disable viewer insights": "Disable viewer insights",
|
||||||
|
"Enable viewer insights": "Enable viewer insights",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"Drafts": "Drafts",
|
"Drafts": "Drafts",
|
||||||
"Trash": "Trash",
|
"Trash": "Trash",
|
||||||
@@ -583,13 +585,10 @@
|
|||||||
"Creator": "Creator",
|
"Creator": "Creator",
|
||||||
"Last edited": "Last edited",
|
"Last edited": "Last edited",
|
||||||
"Previously edited": "Previously edited",
|
"Previously edited": "Previously edited",
|
||||||
"Views": "Views",
|
|
||||||
"No one else has viewed yet": "No one else has viewed yet",
|
"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": "Viewed {{ count }} time by {{ teamMembers }} people",
|
||||||
"Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people",
|
"Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people",
|
||||||
"Viewer insights": "Viewer insights",
|
"Viewer insights are disabled.": "Viewer insights are disabled.",
|
||||||
"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",
|
|
||||||
"Sorry, the last change could not be persisted – please reload the page": "Sorry, the last change could not be persisted – please reload the page",
|
"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": "{{ count }} day",
|
||||||
"{{ count }} days_plural": "{{ count }} days",
|
"{{ count }} days_plural": "{{ count }} days",
|
||||||
@@ -800,6 +799,7 @@
|
|||||||
"Shared nested": "Shared nested",
|
"Shared nested": "Shared nested",
|
||||||
"Nested documents are publicly available": "Nested documents are publicly available",
|
"Nested documents are publicly available": "Nested documents are publicly available",
|
||||||
"Domain": "Domain",
|
"Domain": "Domain",
|
||||||
|
"Views": "Views",
|
||||||
"Everyone": "Everyone",
|
"Everyone": "Everyone",
|
||||||
"Admins": "Admins",
|
"Admins": "Admins",
|
||||||
"Settings saved": "Settings saved",
|
"Settings saved": "Settings saved",
|
||||||
|
|||||||
Reference in New Issue
Block a user