feat: Add print, duplicate, template to command bar

This commit is contained in:
Tom Moor
2021-10-25 20:41:28 -07:00
parent 4266a95569
commit 043a7b41b5
5 changed files with 166 additions and 11 deletions

View File

@ -1,11 +1,18 @@
// @flow
import invariant from "invariant";
import {
DownloadIcon,
DuplicateIcon,
StarredIcon,
PrintIcon,
UnstarredIcon,
DocumentIcon,
NewDocumentIcon,
ShapesIcon,
ImportIcon,
} from "outline-icons";
import * as React from "react";
import DocumentTemplatize from "scenes/DocumentTemplatize";
import { createAction } from "actions";
import { DocumentSection } from "actions/sections";
import getDataTransferFiles from "utils/getDataTransferFiles";
@ -50,15 +57,113 @@ export const createDocument = createAction({
activeCollectionId && history.push(newDocumentPath(activeCollectionId)),
});
export const starDocument = createAction({
name: ({ t }) => t("Star"),
section: DocumentSection,
icon: <StarredIcon />,
keywords: "favorite bookmark",
visible: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
return (
!document?.isStarred && stores.policies.abilities(activeDocumentId).star
);
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
document?.star();
},
});
export const unstarDocument = createAction({
name: ({ t }) => t("Unstar"),
section: DocumentSection,
icon: <UnstarredIcon />,
keywords: "unfavorite unbookmark",
visible: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
return (
!!document?.isStarred &&
stores.policies.abilities(activeDocumentId).unstar
);
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
document?.unstar();
},
});
export const downloadDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Download") : t("Download document"),
section: DocumentSection,
icon: <DownloadIcon />,
keywords: "export",
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).download,
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
document?.download();
},
});
export const duplicateDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Duplicate") : t("Duplicate document"),
section: DocumentSection,
icon: <DuplicateIcon />,
keywords: "copy",
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).update,
perform: async ({ activeDocumentId, t, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
invariant(document, "Document must exist");
const duped = await document.duplicate();
// when duplicating, go straight to the duplicated document content
history.push(duped.url);
stores.toasts.showToast(t("Document duplicated"), { type: "success" });
},
});
export const printDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Print") : t("Print document"),
section: DocumentSection,
icon: <PrintIcon />,
visible: ({ activeDocumentId }) => !!activeDocumentId,
perform: async () => {
window.print();
},
});
export const importDocument = createAction({
name: ({ t }) => t("Import document"),
name: ({ t, activeDocumentId }) => t("Import document"),
section: DocumentSection,
icon: <ImportIcon />,
keywords: "upload",
visible: ({ activeCollectionId, stores }) =>
!!activeCollectionId &&
stores.policies.abilities(activeCollectionId).update,
perform: ({ activeCollectionId, stores }) => {
visible: ({ activeCollectionId, activeDocumentId, stores }) => {
if (activeDocumentId) {
return !!stores.policies.abilities(activeDocumentId).createChildDocument;
}
if (activeCollectionId) {
return !!stores.policies.abilities(activeCollectionId).update;
}
return false;
},
perform: ({ activeCollectionId, activeDocumentId, stores }) => {
const { documents, toasts } = stores;
const input = document.createElement("input");
@ -71,7 +176,7 @@ export const importDocument = createAction({
const file = files[0];
const document = await documents.import(
file,
null,
activeDocumentId,
activeCollectionId,
{
publish: true,
@ -90,8 +195,47 @@ export const importDocument = createAction({
},
});
export const createTemplate = createAction({
name: ({ t }) => t("Templatize"),
section: DocumentSection,
icon: <ShapesIcon />,
keywords: "new create template",
visible: ({ activeCollectionId, activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
invariant(document, "Document must exist");
return (
!!activeCollectionId &&
stores.policies.abilities(activeCollectionId).update &&
!document.isTemplate
);
},
perform: ({ activeDocumentId, stores, t, event }) => {
event?.preventDefault();
event?.stopPropagation();
stores.dialogs.openModal({
title: t("Create template"),
content: (
<DocumentTemplatize
documentId={activeDocumentId}
onSubmit={stores.dialogs.closeAllModals}
/>
),
});
},
});
export const rootDocumentActions = [
openDocument,
createDocument,
createTemplate,
importDocument,
downloadDocument,
starDocument,
unstarDocument,
duplicateDocument,
printDocument,
];

View File

@ -88,6 +88,10 @@ const Animator = styled(KBarAnimator)`
${breakpoint("desktopLarge")`
max-width: 740px;
`};
@media print {
display: none;
}
`;
export default observer(CommandBar);

View File

@ -26,5 +26,8 @@ export default function useCommandBarActions(actions: Action[]) {
actions.map((action) => actionToKBar(action, context))
);
useRegisterActions(registerable, [registerable.length, location.pathname]);
useRegisterActions(registerable, [
...registerable.map((r) => r.id),
location.pathname,
]);
}

View File

@ -479,7 +479,7 @@ function DocumentMenu({
isOpen={showTemplateModal}
>
<DocumentTemplatize
document={document}
documentId={document.id}
onSubmit={() => setShowTemplateModal(false)}
/>
</Modal>

View File

@ -1,26 +1,30 @@
// @flow
import invariant from "invariant";
import { observer } from "mobx-react";
import * as React from "react";
import { useState } from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import Document from "models/Document";
import Button from "components/Button";
import Flex from "components/Flex";
import HelpText from "components/HelpText";
import useStores from "hooks/useStores";
import useToasts from "hooks/useToasts";
import { documentUrl } from "utils/routeHelpers";
type Props = {
document: Document,
documentId: string,
onSubmit: () => void,
};
function DocumentTemplatize({ document, onSubmit }: Props) {
function DocumentTemplatize({ documentId, onSubmit }: Props) {
const [isSaving, setIsSaving] = useState();
const history = useHistory();
const { showToast } = useToasts();
const { t } = useTranslation();
const { documents } = useStores();
const document = documents.get(documentId);
invariant(document, "Document must exist");
const handleSubmit = React.useCallback(
async (ev: SyntheticEvent<>) => {