diff --git a/app/components/DocumentList.js b/app/components/DocumentList.js
index 00d009564..d8c8d20ba 100644
--- a/app/components/DocumentList.js
+++ b/app/components/DocumentList.js
@@ -2,7 +2,7 @@
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import * as React from "react";
import Document from "models/Document";
-import DocumentPreview from "components/DocumentPreview";
+import DocumentListItem from "components/DocumentListItem";
type Props = {
documents: Document[],
@@ -18,7 +18,7 @@ export default function DocumentList({ limit, documents, ...rest }: Props) {
defaultActiveChildIndex={0}
>
{items.map((document) => (
-
+
))}
);
diff --git a/app/components/DocumentListItem.js b/app/components/DocumentListItem.js
new file mode 100644
index 000000000..1f88f0aed
--- /dev/null
+++ b/app/components/DocumentListItem.js
@@ -0,0 +1,242 @@
+// @flow
+import { observer } from "mobx-react";
+import { StarredIcon, PlusIcon } from "outline-icons";
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import { Link, useHistory } from "react-router-dom";
+import styled, { withTheme } from "styled-components";
+import Document from "models/Document";
+import Badge from "components/Badge";
+import Button from "components/Button";
+import DocumentMeta from "components/DocumentMeta";
+import EventBoundary from "components/EventBoundary";
+import Flex from "components/Flex";
+import Highlight from "components/Highlight";
+import Tooltip from "components/Tooltip";
+import useCurrentUser from "hooks/useCurrentUser";
+import DocumentMenu from "menus/DocumentMenu";
+import { newDocumentUrl } from "utils/routeHelpers";
+
+type Props = {
+ document: Document,
+ highlight?: ?string,
+ context?: ?string,
+ showCollection?: boolean,
+ showPublished?: boolean,
+ showPin?: boolean,
+ showDraft?: boolean,
+ showTemplate?: boolean,
+};
+
+const SEARCH_RESULT_REGEX = /]*>(.*?)<\/b>/gi;
+
+function replaceResultMarks(tag: string) {
+ // don't use SEARCH_RESULT_REGEX here as it causes
+ // an infinite loop to trigger a regex inside it's own callback
+ return tag.replace(/]*>(.*?)<\/b>/gi, "$1");
+}
+
+function DocumentListItem(props: Props) {
+ const { t } = useTranslation();
+ const currentUser = useCurrentUser();
+ const history = useHistory();
+ const {
+ document,
+ showCollection,
+ showPublished,
+ showPin,
+ showDraft = true,
+ showTemplate,
+ highlight,
+ context,
+ } = props;
+
+ const handleStar = React.useCallback(
+ (ev: SyntheticEvent<>) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ document.star();
+ },
+ [document]
+ );
+
+ const handleUnstar = React.useCallback(
+ (ev: SyntheticEvent<>) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ document.unstar();
+ },
+ [document]
+ );
+
+ const handleNewFromTemplate = React.useCallback(
+ (ev: SyntheticEvent<>) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ history.push(
+ newDocumentUrl(document.collectionId, {
+ templateId: document.id,
+ })
+ );
+ },
+ [history, document]
+ );
+
+ const queryIsInTitle =
+ !!highlight &&
+ !!document.title.toLowerCase().includes(highlight.toLowerCase());
+
+ return (
+
+
+
+ {document.isNew && document.createdBy.id !== currentUser.id && (
+ {t("New")}
+ )}
+ {!document.isDraft && !document.isArchived && !document.isTemplate && (
+
+ {document.isStarred ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {document.isDraft && showDraft && (
+
+ {t("Draft")}
+
+ )}
+ {document.isTemplate && showTemplate && (
+ {t("Template")}
+ )}
+
+ {document.isTemplate && !document.isArchived && !document.isDeleted && (
+ } neutral>
+ {t("New doc")}
+
+ )}
+
+
+
+
+
+
+
+ {!queryIsInTitle && (
+
+ )}
+
+
+ );
+}
+
+const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
+
+))`
+ flex-shrink: 0;
+ opacity: ${(props) => (props.solid ? "1 !important" : 0)};
+ transition: all 100ms ease-in-out;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+ &:active {
+ transform: scale(0.95);
+ }
+`);
+
+const SecondaryActions = styled(Flex)`
+ align-items: center;
+ position: absolute;
+ right: 16px;
+ top: 50%;
+ transform: translateY(-50%);
+`;
+
+const DocumentLink = styled(Link)`
+ display: block;
+ margin: 10px -8px;
+ padding: 6px 8px;
+ border-radius: 8px;
+ max-height: 50vh;
+ min-width: 100%;
+ max-width: calc(100vw - 40px);
+ overflow: hidden;
+ position: relative;
+
+ ${SecondaryActions} {
+ opacity: 0;
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: ${(props) => props.theme.listItemHoverBackground};
+
+ ${SecondaryActions} {
+ opacity: 1;
+ }
+
+ ${StyledStar} {
+ opacity: 0.5;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
+`;
+
+const Heading = styled.h3`
+ display: flex;
+ align-items: center;
+ height: 24px;
+ margin-top: 0;
+ margin-bottom: 0.25em;
+ overflow: hidden;
+ white-space: nowrap;
+ color: ${(props) => props.theme.text};
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
+ Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
+`;
+
+const Actions = styled(Flex)`
+ margin-left: 4px;
+ align-items: center;
+`;
+
+const Title = styled(Highlight)`
+ max-width: 90%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+`;
+
+const ResultContext = styled(Highlight)`
+ display: block;
+ color: ${(props) => props.theme.textTertiary};
+ font-size: 14px;
+ margin-top: -0.25em;
+ margin-bottom: 0.25em;
+`;
+
+export default observer(DocumentListItem);
diff --git a/app/components/DocumentPreview/DocumentPreview.js b/app/components/DocumentPreview/DocumentPreview.js
deleted file mode 100644
index 6ae35efda..000000000
--- a/app/components/DocumentPreview/DocumentPreview.js
+++ /dev/null
@@ -1,247 +0,0 @@
-// @flow
-import { observable } from "mobx";
-import { observer } from "mobx-react";
-import { StarredIcon, PlusIcon } from "outline-icons";
-import * as React from "react";
-import { withTranslation, type TFunction } from "react-i18next";
-import { Link, Redirect } from "react-router-dom";
-import styled, { withTheme } from "styled-components";
-import Document from "models/Document";
-import Badge from "components/Badge";
-import Button from "components/Button";
-import DocumentMeta from "components/DocumentMeta";
-import EventBoundary from "components/EventBoundary";
-import Flex from "components/Flex";
-import Highlight from "components/Highlight";
-import Tooltip from "components/Tooltip";
-import DocumentMenu from "menus/DocumentMenu";
-import { newDocumentUrl } from "utils/routeHelpers";
-
-type Props = {
- document: Document,
- highlight?: ?string,
- context?: ?string,
- showCollection?: boolean,
- showPublished?: boolean,
- showPin?: boolean,
- showDraft?: boolean,
- showTemplate?: boolean,
- t: TFunction,
-};
-
-const SEARCH_RESULT_REGEX = /]*>(.*?)<\/b>/gi;
-
-@observer
-class DocumentPreview extends React.Component {
- @observable redirectTo: ?string;
-
- handleStar = (ev: SyntheticEvent<>) => {
- ev.preventDefault();
- ev.stopPropagation();
- this.props.document.star();
- };
-
- handleUnstar = (ev: SyntheticEvent<>) => {
- ev.preventDefault();
- ev.stopPropagation();
- this.props.document.unstar();
- };
-
- replaceResultMarks = (tag: string) => {
- // don't use SEARCH_RESULT_REGEX here as it causes
- // an infinite loop to trigger a regex inside it's own callback
- return tag.replace(/]*>(.*?)<\/b>/gi, "$1");
- };
-
- handleNewFromTemplate = (event: SyntheticEvent<>) => {
- event.preventDefault();
- event.stopPropagation();
-
- const { document } = this.props;
-
- this.redirectTo = newDocumentUrl(document.collectionId, {
- templateId: document.id,
- });
- };
-
- render() {
- const {
- document,
- showCollection,
- showPublished,
- showPin,
- showDraft = true,
- showTemplate,
- highlight,
- context,
- t,
- } = this.props;
-
- if (this.redirectTo) {
- return ;
- }
-
- const queryIsInTitle =
- !!highlight &&
- !!document.title.toLowerCase().includes(highlight.toLowerCase());
-
- return (
-
-
-
- {document.isNew && {t("New")}}
- {!document.isDraft &&
- !document.isArchived &&
- !document.isTemplate && (
-
- {document.isStarred ? (
-
- ) : (
-
- )}
-
- )}
- {document.isDraft && showDraft && (
-
- {t("Draft")}
-
- )}
- {document.isTemplate && showTemplate && (
- {t("Template")}
- )}
-
- {document.isTemplate &&
- !document.isArchived &&
- !document.isDeleted && (
- }
- neutral
- >
- {t("New doc")}
-
- )}
-
-
-
-
-
-
-
- {!queryIsInTitle && (
-
- )}
-
-
- );
- }
-}
-
-const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
-
-))`
- flex-shrink: 0;
- opacity: ${(props) => (props.solid ? "1 !important" : 0)};
- transition: all 100ms ease-in-out;
-
- &:hover {
- transform: scale(1.1);
- }
- &:active {
- transform: scale(0.95);
- }
-`);
-
-const SecondaryActions = styled(Flex)`
- align-items: center;
- position: absolute;
- right: 16px;
- top: 50%;
- transform: translateY(-50%);
-`;
-
-const DocumentLink = styled(Link)`
- display: block;
- margin: 10px -8px;
- padding: 6px 8px;
- border-radius: 8px;
- max-height: 50vh;
- min-width: 100%;
- max-width: calc(100vw - 40px);
- overflow: hidden;
- position: relative;
-
- ${SecondaryActions} {
- opacity: 0;
- }
-
- &:hover,
- &:active,
- &:focus {
- background: ${(props) => props.theme.listItemHoverBackground};
-
- ${SecondaryActions} {
- opacity: 1;
- }
-
- ${StyledStar} {
- opacity: 0.5;
-
- &:hover {
- opacity: 1;
- }
- }
- }
-`;
-
-const Heading = styled.h3`
- display: flex;
- align-items: center;
- height: 24px;
- margin-top: 0;
- margin-bottom: 0.25em;
- overflow: hidden;
- white-space: nowrap;
- color: ${(props) => props.theme.text};
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
- Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
-`;
-
-const Actions = styled(Flex)`
- margin-left: 4px;
- align-items: center;
-`;
-
-const Title = styled(Highlight)`
- max-width: 90%;
- overflow: hidden;
- text-overflow: ellipsis;
-`;
-
-const ResultContext = styled(Highlight)`
- display: block;
- color: ${(props) => props.theme.textTertiary};
- font-size: 14px;
- margin-top: -0.25em;
- margin-bottom: 0.25em;
-`;
-
-export default withTranslation()(DocumentPreview);
diff --git a/app/components/DocumentPreview/index.js b/app/components/DocumentPreview/index.js
deleted file mode 100644
index 5b0f0d9da..000000000
--- a/app/components/DocumentPreview/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// @flow
-import DocumentPreview from "./DocumentPreview";
-export default DocumentPreview;
diff --git a/app/components/PaginatedDocumentList.js b/app/components/PaginatedDocumentList.js
index 8d2188e8c..45884e20b 100644
--- a/app/components/PaginatedDocumentList.js
+++ b/app/components/PaginatedDocumentList.js
@@ -2,7 +2,7 @@
import { observer } from "mobx-react";
import * as React from "react";
import Document from "models/Document";
-import DocumentPreview from "components/DocumentPreview";
+import DocumentListItem from "components/DocumentListItem";
import PaginatedList from "components/PaginatedList";
type Props = {
@@ -26,7 +26,7 @@ class PaginatedDocumentList extends React.Component {
fetch={fetch}
options={options}
renderItem={(item) => (
-
+
)}
/>
);
diff --git a/app/scenes/Search/Search.js b/app/scenes/Search/Search.js
index b87c16417..876b56aa4 100644
--- a/app/scenes/Search/Search.js
+++ b/app/scenes/Search/Search.js
@@ -21,7 +21,7 @@ import UsersStore from "stores/UsersStore";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
-import DocumentPreview from "components/DocumentPreview";
+import DocumentListItem from "components/DocumentListItem";
import Empty from "components/Empty";
import Fade from "components/Fade";
import Flex from "components/Flex";
@@ -350,7 +350,7 @@ class Search extends React.Component {
if (!document) return null;
return (
- index === 0 && this.setFirstDocumentRef(ref)}
key={document.id}
document={document}
diff --git a/app/utils/keyboard.js b/app/utils/keyboard.js
index 231e7e7ff..43291f537 100644
--- a/app/utils/keyboard.js
+++ b/app/utils/keyboard.js
@@ -5,6 +5,6 @@ export const metaDisplay = isMac ? "⌘" : "Ctrl";
export const meta = isMac ? "cmd" : "ctrl";
-export function isMetaKey(event: KeyboardEvent) {
+export function isMetaKey(event: KeyboardEvent | MouseEvent) {
return isMac ? event.metaKey : event.ctrlKey;
}