This repository has been archived on 2022-08-14. You can view files and clone it, but cannot push or open issues or pull requests.
outline/app/menus/DocumentMenu.js

500 lines
14 KiB
JavaScript
Raw Normal View History

Slate editor (#38) * WIP: Slate editor * WIP * Focus at start / end working * ah ha * Super basic floating toolbar * Nested list editing * Pulling more logic into plugins * inline code markdown * Backspace at end of code block should remove mark * Ensure there is always an empty line at editor end * Add keyboard shortcuts for bold, italic, underline * Add strikethrough shortcode and toolbar * Toolbar to declarative Fixed paragraph styling Removed unused stuffs * Super basic link editing * Split Toolbar, now possible to edit and remove links * Add new link to selection from toolbar working * Ensure toolbar doesn't extend off screen * Fix minor js issues, disable formatting of document title * Boom, icons * Remove codemirror, fix MD parsing issues * CMD+S now saves inplace * Add --- shortcut for horizontal rule * Improved styling for link editor * Add header anchors in readOnly * More readable core text color * Restored image file uploading :tada: * Add support for inline md syntax, ** __ etc * Centered * Flooooow * Checklist support * Upgrade edit list plugin * Finally. Allow keydown within rich textarea * Update Markdown serializer * Cleanup, remove async editor loading * Editor > MarkdownEditor Fixed unsaved changes warning triggered when all changes are saved * MOAR typing * Combine edit and view * Fixed checkboxes still editable in readOnly * wip * Breadcrumb Restored scroll * Move document scene actions to menu * Added: Support for code blocks, syntax highlighting * Cleanup * > styled component * Prevent CMD+Enter from adding linebreak * Show image uploading in layout activity indicator * Upgrade editor deps * Improve link toolbar. Only one scenario where it's not working now
2017-05-18 02:36:31 +00:00
// @flow
import { observer } from "mobx-react";
import {
EditIcon,
PinIcon,
StarredIcon,
UnstarredIcon,
DuplicateIcon,
ArchiveIcon,
TrashIcon,
MoveIcon,
HistoryIcon,
UnpublishIcon,
ShapesIcon,
PrintIcon,
ImportIcon,
NewDocumentIcon,
DownloadIcon,
BuildingBlocksIcon,
RestoreIcon,
CrossIcon,
} from "outline-icons";
import * as React from "react";
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";
import styled from "styled-components";
import Document from "models/Document";
import DocumentDelete from "scenes/DocumentDelete";
import DocumentMove from "scenes/DocumentMove";
import DocumentPermanentDelete from "scenes/DocumentPermanentDelete";
import DocumentTemplatize from "scenes/DocumentTemplatize";
import CollectionIcon from "components/CollectionIcon";
import ContextMenu from "components/ContextMenu";
import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
import Template from "components/ContextMenu/Template";
import Flex from "components/Flex";
import Modal from "components/Modal";
import useCurrentTeam from "hooks/useCurrentTeam";
import useStores from "hooks/useStores";
import useToasts from "hooks/useToasts";
2021-03-21 06:20:49 +00:00
import getDataTransferFiles from "utils/getDataTransferFiles";
import {
documentHistoryUrl,
documentUrl,
editDocumentUrl,
2021-10-24 19:30:27 +00:00
newDocumentPath,
} from "utils/routeHelpers";
Slate editor (#38) * WIP: Slate editor * WIP * Focus at start / end working * ah ha * Super basic floating toolbar * Nested list editing * Pulling more logic into plugins * inline code markdown * Backspace at end of code block should remove mark * Ensure there is always an empty line at editor end * Add keyboard shortcuts for bold, italic, underline * Add strikethrough shortcode and toolbar * Toolbar to declarative Fixed paragraph styling Removed unused stuffs * Super basic link editing * Split Toolbar, now possible to edit and remove links * Add new link to selection from toolbar working * Ensure toolbar doesn't extend off screen * Fix minor js issues, disable formatting of document title * Boom, icons * Remove codemirror, fix MD parsing issues * CMD+S now saves inplace * Add --- shortcut for horizontal rule * Improved styling for link editor * Add header anchors in readOnly * More readable core text color * Restored image file uploading :tada: * Add support for inline md syntax, ** __ etc * Centered * Flooooow * Checklist support * Upgrade edit list plugin * Finally. Allow keydown within rich textarea * Update Markdown serializer * Cleanup, remove async editor loading * Editor > MarkdownEditor Fixed unsaved changes warning triggered when all changes are saved * MOAR typing * Combine edit and view * Fixed checkboxes still editable in readOnly * wip * Breadcrumb Restored scroll * Move document scene actions to menu * Added: Support for code blocks, syntax highlighting * Cleanup * > styled component * Prevent CMD+Enter from adding linebreak * Show image uploading in layout activity indicator * Upgrade editor deps * Improve link toolbar. Only one scenario where it's not working now
2017-05-18 02:36:31 +00:00
type Props = {|
2018-05-05 23:16:08 +00:00
document: Document,
className: string,
isRevision?: boolean,
2018-05-06 05:45:10 +00:00
showPrint?: boolean,
modal?: boolean,
showToggleEmbeds?: boolean,
2019-04-09 04:47:27 +00:00
showPin?: boolean,
label?: (any) => React.Node,
onOpen?: () => void,
onClose?: () => void,
|};
Slate editor (#38) * WIP: Slate editor * WIP * Focus at start / end working * ah ha * Super basic floating toolbar * Nested list editing * Pulling more logic into plugins * inline code markdown * Backspace at end of code block should remove mark * Ensure there is always an empty line at editor end * Add keyboard shortcuts for bold, italic, underline * Add strikethrough shortcode and toolbar * Toolbar to declarative Fixed paragraph styling Removed unused stuffs * Super basic link editing * Split Toolbar, now possible to edit and remove links * Add new link to selection from toolbar working * Ensure toolbar doesn't extend off screen * Fix minor js issues, disable formatting of document title * Boom, icons * Remove codemirror, fix MD parsing issues * CMD+S now saves inplace * Add --- shortcut for horizontal rule * Improved styling for link editor * Add header anchors in readOnly * More readable core text color * Restored image file uploading :tada: * Add support for inline md syntax, ** __ etc * Centered * Flooooow * Checklist support * Upgrade edit list plugin * Finally. Allow keydown within rich textarea * Update Markdown serializer * Cleanup, remove async editor loading * Editor > MarkdownEditor Fixed unsaved changes warning triggered when all changes are saved * MOAR typing * Combine edit and view * Fixed checkboxes still editable in readOnly * wip * Breadcrumb Restored scroll * Move document scene actions to menu * Added: Support for code blocks, syntax highlighting * Cleanup * > styled component * Prevent CMD+Enter from adding linebreak * Show image uploading in layout activity indicator * Upgrade editor deps * Improve link toolbar. Only one scenario where it's not working now
2017-05-18 02:36:31 +00:00
function DocumentMenu({
document,
isRevision,
className,
modal = true,
showToggleEmbeds,
showPrint,
showPin,
label,
onOpen,
onClose,
}: Props) {
const team = useCurrentTeam();
const { policies, collections, documents } = useStores();
const { showToast } = useToasts();
2021-03-25 02:23:16 +00:00
const menu = useMenuState({
modal,
unstable_preventOverflow: true,
unstable_fixed: true,
unstable_flip: true,
});
const history = useHistory();
const { t } = useTranslation();
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 [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
const handleOpen = React.useCallback(() => {
setRenderModals(true);
if (onOpen) {
onOpen();
}
}, [onOpen]);
const handleDuplicate = React.useCallback(
async (ev: SyntheticEvent<>) => {
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 (ev: SyntheticEvent<>) => {
await document.archive();
showToast(t("Document archived"), { type: "success" });
},
[showToast, t, document]
);
const handleRestore = React.useCallback(
async (ev: SyntheticEvent<>, options?: { collectionId: string }) => {
await document.restore(options);
showToast(t("Document restored"), { type: "success" });
},
[showToast, t, document]
);
const handleUnpublish = React.useCallback(
async (ev: SyntheticEvent<>) => {
await document.unpublish();
showToast(t("Document unpublished"), { type: "success" });
},
[showToast, t, document]
);
const handlePrint = React.useCallback(
(ev: SyntheticEvent<>) => {
menu.hide();
window.print();
},
[menu]
);
const handleStar = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
document.star();
},
[document]
);
2017-09-15 04:59:59 +00:00
const handleUnstar = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
document.unstar();
},
[document]
);
2017-09-15 04:59:59 +00:00
const collection = collections.get(document.collectionId);
const can = policies.abilities(document.id);
const canViewHistory = can.read && !can.restore;
const restoreItems = React.useMemo(
() => [
...collections.orderedData.reduce((filtered, collection) => {
const can = policies.abilities(collection.id);
if (can.update) {
filtered.push({
onClick: (ev) => handleRestore(ev, { collectionId: collection.id }),
title: (
<Flex align="center">
<CollectionIcon collection={collection} />
<CollectionName>{collection.name}</CollectionName>
</Flex>
),
});
}
return filtered;
}, []),
],
[collections.orderedData, handleRestore, policies]
);
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) {
showToast(err.message, {
2021-03-21 06:20:49 +00:00
type: "error",
});
throw err;
}
},
[history, showToast, collection, documents, document.id]
2021-03-21 06:20:49 +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>
{label ? (
<MenuButton {...menu}>{label}</MenuButton>
) : (
<OverflowMenuButton
className={className}
aria-label={t("Show menu")}
{...menu}
/>
)}
<ContextMenu
{...menu}
aria-label={t("Document options")}
onOpen={handleOpen}
onClose={onClose}
>
<Template
{...menu}
items={[
{
title: t("Restore"),
visible: (!!collection && can.restore) || can.unarchive,
onClick: handleRestore,
icon: <RestoreIcon />,
},
{
title: t("Restore"),
visible:
!collection && !!can.restore && restoreItems.length !== 0,
style: {
left: -170,
position: "relative",
top: -40,
},
icon: <RestoreIcon />,
hover: true,
items: [
{
type: "heading",
title: t("Choose a collection"),
},
...restoreItems,
],
},
{
title: t("Unpin"),
onClick: document.unpin,
visible: !!(showPin && document.pinned && can.unpin),
icon: <PinIcon />,
},
{
title: t("Pin to collection"),
onClick: document.pin,
visible: !!(showPin && !document.pinned && can.pin),
icon: <PinIcon />,
},
{
title: t("Unstar"),
onClick: handleUnstar,
visible: document.isStarred && !!can.unstar,
icon: <UnstarredIcon />,
},
{
title: t("Star"),
onClick: handleStar,
visible: !document.isStarred && !!can.star,
icon: <StarredIcon />,
},
{
type: "separator",
},
{
title: t("Edit"),
to: editDocumentUrl(document),
visible: !!can.update,
icon: <EditIcon />,
},
{
title: t("New nested document"),
2021-10-24 19:30:27 +00:00
to: newDocumentPath(document.collectionId, {
parentDocumentId: document.id,
}),
visible: !!can.createChildDocument,
icon: <NewDocumentIcon />,
},
2021-03-21 06:20:49 +00:00
{
title: t("Import document"),
visible: can.createChildDocument,
onClick: handleImportDocument,
icon: <ImportIcon />,
2021-03-21 06:20:49 +00:00
},
{
title: `${t("Create template")}`,
onClick: () => setShowTemplateModal(true),
visible: !!can.update && !document.isTemplate,
icon: <ShapesIcon />,
},
{
title: t("Duplicate"),
onClick: handleDuplicate,
visible: !!can.update,
icon: <DuplicateIcon />,
},
{
title: t("Unpublish"),
onClick: handleUnpublish,
visible: !!can.unpublish,
icon: <UnpublishIcon />,
},
{
title: t("Archive"),
onClick: handleArchive,
visible: !!can.archive,
icon: <ArchiveIcon />,
},
{
title: `${t("Delete")}`,
onClick: () => setShowDeleteModal(true),
visible: !!can.delete,
icon: <TrashIcon />,
},
{
title: `${t("Permanently delete")}`,
onClick: () => setShowPermanentDeleteModal(true),
visible: can.permanentDelete,
icon: <CrossIcon />,
},
{
title: `${t("Move")}`,
onClick: () => setShowMoveModal(true),
visible: !!can.move,
icon: <MoveIcon />,
},
{
title: t("Enable embeds"),
onClick: document.enableEmbeds,
visible:
!!showToggleEmbeds &&
document.embedsDisabled &&
!team.collaborativeEditing,
icon: <BuildingBlocksIcon />,
},
{
title: t("Disable embeds"),
onClick: document.disableEmbeds,
visible:
!!showToggleEmbeds &&
!document.embedsDisabled &&
!team.collaborativeEditing,
icon: <BuildingBlocksIcon />,
},
{
type: "separator",
},
{
title: t("History"),
to: isRevision
? documentUrl(document)
: documentHistoryUrl(document),
visible: canViewHistory,
icon: <HistoryIcon />,
},
{
title: t("Download"),
onClick: document.download,
visible: !!can.download,
icon: <DownloadIcon />,
},
{
title: t("Print"),
onClick: handlePrint,
visible: !!showPrint,
icon: <PrintIcon />,
},
]}
/>
</ContextMenu>
{renderModals && (
<>
{can.move && (
<Modal
title={t("Move {{ documentName }}", {
documentName: document.noun,
})}
onRequestClose={() => setShowMoveModal(false)}
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>
)}
</>
)}
</>
);
Slate editor (#38) * WIP: Slate editor * WIP * Focus at start / end working * ah ha * Super basic floating toolbar * Nested list editing * Pulling more logic into plugins * inline code markdown * Backspace at end of code block should remove mark * Ensure there is always an empty line at editor end * Add keyboard shortcuts for bold, italic, underline * Add strikethrough shortcode and toolbar * Toolbar to declarative Fixed paragraph styling Removed unused stuffs * Super basic link editing * Split Toolbar, now possible to edit and remove links * Add new link to selection from toolbar working * Ensure toolbar doesn't extend off screen * Fix minor js issues, disable formatting of document title * Boom, icons * Remove codemirror, fix MD parsing issues * CMD+S now saves inplace * Add --- shortcut for horizontal rule * Improved styling for link editor * Add header anchors in readOnly * More readable core text color * Restored image file uploading :tada: * Add support for inline md syntax, ** __ etc * Centered * Flooooow * Checklist support * Upgrade edit list plugin * Finally. Allow keydown within rich textarea * Update Markdown serializer * Cleanup, remove async editor loading * Editor > MarkdownEditor Fixed unsaved changes warning triggered when all changes are saved * MOAR typing * Combine edit and view * Fixed checkboxes still editable in readOnly * wip * Breadcrumb Restored scroll * Move document scene actions to menu * Added: Support for code blocks, syntax highlighting * Cleanup * > styled component * Prevent CMD+Enter from adding linebreak * Show image uploading in layout activity indicator * Upgrade editor deps * Improve link toolbar. Only one scenario where it's not working now
2017-05-18 02:36:31 +00:00
}
const CollectionName = styled.div`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
export default observer(DocumentMenu);