From 8c39487c8082ca4141d10c89679b4c9ddb2429bb Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 8 Aug 2022 17:31:53 +0200 Subject: [PATCH] Move various document menu actions to action definitions --- app/actions/definitions/documents.tsx | 138 ++++++++++++- app/actions/index.ts | 1 + app/menus/DocumentMenu.tsx | 215 +++------------------ app/types.ts | 1 + shared/i18n/locales/en_US/translation.json | 16 +- 5 files changed, 170 insertions(+), 201 deletions(-) diff --git a/app/actions/definitions/documents.tsx b/app/actions/definitions/documents.tsx index bcfb11d83..1092dbe49 100644 --- a/app/actions/definitions/documents.tsx +++ b/app/actions/definitions/documents.tsx @@ -11,9 +11,16 @@ import { ImportIcon, PinIcon, SearchIcon, + MoveIcon, + TrashIcon, + CrossIcon, + ArchiveIcon, } from "outline-icons"; import * as React from "react"; import { getEventFiles } from "@shared/utils/files"; +import DocumentDelete from "~/scenes/DocumentDelete"; +import DocumentMove from "~/scenes/DocumentMove"; +import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete"; import DocumentTemplatizeDialog from "~/components/DocumentTemplatizeDialog"; import { createAction } from "~/actions"; import { DocumentSection } from "~/actions/sections"; @@ -296,7 +303,7 @@ export const createTemplate = createAction({ return false; } const document = stores.documents.get(activeDocumentId); - return ( + return !!( !!activeCollectionId && stores.policies.abilities(activeCollectionId).update && !document?.isTemplate && @@ -329,15 +336,144 @@ export const searchDocumentsForQuery = (searchQuery: string) => visible: ({ location }) => location.pathname !== searchPath(), }); +export const moveDocument = createAction({ + name: ({ t }) => t("Move"), + section: DocumentSection, + icon: , + visible: ({ activeDocumentId, stores }) => { + if (!activeDocumentId) { + return false; + } + return !!stores.policies.abilities(activeDocumentId).move; + }, + perform: ({ activeDocumentId, stores, t }) => { + if (activeDocumentId) { + const document = stores.documents.get(activeDocumentId); + if (!document) { + return; + } + + stores.dialogs.openModal({ + title: t("Move {{ documentName }}", { + documentName: document.noun, + }), + content: ( + + ), + }); + } + }, +}); + +export const archiveDocument = createAction({ + name: ({ t }) => t("Archive"), + section: DocumentSection, + icon: , + visible: ({ activeDocumentId, stores }) => { + if (!activeDocumentId) { + return false; + } + return !!stores.policies.abilities(activeDocumentId).archive; + }, + perform: async ({ activeDocumentId, stores, t }) => { + if (activeDocumentId) { + const document = stores.documents.get(activeDocumentId); + if (!document) { + return; + } + + await document.archive(); + stores.toasts.showToast(t("Document archived"), { + type: "success", + }); + } + }, +}); + +export const deleteDocument = createAction({ + name: ({ t }) => t("Delete"), + section: DocumentSection, + icon: , + dangerous: true, + visible: ({ activeDocumentId, stores }) => { + if (!activeDocumentId) { + return false; + } + return !!stores.policies.abilities(activeDocumentId).delete; + }, + perform: ({ activeDocumentId, stores, t }) => { + if (activeDocumentId) { + const document = stores.documents.get(activeDocumentId); + if (!document) { + return; + } + + stores.dialogs.openModal({ + title: t("Delete {{ documentName }}", { + documentName: document.noun, + }), + isCentered: true, + content: ( + + ), + }); + } + }, +}); + +export const permanentlyDeleteDocument = createAction({ + name: ({ t }) => t("Permanently delete"), + section: DocumentSection, + icon: , + dangerous: true, + visible: ({ activeDocumentId, stores }) => { + if (!activeDocumentId) { + return false; + } + return !!stores.policies.abilities(activeDocumentId).permanentDelete; + }, + perform: ({ activeDocumentId, stores, t }) => { + if (activeDocumentId) { + const document = stores.documents.get(activeDocumentId); + if (!document) { + return; + } + + stores.dialogs.openModal({ + title: t("Permanently delete {{ documentName }}", { + documentName: document.noun, + }), + isCentered: true, + content: ( + + ), + }); + } + }, +}); + export const rootDocumentActions = [ openDocument, + archiveDocument, createDocument, createTemplate, + deleteDocument, importDocument, downloadDocument, starDocument, unstarDocument, duplicateDocument, + moveDocument, + permanentlyDeleteDocument, printDocument, pinDocumentToCollection, pinDocumentToHome, diff --git a/app/actions/index.ts b/app/actions/index.ts index b13eaa99b..6abb8ad87 100644 --- a/app/actions/index.ts +++ b/app/actions/index.ts @@ -56,6 +56,7 @@ export function actionToMenuItem( title, icon, visible, + dangerous: action.dangerous, onClick: () => action.perform && action.perform(context), selected: action.selected ? action.selected(context) : undefined, }; diff --git a/app/menus/DocumentMenu.tsx b/app/menus/DocumentMenu.tsx index d26a8780b..2b84faa74 100644 --- a/app/menus/DocumentMenu.tsx +++ b/app/menus/DocumentMenu.tsx @@ -1,20 +1,11 @@ import { observer } from "mobx-react"; import { EditIcon, - StarredIcon, - UnstarredIcon, - DuplicateIcon, - ArchiveIcon, - TrashIcon, - MoveIcon, HistoryIcon, UnpublishIcon, PrintIcon, - ImportIcon, NewDocumentIcon, - DownloadIcon, RestoreIcon, - CrossIcon, } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; @@ -25,19 +16,27 @@ import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; import { getEventFiles } from "@shared/utils/files"; import Document from "~/models/Document"; -import DocumentDelete from "~/scenes/DocumentDelete"; -import DocumentMove from "~/scenes/DocumentMove"; -import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete"; import CollectionIcon from "~/components/CollectionIcon"; import ContextMenu from "~/components/ContextMenu"; import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton"; import Separator from "~/components/ContextMenu/Separator"; import Template from "~/components/ContextMenu/Template"; import Flex from "~/components/Flex"; -import Modal from "~/components/Modal"; import Switch from "~/components/Switch"; import { actionToMenuItem } from "~/actions"; -import { pinDocument, createTemplate } from "~/actions/definitions/documents"; +import { + pinDocument, + createTemplate, + moveDocument, + deleteDocument, + permanentlyDeleteDocument, + downloadDocument, + importDocument, + starDocument, + unstarDocument, + duplicateDocument, + archiveDocument, +} from "~/actions/definitions/documents"; import useActionContext from "~/hooks/useActionContext"; import useCurrentTeam from "~/hooks/useCurrentTeam"; import useMobile from "~/hooks/useMobile"; @@ -94,39 +93,8 @@ function DocumentMenu({ }); const { t } = useTranslation(); const isMobile = useMobile(); - const [renderModals, setRenderModals] = React.useState(false); - const [showDeleteModal, setShowDeleteModal] = React.useState(false); - const [ - showPermanentDeleteModal, - setShowPermanentDeleteModal, - ] = React.useState(false); - const [showMoveModal, setShowMoveModal] = React.useState(false); const file = React.useRef(null); - const handleOpen = React.useCallback(() => { - setRenderModals(true); - - if (onOpen) { - onOpen(); - } - }, [onOpen]); - - const handleDuplicate = React.useCallback(async () => { - const duped = await document.duplicate(); - // when duplicating, go straight to the duplicated document content - history.push(duped.url); - showToast(t("Document duplicated"), { - type: "success", - }); - }, [t, history, showToast, document]); - - const handleArchive = React.useCallback(async () => { - await document.archive(); - showToast(t("Document archived"), { - type: "success", - }); - }, [showToast, t, document]); - const handleRestore = React.useCallback( async ( ev: React.SyntheticEvent, @@ -154,24 +122,6 @@ function DocumentMenu({ window.print(); }, [menu]); - const handleStar = React.useCallback( - (ev: React.SyntheticEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - document.star(); - }, - [document] - ); - - const handleUnstar = React.useCallback( - (ev: React.SyntheticEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - document.unstar(); - }, - [document] - ); - const collection = collections.get(document.collectionId); const can = usePolicy(document.id); const canViewHistory = can.read && !can.restore; @@ -205,19 +155,6 @@ function DocumentMenu({ ev.stopPropagation(); }, []); - const handleImportDocument = React.useCallback( - (ev: React.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: React.ChangeEvent) => { const files = getEventFiles(ev); @@ -280,7 +217,7 @@ function DocumentMenu({