import emojiRegex from "emoji-regex"; import { observer } from "mobx-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useHistory, useRouteMatch } from "react-router-dom"; import styled from "styled-components"; import { s } from "@shared/styles"; import { stringToColor } from "@shared/utils/color"; import User from "~/models/User"; import Avatar from "~/components/Avatar"; import { useDocumentContext } from "~/components/DocumentContext"; import DocumentViews from "~/components/DocumentViews"; import Flex from "~/components/Flex"; import ListItem from "~/components/List/Item"; import PaginatedList from "~/components/PaginatedList"; import Text from "~/components/Text"; import Time from "~/components/Time"; 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 match = useRouteMatch<{ documentSlug: string }>(); const history = useHistory(); const selectedText = useTextSelection(); const document = documents.getByUrl(match.params.documentSlug); const { editor } = useDocumentContext(); const text = editor?.getPlainText(); const stats = useTextStats(text ?? "", selectedText); const can = usePolicy(document); const documentViews = document ? views.inDocument(document.id) : []; const onCloseInsights = () => { if (document) { history.push(documentPath(document)); } }; useKeyDown("Escape", onCloseInsights); return ( {document ? (
{document.sourceMetadata && ( <> {t("Source")} { {t("Imported from {{ source }}", { source: document.sourceName ?? `“${document.sourceMetadata.fileName}”`, })} } )} {t("Stats")} {stats.total.words > 0 && (
  • {t(`{{ count }} minute read`, { count: stats.total.readingTime, })}
  • )}
  • {t(`{{ count }} words`, { count: stats.total.words })}
  • {t(`{{ count }} characters`, { count: stats.total.characters, })}
  • {t(`{{ number }} emoji`, { number: stats.total.emoji })}
  • {stats.selected.characters === 0 ? (
  • {t("No text selected")}
  • ) : ( <>
  • {t(`{{ count }} words selected`, { count: stats.selected.words, })}
  • {t(`{{ count }} characters selected`, { count: stats.selected.characters, })}
  • )}
    {t("Contributors")} {t(`Created`)} {document.sourceMetadata?.createdByName && ( } subtitle={t("Creator")} border={false} small /> )} ( } 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 /> )} /> {(document.insightsEnabled || can.updateInsights) && ( {t("Viewed by")} {can.updateInsights && } {document.insightsEnabled ? ( <> {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, } )} . {documentViews.length > 1 && ( )} ) : ( {t("Viewer insights are disabled.")} )} )}
    ) : null}
    ); } function useTextStats(text: string, selectedText: string) { const numTotalWords = countWords(text); const regex = emojiRegex(); const matches = Array.from(text.matchAll(regex)); return { total: { words: numTotalWords, characters: text.length, emoji: matches.length ?? 0, readingTime: Math.max(1, Math.floor(numTotalWords / 200)), }, selected: { words: countWords(selectedText), characters: selectedText.length, }, }; } function countWords(text: string): number { const t = text.trim(); // Hyphenated words are counted as two words return t ? t.replace(/-/g, " ").split(/\s+/g).length : 0; } const ListSpacing = styled("div")` margin-top: -0.5em; margin-bottom: 0.5em; `; const List = styled("ul")` margin: 0; padding: 0; list-style: none; li:before { content: "·"; display: inline-block; font-weight: 600; color: ${s("textTertiary")}; width: 10px; } `; const Content = styled(Flex)` padding: 0 16px; user-select: none; `; const Heading = styled("h3")` font-size: 15px; `; export default observer(Insights);