Compare commits

...

11 Commits

Author SHA1 Message Date
Saumya Pandey c584096d59 Merge branch 'main' of https://github.com/outline/outline into feat/undo-document-move 2022-04-30 13:05:09 +05:30
Saumya Pandey 5a3d55bc58 fix: delete moveWithUndo 2022-03-05 23:10:08 +05:30
Tom Moor eaff7d933e Merge branch 'main' into feat/undo-document-move 2022-03-04 18:29:50 -08:00
Saumya Pandey 21b378b80d style action in toast 2022-02-12 10:59:50 +05:30
Saumya Pandey c143036374 remove async 2022-02-12 10:21:23 +05:30
Saumya Pandey a773516e01 lighten up DragObject 2022-02-12 10:18:51 +05:30
Saumya Pandey c7045b0c00 create moveWithUndo inside document model 2022-02-12 10:02:24 +05:30
Saumya Pandey 53d0cdd151 fix: remove undo state from server 2022-02-10 01:24:56 +05:30
Saumya Pandey a22e50cd3d fix: move to client side 2022-02-10 00:42:27 +05:30
Saumya Pandey 00f65ce29d fix: undo handling for all the documents.move op 2022-02-07 23:33:03 +05:30
Saumya Pandey da8936e9d8 fix: return undo state in response 2022-02-06 14:30:16 +05:30
7 changed files with 85 additions and 40 deletions
@@ -16,6 +16,7 @@ import useActionContext from "~/hooks/useActionContext";
import useBoolean from "~/hooks/useBoolean";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import CollectionMenu from "~/menus/CollectionMenu";
import { NavigationNode } from "~/types";
import DropToImport from "./DropToImport";
@@ -48,6 +49,7 @@ const CollectionLink: React.FC<Props> = ({
const { t } = useTranslation();
const history = useHistory();
const inStarredSection = useStarredContext();
const { showToast } = useToasts();
const handleTitleChange = React.useCallback(
async (name: string) => {
@@ -62,16 +64,17 @@ const CollectionLink: React.FC<Props> = ({
// Drop to re-parent document
const [{ isOver, canDrop }, drop] = useDrop({
accept: "document",
drop: (item: DragObject, monitor) => {
drop: async (item: DragObject, monitor) => {
const { id, collectionId } = item;
const document = documents.get(id);
if (monitor.didDrop()) {
return;
}
if (!collection) {
if (!collection || !document) {
return;
}
const document = documents.get(id);
if (collection.id === collectionId && !document?.parentDocumentId) {
return;
}
@@ -97,7 +100,21 @@ const CollectionLink: React.FC<Props> = ({
),
});
} else {
documents.move(id, collection.id);
const undo = document.metaData;
await document.move(collection.id);
showToast(t("Document moved"), {
type: "info",
action: {
text: "undo",
onClick: async () => {
await document.move(
undo.collectionId,
undo.parentDocumentId,
undo.index
);
},
},
});
}
},
canDrop: () => canUpdate,
@@ -178,10 +178,13 @@ function InnerDocumentLink(
if (monitor.didDrop()) {
return;
}
if (!collection) {
return;
}
documents.move(item.id, collection.id, node.id);
const document = documents.get(item.id);
document?.moveWithUndo(collection.id, node.id);
},
canDrop: (_item, monitor) =>
!isDraft &&
@@ -244,12 +247,14 @@ function InnerDocumentLink(
return;
}
if (expanded) {
documents.move(item.id, collection.id, node.id, 0);
return;
}
documents.move(item.id, collection.id, parentId, index + 1);
const parentDocumentId = expanded ? node.id : parentId;
const droppedDocumentIndex = expanded ? 0 : index + 1;
const document = documents.get(item.id);
document?.moveWithUndo(
collection.id,
parentDocumentId,
droppedDocumentIndex
);
},
collect: (monitor) => ({
isOverReorder: !!monitor.isOver(),
+5 -5
View File
@@ -1,5 +1,5 @@
import { CheckboxIcon, InfoIcon, WarningIcon } from "outline-icons";
import { darken } from "polished";
import { darken, lighten } from "polished";
import * as React from "react";
import styled, { css } from "styled-components";
import { fadeAndScaleIn, pulse } from "~/styles/animations";
@@ -69,17 +69,17 @@ function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
const Action = styled.span`
display: inline-block;
padding: 10px 12px;
padding: 6px 12px;
margin-left: 8px;
height: 100%;
text-transform: uppercase;
font-size: 12px;
color: ${(props) => props.theme.toastText};
background: ${(props) => darken(0.05, props.theme.toastBackground)};
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-radius: 5px;
&:hover {
background: ${(props) => darken(0.1, props.theme.toastBackground)};
background: ${(props) => lighten(0.1, props.theme.toastBackground)};
}
`;
+23
View File
@@ -136,6 +136,29 @@ export default class Collection extends ParanoidModel {
return result;
}
documentIndexInCollection(documentId: string) {
let index: number | undefined;
const findIndex = (nodes: NavigationNode[]) => {
if (index) {
return;
}
nodes.forEach((node, i) => {
if (node.id === documentId) {
index = i;
return;
}
});
nodes.forEach((node) => {
findIndex(node.children);
});
};
findIndex(this.documents);
return index;
}
pathToDocument(documentId: string) {
let path: NavigationNode[] | undefined;
const document = this.store.rootStore.documents.get(documentId);
+18 -2
View File
@@ -379,10 +379,26 @@ export default class Document extends ParanoidModel {
}
};
move = (collectionId: string, parentDocumentId?: string | undefined) => {
return this.store.move(this.id, collectionId, parentDocumentId);
move = (
collectionId: string,
parentDocumentId?: string | null,
index?: number | null
) => {
return this.store.move(this.id, collectionId, parentDocumentId, index);
};
@computed
get metaData() {
const collection = this.store.rootStore.collections.get(this.collectionId);
const undo = {
id: this.id,
collectionId: this.collectionId,
parentDocumentId: this.parentDocumentId,
index: collection?.documentIndexInCollection?.(this.id),
};
return undo;
}
duplicate = () => {
return this.store.duplicate(this);
};
+5 -21
View File
@@ -5,27 +5,13 @@ import { Trans, useTranslation } from "react-i18next";
import Collection from "~/models/Collection";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import { DragObject } from "~/components/Sidebar/components/SidebarLink";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { NavigationNode } from "~/types";
type Props = {
item:
| {
active: boolean | null | undefined;
children: Array<NavigationNode>;
collectionId: string;
depth: number;
id: string;
title: string;
url: string;
}
| {
id: string;
collectionId: string;
title: string;
};
item: DragObject;
collection: Collection;
onCancel: () => void;
onSubmit: () => void;
@@ -49,10 +35,8 @@ function DocumentReparent({ collection, item, onSubmit, onCancel }: Props) {
setIsSaving(true);
try {
await documents.move(item.id, collection.id);
showToast(t("Document moved"), {
type: "info",
});
const document = documents.get(item.id);
document?.moveWithUndo(collection.id);
onSubmit();
} catch (err) {
showToast(err.message, {
@@ -62,7 +46,7 @@ function DocumentReparent({ collection, item, onSubmit, onCancel }: Props) {
setIsSaving(false);
}
},
[documents, item.id, collection.id, showToast, t, onSubmit]
[documents, collection.id, showToast, item, onSubmit]
);
return (
+1 -1
View File
@@ -150,6 +150,7 @@
"Logo": "Logo",
"Document archived": "Document archived",
"Move document": "Move document",
"Document moved": "Document moved",
"You can't reorder documents in an alphabetically sorted collection": "You can't reorder documents in an alphabetically sorted collection",
"Collections": "Collections",
"Untitled": "Untitled",
@@ -423,7 +424,6 @@
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.",
"If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.",
"Archiving": "Archiving",
"Document moved": "Document moved",
"Current location": "Current location",
"Choose a new location": "Choose a new location",
"Search collections & documents": "Search collections & documents",