2017-05-18 02:36:31 +00:00
|
|
|
|
// @flow
|
2021-01-14 06:00:25 +00:00
|
|
|
|
import { observer } from "mobx-react";
|
2020-08-09 05:53:59 +00:00
|
|
|
|
import * as React from "react";
|
2021-01-14 06:00:25 +00:00
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import { useHistory } from "react-router-dom";
|
|
|
|
|
import { useMenuState, MenuButton } from "reakit/Menu";
|
2021-03-21 06:20:49 +00:00
|
|
|
|
import { VisuallyHidden } from "reakit/VisuallyHidden";
|
2021-01-14 06:00:25 +00:00
|
|
|
|
import styled from "styled-components";
|
2020-08-09 05:53:59 +00:00
|
|
|
|
import Document from "models/Document";
|
2020-08-08 22:18:37 +00:00
|
|
|
|
import DocumentDelete from "scenes/DocumentDelete";
|
2021-03-31 01:46:14 +00:00
|
|
|
|
import DocumentMove from "scenes/DocumentMove";
|
2021-06-25 23:14:40 +00:00
|
|
|
|
import DocumentPermanentDelete from "scenes/DocumentPermanentDelete";
|
2020-08-08 22:18:37 +00:00
|
|
|
|
import DocumentTemplatize from "scenes/DocumentTemplatize";
|
2020-09-07 18:51:09 +00:00
|
|
|
|
import CollectionIcon from "components/CollectionIcon";
|
2021-01-14 06:00:25 +00:00
|
|
|
|
import ContextMenu from "components/ContextMenu";
|
|
|
|
|
import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
|
|
|
|
|
import Template from "components/ContextMenu/Template";
|
|
|
|
|
import Flex from "components/Flex";
|
2020-08-09 05:53:59 +00:00
|
|
|
|
import Modal from "components/Modal";
|
2021-01-14 06:00:25 +00:00
|
|
|
|
import useStores from "hooks/useStores";
|
2021-03-21 06:20:49 +00:00
|
|
|
|
import getDataTransferFiles from "utils/getDataTransferFiles";
|
2019-04-18 02:11:23 +00:00
|
|
|
|
import {
|
2020-09-01 03:03:05 +00:00
|
|
|
|
documentHistoryUrl,
|
|
|
|
|
documentUrl,
|
2020-08-08 22:18:37 +00:00
|
|
|
|
editDocumentUrl,
|
2019-04-18 02:11:23 +00:00
|
|
|
|
newDocumentUrl,
|
2020-06-20 20:59:15 +00:00
|
|
|
|
} from "utils/routeHelpers";
|
2017-05-18 02:36:31 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
type Props = {|
|
2018-05-05 23:16:08 +00:00
|
|
|
|
document: Document,
|
|
|
|
|
className: string,
|
2019-10-16 04:42:07 +00:00
|
|
|
|
isRevision?: boolean,
|
2018-05-06 05:45:10 +00:00
|
|
|
|
showPrint?: boolean,
|
2021-01-14 06:00:25 +00:00
|
|
|
|
modal?: boolean,
|
2018-12-15 22:06:29 +00:00
|
|
|
|
showToggleEmbeds?: boolean,
|
2019-04-09 04:47:27 +00:00
|
|
|
|
showPin?: boolean,
|
2021-01-14 06:00:25 +00:00
|
|
|
|
label?: (any) => React.Node,
|
2019-07-13 17:15:38 +00:00
|
|
|
|
onOpen?: () => void,
|
|
|
|
|
onClose?: () => void,
|
2021-01-14 06:00:25 +00:00
|
|
|
|
|};
|
2017-05-18 02:36:31 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
function DocumentMenu({
|
|
|
|
|
document,
|
|
|
|
|
isRevision,
|
|
|
|
|
className,
|
|
|
|
|
modal = true,
|
|
|
|
|
showToggleEmbeds,
|
|
|
|
|
showPrint,
|
|
|
|
|
showPin,
|
|
|
|
|
label,
|
|
|
|
|
onOpen,
|
|
|
|
|
onClose,
|
|
|
|
|
}: Props) {
|
2021-03-21 06:20:49 +00:00
|
|
|
|
const { policies, collections, ui, documents } = useStores();
|
2021-03-25 02:23:16 +00:00
|
|
|
|
const menu = useMenuState({
|
|
|
|
|
modal,
|
|
|
|
|
unstable_preventOverflow: true,
|
|
|
|
|
unstable_fixed: true,
|
|
|
|
|
unstable_flip: true,
|
|
|
|
|
});
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const history = useHistory();
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const [renderModals, setRenderModals] = React.useState(false);
|
|
|
|
|
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
|
2021-06-25 23:14:40 +00:00
|
|
|
|
const [
|
|
|
|
|
showPermanentDeleteModal,
|
|
|
|
|
setShowPermanentDeleteModal,
|
|
|
|
|
] = React.useState(false);
|
2021-03-31 01:46:14 +00:00
|
|
|
|
const [showMoveModal, setShowMoveModal] = React.useState(false);
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const [showTemplateModal, setShowTemplateModal] = React.useState(false);
|
2021-03-21 06:20:49 +00:00
|
|
|
|
const file = React.useRef<?HTMLInputElement>();
|
2019-01-19 08:23:39 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleOpen = React.useCallback(() => {
|
|
|
|
|
setRenderModals(true);
|
|
|
|
|
if (onOpen) {
|
|
|
|
|
onOpen();
|
2019-10-16 04:42:07 +00:00
|
|
|
|
}
|
2021-01-14 06:00:25 +00:00
|
|
|
|
}, [onOpen]);
|
2020-08-08 22:18:37 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleDuplicate = React.useCallback(
|
|
|
|
|
async (ev: SyntheticEvent<>) => {
|
|
|
|
|
const duped = await document.duplicate();
|
2020-08-08 22:18:37 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
// when duplicating, go straight to the duplicated document content
|
|
|
|
|
history.push(duped.url);
|
|
|
|
|
ui.showToast(t("Document duplicated"), { type: "success" });
|
|
|
|
|
},
|
|
|
|
|
[ui, t, history, document]
|
|
|
|
|
);
|
2019-04-06 23:20:27 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleArchive = React.useCallback(
|
|
|
|
|
async (ev: SyntheticEvent<>) => {
|
|
|
|
|
await document.archive();
|
|
|
|
|
ui.showToast(t("Document archived"), { type: "success" });
|
|
|
|
|
},
|
|
|
|
|
[ui, t, document]
|
|
|
|
|
);
|
2018-06-05 13:57:26 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleRestore = React.useCallback(
|
|
|
|
|
async (ev: SyntheticEvent<>, options?: { collectionId: string }) => {
|
|
|
|
|
await document.restore(options);
|
|
|
|
|
ui.showToast(t("Document restored"), { type: "success" });
|
|
|
|
|
},
|
|
|
|
|
[ui, t, document]
|
|
|
|
|
);
|
2020-09-01 03:03:05 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleUnpublish = React.useCallback(
|
|
|
|
|
async (ev: SyntheticEvent<>) => {
|
|
|
|
|
await document.unpublish();
|
|
|
|
|
ui.showToast(t("Document unpublished"), { type: "success" });
|
|
|
|
|
},
|
|
|
|
|
[ui, t, document]
|
|
|
|
|
);
|
2018-03-01 07:28:36 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handlePrint = React.useCallback((ev: SyntheticEvent<>) => {
|
|
|
|
|
window.print();
|
|
|
|
|
}, []);
|
2018-03-01 07:28:36 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleStar = React.useCallback(
|
|
|
|
|
(ev: SyntheticEvent<>) => {
|
2021-01-21 07:07:39 +00:00
|
|
|
|
ev.preventDefault();
|
2021-01-14 06:00:25 +00:00
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
document.star();
|
|
|
|
|
},
|
|
|
|
|
[document]
|
|
|
|
|
);
|
2017-09-15 04:59:59 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const handleUnstar = React.useCallback(
|
|
|
|
|
(ev: SyntheticEvent<>) => {
|
2021-01-21 07:07:39 +00:00
|
|
|
|
ev.preventDefault();
|
2021-01-14 06:00:25 +00:00
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
document.unstar();
|
|
|
|
|
},
|
|
|
|
|
[document]
|
|
|
|
|
);
|
2017-09-15 04:59:59 +00:00
|
|
|
|
|
2021-02-10 03:04:03 +00:00
|
|
|
|
const collection = collections.get(document.collectionId);
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const can = policies.abilities(document.id);
|
|
|
|
|
const canViewHistory = can.read && !can.restore;
|
2020-08-12 17:49:15 +00:00
|
|
|
|
|
2021-03-21 06:20:49 +00:00
|
|
|
|
const stopPropagation = React.useCallback((ev: SyntheticEvent<>) => {
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleImportDocument = React.useCallback(
|
|
|
|
|
(ev: SyntheticEvent<>) => {
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
|
|
|
|
|
// simulate a click on the file upload input element
|
|
|
|
|
if (file.current) {
|
|
|
|
|
file.current.click();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[file]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleFilePicked = React.useCallback(
|
|
|
|
|
async (ev: SyntheticEvent<>) => {
|
|
|
|
|
const files = getDataTransferFiles(ev);
|
|
|
|
|
|
|
|
|
|
// Because this is the onChange handler it's possible for the change to be
|
|
|
|
|
// from previously selecting a file to not selecting a file – aka empty
|
|
|
|
|
if (!files.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!collection) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const file = files[0];
|
|
|
|
|
const importedDocument = await documents.import(
|
|
|
|
|
file,
|
|
|
|
|
document.id,
|
|
|
|
|
collection.id,
|
|
|
|
|
{
|
|
|
|
|
publish: true,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
history.push(importedDocument.url);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
ui.showToast(err.message, {
|
|
|
|
|
type: "error",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[history, ui, collection, documents, document.id]
|
|
|
|
|
);
|
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
return (
|
|
|
|
|
<>
|
2021-03-21 06:20:49 +00:00
|
|
|
|
<VisuallyHidden>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
ref={file}
|
|
|
|
|
onChange={handleFilePicked}
|
|
|
|
|
onClick={stopPropagation}
|
|
|
|
|
accept={documents.importFileTypes.join(", ")}
|
|
|
|
|
tabIndex="-1"
|
|
|
|
|
/>
|
|
|
|
|
</VisuallyHidden>
|
2021-01-14 06:00:25 +00:00
|
|
|
|
{label ? (
|
|
|
|
|
<MenuButton {...menu}>{label}</MenuButton>
|
|
|
|
|
) : (
|
2021-01-23 03:31:30 +00:00
|
|
|
|
<OverflowMenuButton
|
|
|
|
|
className={className}
|
|
|
|
|
aria-label={t("Show menu")}
|
|
|
|
|
{...menu}
|
|
|
|
|
/>
|
2021-01-14 06:00:25 +00:00
|
|
|
|
)}
|
|
|
|
|
<ContextMenu
|
|
|
|
|
{...menu}
|
|
|
|
|
aria-label={t("Document options")}
|
|
|
|
|
onOpen={handleOpen}
|
|
|
|
|
onClose={onClose}
|
|
|
|
|
>
|
|
|
|
|
<Template
|
|
|
|
|
{...menu}
|
|
|
|
|
items={[
|
|
|
|
|
{
|
|
|
|
|
title: t("Restore"),
|
2021-06-11 05:52:32 +00:00
|
|
|
|
visible: (!!collection && can.restore) || can.unarchive,
|
2021-01-14 06:00:25 +00:00
|
|
|
|
onClick: handleRestore,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Restore"),
|
|
|
|
|
visible: !collection && !!can.restore,
|
|
|
|
|
style: {
|
|
|
|
|
left: -170,
|
|
|
|
|
position: "relative",
|
|
|
|
|
top: -40,
|
2020-11-15 04:44:31 +00:00
|
|
|
|
},
|
2021-01-14 06:00:25 +00:00
|
|
|
|
hover: true,
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
type: "heading",
|
|
|
|
|
title: t("Choose a collection"),
|
2020-11-15 04:44:31 +00:00
|
|
|
|
},
|
2021-01-14 06:00:25 +00:00
|
|
|
|
...collections.orderedData.map((collection) => {
|
|
|
|
|
const can = policies.abilities(collection.id);
|
2020-08-08 22:18:37 +00:00
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
return {
|
|
|
|
|
title: (
|
|
|
|
|
<Flex align="center">
|
|
|
|
|
<CollectionIcon collection={collection} />
|
|
|
|
|
<CollectionName>{collection.name}</CollectionName>
|
|
|
|
|
</Flex>
|
|
|
|
|
),
|
|
|
|
|
onClick: (ev) =>
|
|
|
|
|
handleRestore(ev, { collectionId: collection.id }),
|
|
|
|
|
disabled: !can.update,
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Unpin"),
|
|
|
|
|
onClick: document.unpin,
|
|
|
|
|
visible: !!(showPin && document.pinned && can.unpin),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Pin to collection"),
|
|
|
|
|
onClick: document.pin,
|
|
|
|
|
visible: !!(showPin && !document.pinned && can.pin),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Unstar"),
|
|
|
|
|
onClick: handleUnstar,
|
|
|
|
|
visible: document.isStarred && !!can.unstar,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Star"),
|
|
|
|
|
onClick: handleStar,
|
|
|
|
|
visible: !document.isStarred && !!can.star,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Enable embeds"),
|
|
|
|
|
onClick: document.enableEmbeds,
|
|
|
|
|
visible: !!showToggleEmbeds && document.embedsDisabled,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Disable embeds"),
|
|
|
|
|
onClick: document.disableEmbeds,
|
|
|
|
|
visible: !!showToggleEmbeds && !document.embedsDisabled,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "separator",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("New nested document"),
|
|
|
|
|
to: newDocumentUrl(document.collectionId, {
|
|
|
|
|
parentDocumentId: document.id,
|
|
|
|
|
}),
|
|
|
|
|
visible: !!can.createChildDocument,
|
|
|
|
|
},
|
2021-03-21 06:20:49 +00:00
|
|
|
|
{
|
|
|
|
|
title: t("Import document"),
|
|
|
|
|
visible: can.createChildDocument,
|
|
|
|
|
onClick: handleImportDocument,
|
|
|
|
|
},
|
2021-01-14 06:00:25 +00:00
|
|
|
|
{
|
|
|
|
|
title: `${t("Create template")}…`,
|
|
|
|
|
onClick: () => setShowTemplateModal(true),
|
|
|
|
|
visible: !!can.update && !document.isTemplate,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Edit"),
|
|
|
|
|
to: editDocumentUrl(document),
|
|
|
|
|
visible: !!can.update,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Duplicate"),
|
|
|
|
|
onClick: handleDuplicate,
|
|
|
|
|
visible: !!can.update,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Unpublish"),
|
|
|
|
|
onClick: handleUnpublish,
|
|
|
|
|
visible: !!can.unpublish,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Archive"),
|
|
|
|
|
onClick: handleArchive,
|
|
|
|
|
visible: !!can.archive,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: `${t("Delete")}…`,
|
|
|
|
|
onClick: () => setShowDeleteModal(true),
|
|
|
|
|
visible: !!can.delete,
|
|
|
|
|
},
|
2021-06-25 23:14:40 +00:00
|
|
|
|
{
|
|
|
|
|
title: `${t("Permanently delete")}…`,
|
|
|
|
|
onClick: () => setShowPermanentDeleteModal(true),
|
|
|
|
|
visible: can.permanentDelete,
|
|
|
|
|
},
|
2021-01-14 06:00:25 +00:00
|
|
|
|
{
|
|
|
|
|
title: `${t("Move")}…`,
|
2021-03-31 01:46:14 +00:00
|
|
|
|
onClick: () => setShowMoveModal(true),
|
2021-01-14 06:00:25 +00:00
|
|
|
|
visible: !!can.move,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "separator",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("History"),
|
|
|
|
|
to: isRevision
|
|
|
|
|
? documentUrl(document)
|
|
|
|
|
: documentHistoryUrl(document),
|
|
|
|
|
visible: canViewHistory,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Download"),
|
|
|
|
|
onClick: document.download,
|
|
|
|
|
visible: !!can.download,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t("Print"),
|
|
|
|
|
onClick: handlePrint,
|
|
|
|
|
visible: !!showPrint,
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
</ContextMenu>
|
|
|
|
|
{renderModals && (
|
|
|
|
|
<>
|
2021-06-25 23:14:40 +00:00
|
|
|
|
{can.move && (
|
|
|
|
|
<Modal
|
|
|
|
|
title={t("Move {{ documentName }}", {
|
|
|
|
|
documentName: document.noun,
|
|
|
|
|
})}
|
2021-03-31 01:46:14 +00:00
|
|
|
|
onRequestClose={() => setShowMoveModal(false)}
|
2021-06-25 23:14:40 +00:00
|
|
|
|
isOpen={showMoveModal}
|
|
|
|
|
>
|
|
|
|
|
<DocumentMove
|
|
|
|
|
document={document}
|
|
|
|
|
onRequestClose={() => setShowMoveModal(false)}
|
|
|
|
|
/>
|
|
|
|
|
</Modal>
|
|
|
|
|
)}
|
|
|
|
|
{can.delete && (
|
|
|
|
|
<Modal
|
|
|
|
|
title={t("Delete {{ documentName }}", {
|
|
|
|
|
documentName: document.noun,
|
|
|
|
|
})}
|
|
|
|
|
onRequestClose={() => setShowDeleteModal(false)}
|
|
|
|
|
isOpen={showDeleteModal}
|
|
|
|
|
>
|
|
|
|
|
<DocumentDelete
|
|
|
|
|
document={document}
|
|
|
|
|
onSubmit={() => setShowDeleteModal(false)}
|
|
|
|
|
/>
|
|
|
|
|
</Modal>
|
|
|
|
|
)}
|
|
|
|
|
{can.permanentDelete && (
|
|
|
|
|
<Modal
|
|
|
|
|
title={t("Permanently delete {{ documentName }}", {
|
|
|
|
|
documentName: document.noun,
|
|
|
|
|
})}
|
|
|
|
|
onRequestClose={() => setShowPermanentDeleteModal(false)}
|
|
|
|
|
isOpen={showPermanentDeleteModal}
|
|
|
|
|
>
|
|
|
|
|
<DocumentPermanentDelete
|
|
|
|
|
document={document}
|
|
|
|
|
onSubmit={() => setShowPermanentDeleteModal(false)}
|
|
|
|
|
/>
|
|
|
|
|
</Modal>
|
|
|
|
|
)}
|
|
|
|
|
{can.update && (
|
|
|
|
|
<Modal
|
|
|
|
|
title={t("Create template")}
|
|
|
|
|
onRequestClose={() => setShowTemplateModal(false)}
|
|
|
|
|
isOpen={showTemplateModal}
|
|
|
|
|
>
|
|
|
|
|
<DocumentTemplatize
|
|
|
|
|
document={document}
|
|
|
|
|
onSubmit={() => setShowTemplateModal(false)}
|
|
|
|
|
/>
|
|
|
|
|
</Modal>
|
|
|
|
|
)}
|
2021-01-14 06:00:25 +00:00
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
2017-05-18 02:36:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-14 06:00:25 +00:00
|
|
|
|
const CollectionName = styled.div`
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
export default observer(DocumentMenu);
|