diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js
index 74e564d59..6b67473db 100644
--- a/app/components/Sidebar/components/CollectionLink.js
+++ b/app/components/Sidebar/components/CollectionLink.js
@@ -3,11 +3,14 @@ import fractionalIndex from "fractional-index";
import { observer } from "mobx-react";
import * as React from "react";
import { useDrop, useDrag } from "react-dnd";
+import { useTranslation } from "react-i18next";
import { useLocation } from "react-router-dom";
import styled from "styled-components";
import Collection from "models/Collection";
import Document from "models/Document";
+import DocumentReparent from "scenes/DocumentReparent";
import CollectionIcon from "components/CollectionIcon";
+import Modal from "components/Modal";
import DocumentLink from "./DocumentLink";
import DropCursor from "./DropCursor";
import DropToImport from "./DropToImport";
@@ -37,8 +40,15 @@ function CollectionLink({
isDraggingAnyCollection,
onChangeDragging,
}: Props) {
+ const { t } = useTranslation();
const { search } = useLocation();
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
+ const [
+ permissionOpen,
+ handlePermissionOpen,
+ handlePermissionClose,
+ ] = useBoolean();
+ const itemRef = React.useRef();
const handleTitleChange = React.useCallback(
async (name: string) => {
@@ -74,9 +84,22 @@ function CollectionLink({
const [{ isOver, canDrop }, drop] = useDrop({
accept: "document",
drop: (item, monitor) => {
+ const { id, collectionId } = item;
if (monitor.didDrop()) return;
if (!collection) return;
- documents.move(item.id, collection.id);
+ if (collection.id === collectionId) return;
+ const prevCollection = collections.get(collectionId);
+
+ if (
+ prevCollection &&
+ prevCollection.permission === null &&
+ prevCollection.permission !== collection.permission
+ ) {
+ itemRef.current = item;
+ handlePermissionOpen();
+ } else {
+ documents.move(id, collection.id);
+ }
},
canDrop: (item, monitor) => {
return policies.abilities(collection.id).update;
@@ -210,6 +233,18 @@ function CollectionLink({
index={index}
/>
))}
+
+
+
>
);
}
diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js
index 58dcebdb1..b4c5b4643 100644
--- a/app/components/Sidebar/components/DocumentLink.js
+++ b/app/components/Sidebar/components/DocumentLink.js
@@ -128,7 +128,12 @@ function DocumentLink(
// Draggable
const [{ isDragging }, drag] = useDrag({
type: "document",
- item: () => ({ ...node, depth, active: isActiveDocument }),
+ item: () => ({
+ ...node,
+ depth,
+ active: isActiveDocument,
+ collectionId: collection?.id || "",
+ }),
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
diff --git a/app/scenes/DocumentReparent.js b/app/scenes/DocumentReparent.js
new file mode 100644
index 000000000..56adbcb29
--- /dev/null
+++ b/app/scenes/DocumentReparent.js
@@ -0,0 +1,98 @@
+// @flow
+import { observer } from "mobx-react";
+import * as React from "react";
+import { useState } from "react";
+import { Trans, useTranslation } from "react-i18next";
+import Collection from "models/Collection";
+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 { type NavigationNode } from "types";
+
+type Props = {|
+ document: Document,
+ item: {|
+ active: ?boolean,
+ children: Array,
+ collectionId: string,
+ depth: number,
+ id: string,
+ title: string,
+ url: string,
+ |},
+ collection: Collection,
+ onCancel: () => void,
+ onSubmit: () => void,
+|};
+
+function DocumentReparent({
+ document,
+ collection,
+ item,
+ onSubmit,
+ onCancel,
+}: Props) {
+ const [isSaving, setIsSaving] = useState();
+ const { showToast } = useToasts();
+ const { documents, collections } = useStores();
+ const { t } = useTranslation();
+ const prevCollection = collections.get(item.collectionId);
+
+ const accessMapping = {
+ read_write: t("view and edit access"),
+ read: t("view only access"),
+ null: t("no access"),
+ };
+
+ const handleSubmit = React.useCallback(
+ async (ev: SyntheticEvent<>) => {
+ ev.preventDefault();
+ setIsSaving(true);
+
+ try {
+ await documents.move(item.id, collection.id);
+ showToast(t("Document moved"), {
+ type: "info",
+ });
+ onSubmit();
+ } catch (err) {
+ showToast(err.message, { type: "error" });
+ } finally {
+ setIsSaving(false);
+ }
+ },
+ [documents, item.id, collection.id, showToast, t, onSubmit]
+ );
+
+ return (
+
+
+
+ );
+}
+
+export default observer(DocumentReparent);
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index e4d66d369..a80e689f8 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -142,6 +142,7 @@
"Keyboard shortcuts": "Keyboard shortcuts",
"Back": "Back",
"Document archived": "Document archived",
+ "Move document": "Move document",
"Collections could not be loaded, please reload the app": "Collections could not be loaded, please reload the app",
"New collection": "New collection",
"Collections": "Collections",
@@ -378,6 +379,12 @@
"Couldn’t create the document, try again?": "Couldn’t create the document, try again?",
"Document permanently deleted": "Document permanently deleted",
"Are you sure you want to permanently delete the {{ documentTitle }} document? This action is immediate and cannot be undone.": "Are you sure you want to permanently delete the {{ documentTitle }} document? This action is immediate and cannot be undone.",
+ "view and edit access": "view and edit access",
+ "view only access": "view only access",
+ "no access": "no access",
+ "Heads up – moving the document {{ title }} to the {{ newCollectionName }} collection will grant all team members {{ newPermission }}, they currently have {{ prevPermission }}.": "Heads up – moving the document {{ title }} to the {{ newCollectionName }} collection will grant all team members {{ newPermission }}, they currently have {{ prevPermission }}.",
+ "Moving": "Moving",
+ "Cancel": "Cancel",
"Template created, go ahead and customize it": "Template created, go ahead and customize it",
"Creating a template from {{titleWithDefault}} is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.": "Creating a template from {{titleWithDefault}} is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.",
"Search documents": "Search documents",