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({
,
- },
- {
- type: "button",
- title: t("Star"),
- onClick: handleStar,
- visible: !document.isStarred && !!can.star,
- icon: ,
- },
- // Pin document
+ actionToMenuItem(unstarDocument, context),
+ actionToMenuItem(starDocument, context),
actionToMenuItem(pinDocument, context),
{
type: "separator",
@@ -348,22 +272,9 @@ function DocumentMenu({
visible: !!can.createChildDocument,
icon: ,
},
- {
- type: "button",
- title: t("Import document"),
- visible: can.createChildDocument,
- onClick: handleImportDocument,
- icon: ,
- },
- // Templatize document
+ actionToMenuItem(importDocument, context),
actionToMenuItem(createTemplate, context),
- {
- type: "button",
- title: t("Duplicate"),
- onClick: handleDuplicate,
- visible: !!can.update,
- icon: ,
- },
+ actionToMenuItem(duplicateDocument, context),
{
type: "button",
title: t("Unpublish"),
@@ -371,36 +282,10 @@ function DocumentMenu({
visible: !!can.unpublish,
icon: ,
},
- {
- type: "button",
- title: t("Archive"),
- onClick: handleArchive,
- visible: !!can.archive,
- icon: ,
- },
- {
- type: "button",
- title: `${t("Move")}…`,
- onClick: () => setShowMoveModal(true),
- visible: !!can.move,
- icon: ,
- },
- {
- type: "button",
- title: `${t("Delete")}…`,
- dangerous: true,
- onClick: () => setShowDeleteModal(true),
- visible: !!can.delete,
- icon: ,
- },
- {
- type: "button",
- title: `${t("Permanently delete")}…`,
- dangerous: true,
- onClick: () => setShowPermanentDeleteModal(true),
- visible: can.permanentDelete,
- icon: ,
- },
+ actionToMenuItem(archiveDocument, context),
+ actionToMenuItem(moveDocument, context),
+ actionToMenuItem(deleteDocument, context),
+ actionToMenuItem(permanentlyDeleteDocument, context),
{
type: "separator",
},
@@ -413,13 +298,7 @@ function DocumentMenu({
visible: canViewHistory,
icon: ,
},
- {
- type: "button",
- title: t("Download"),
- onClick: document.download,
- visible: !!can.download,
- icon: ,
- },
+ actionToMenuItem(downloadDocument, context),
{
type: "button",
title: t("Print"),
@@ -464,54 +343,6 @@ function DocumentMenu({
>
)}
- {renderModals && (
- <>
- {can.move && (
- setShowMoveModal(false)}
- isOpen={showMoveModal}
- >
- setShowMoveModal(false)}
- />
-
- )}
- {can.delete && (
- setShowDeleteModal(false)}
- isOpen={showDeleteModal}
- isCentered
- >
- setShowDeleteModal(false)}
- />
-
- )}
- {can.permanentDelete && (
- setShowPermanentDeleteModal(false)}
- isOpen={showPermanentDeleteModal}
- isCentered
- >
- setShowPermanentDeleteModal(false)}
- />
-
- )}
- >
- )}
>
);
}
diff --git a/app/types.ts b/app/types.ts
index bfd3d1e8e..5364527ed 100644
--- a/app/types.ts
+++ b/app/types.ts
@@ -88,6 +88,7 @@ export type Action = {
section: ((context: ActionContext) => string) | string;
shortcut?: string[];
keywords?: string;
+ dangerous?: boolean;
iconInContextMenu?: boolean;
icon?: React.ReactElement | React.FC;
placeholder?: ((context: ActionContext) => string) | string;
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index c532d18cd..1cc80c1f8 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -26,10 +26,17 @@
"Templatize": "Templatize",
"Create template": "Create template",
"Search documents for \"{{searchQuery}}\"": "Search documents for \"{{searchQuery}}\"",
+ "Move": "Move",
+ "Move {{ documentName }}": "Move {{ documentName }}",
+ "Archive": "Archive",
+ "Document archived": "Document archived",
+ "Delete": "Delete",
+ "Delete {{ documentName }}": "Delete {{ documentName }}",
+ "Permanently delete": "Permanently delete",
+ "Permanently delete {{ documentName }}": "Permanently delete {{ documentName }}",
"Home": "Home",
"Drafts": "Drafts",
"Templates": "Templates",
- "Archive": "Archive",
"Trash": "Trash",
"Settings": "Settings",
"Profile": "Profile",
@@ -156,7 +163,6 @@
"Results": "Results",
"No results for {{query}}": "No results for {{query}}",
"Logo": "Logo",
- "Document archived": "Document archived",
"Move document": "Move document",
"You can't reorder documents in an alphabetically sorted collection": "You can't reorder documents in an alphabetically sorted collection",
"Collections": "Collections",
@@ -168,7 +174,6 @@
"Starred": "Starred",
"Show more": "Show more",
"Toggle sidebar": "Toggle sidebar",
- "Delete {{ documentName }}": "Delete {{ documentName }}",
"Up to date": "Up to date",
"{{ releasesBehind }} versions behind": "{{ releasesBehind }} version behind",
"{{ releasesBehind }} versions behind_plural": "{{ releasesBehind }} versions behind",
@@ -272,19 +277,14 @@
"Manual sort": "Manual sort",
"Edit": "Edit",
"Permissions": "Permissions",
- "Delete": "Delete",
"Document restored": "Document restored",
"Document unpublished": "Document unpublished",
"Document options": "Document options",
"Restore": "Restore",
"Choose a collection": "Choose a collection",
"Unpublish": "Unpublish",
- "Move": "Move",
- "Permanently delete": "Permanently delete",
"Enable embeds": "Enable embeds",
"Full width": "Full width",
- "Move {{ documentName }}": "Move {{ documentName }}",
- "Permanently delete {{ documentName }}": "Permanently delete {{ documentName }}",
"Export options": "Export options",
"Edit group": "Edit group",
"Delete group": "Delete group",