feat: Moving documents via drag and drop in sidebar (#1717)
* wip: added some basic drag and drop UI for combining items * refactor: pathToDocument to accept only id * fix: Multiple drop backends error fix: Incorrect styling dragging over active collection fix: Stay in disabled state until save is complete * Improving display while moving doc * fix: update by user should be changed when moving a doc * add move guard to drag Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
parent
3469b82beb
commit
051ecab0fc
|
@ -99,7 +99,7 @@ const Breadcrumb = ({ document, onlyText }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = collection.pathToDocument
|
const path = collection.pathToDocument
|
||||||
? collection.pathToDocument(document).slice(0, -1)
|
? collection.pathToDocument(document.id).slice(0, -1)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (onlyText === true) {
|
if (onlyText === true) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { observable } from "mobx";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { useDrop } from "react-dnd";
|
||||||
import UiStore from "stores/UiStore";
|
import UiStore from "stores/UiStore";
|
||||||
import Collection from "models/Collection";
|
import Collection from "models/Collection";
|
||||||
import Document from "models/Document";
|
import Document from "models/Document";
|
||||||
|
@ -10,6 +10,7 @@ import DropToImport from "components/DropToImport";
|
||||||
import DocumentLink from "./DocumentLink";
|
import DocumentLink from "./DocumentLink";
|
||||||
import EditableTitle from "./EditableTitle";
|
import EditableTitle from "./EditableTitle";
|
||||||
import SidebarLink from "./SidebarLink";
|
import SidebarLink from "./SidebarLink";
|
||||||
|
import useStores from "hooks/useStores";
|
||||||
import CollectionMenu from "menus/CollectionMenu";
|
import CollectionMenu from "menus/CollectionMenu";
|
||||||
|
|
||||||
type Props = {|
|
type Props = {|
|
||||||
|
@ -20,27 +21,44 @@ type Props = {|
|
||||||
prefetchDocument: (id: string) => Promise<void>,
|
prefetchDocument: (id: string) => Promise<void>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
@observer
|
function CollectionLink({
|
||||||
class CollectionLink extends React.Component<Props> {
|
|
||||||
@observable menuOpen = false;
|
|
||||||
|
|
||||||
handleTitleChange = async (name: string) => {
|
|
||||||
await this.props.collection.save({ name });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
collection,
|
collection,
|
||||||
activeDocument,
|
activeDocument,
|
||||||
prefetchDocument,
|
prefetchDocument,
|
||||||
canUpdate,
|
canUpdate,
|
||||||
ui,
|
ui,
|
||||||
} = this.props;
|
}: Props) {
|
||||||
|
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const handleTitleChange = React.useCallback(
|
||||||
|
async (name: string) => {
|
||||||
|
await collection.save({ name });
|
||||||
|
},
|
||||||
|
[collection]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { documents, policies } = useStores();
|
||||||
const expanded = collection.id === ui.activeCollectionId;
|
const expanded = collection.id === ui.activeCollectionId;
|
||||||
|
|
||||||
|
// Droppable
|
||||||
|
const [{ isOver, canDrop }, drop] = useDrop({
|
||||||
|
accept: "document",
|
||||||
|
drop: (item, monitor) => {
|
||||||
|
if (!collection) return;
|
||||||
|
documents.move(item.id, collection.id);
|
||||||
|
},
|
||||||
|
canDrop: (item, monitor) => {
|
||||||
|
return policies.abilities(collection.id).update;
|
||||||
|
},
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isOver: !!monitor.isOver(),
|
||||||
|
canDrop: monitor.canDrop(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div ref={drop}>
|
||||||
<DropToImport key={collection.id} collectionId={collection.id}>
|
<DropToImport key={collection.id} collectionId={collection.id}>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
key={collection.id}
|
key={collection.id}
|
||||||
|
@ -50,11 +68,12 @@ class CollectionLink extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
iconColor={collection.color}
|
iconColor={collection.color}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
menuOpen={this.menuOpen}
|
menuOpen={menuOpen}
|
||||||
|
isActiveDrop={isOver && canDrop}
|
||||||
label={
|
label={
|
||||||
<EditableTitle
|
<EditableTitle
|
||||||
title={collection.name}
|
title={collection.name}
|
||||||
onSubmit={this.handleTitleChange}
|
onSubmit={handleTitleChange}
|
||||||
canUpdate={canUpdate}
|
canUpdate={canUpdate}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -63,12 +82,13 @@ class CollectionLink extends React.Component<Props> {
|
||||||
<CollectionMenu
|
<CollectionMenu
|
||||||
position="right"
|
position="right"
|
||||||
collection={collection}
|
collection={collection}
|
||||||
onOpen={() => (this.menuOpen = true)}
|
onOpen={() => setMenuOpen(true)}
|
||||||
onClose={() => (this.menuOpen = false)}
|
onClose={() => setMenuOpen(false)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
></SidebarLink>
|
></SidebarLink>
|
||||||
</DropToImport>
|
</DropToImport>
|
||||||
|
</div>
|
||||||
|
|
||||||
{expanded &&
|
{expanded &&
|
||||||
collection.documents.map((node) => (
|
collection.documents.map((node) => (
|
||||||
|
@ -85,6 +105,5 @@ class CollectionLink extends React.Component<Props> {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default CollectionLink;
|
export default observer(CollectionLink);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { CollapsedIcon } from "outline-icons";
|
import { CollapsedIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { useDrag, useDrop } from "react-dnd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Collection from "models/Collection";
|
import Collection from "models/Collection";
|
||||||
|
@ -33,7 +34,7 @@ function DocumentLink({
|
||||||
depth,
|
depth,
|
||||||
canUpdate,
|
canUpdate,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { documents } = useStores();
|
const { documents, policies } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isActiveDocument = activeDocument && activeDocument.id === node.id;
|
const isActiveDocument = activeDocument && activeDocument.id === node.id;
|
||||||
|
@ -48,13 +49,19 @@ function DocumentLink({
|
||||||
}
|
}
|
||||||
}, [fetchChildDocuments, node, hasChildDocuments, isActiveDocument]);
|
}, [fetchChildDocuments, node, hasChildDocuments, isActiveDocument]);
|
||||||
|
|
||||||
|
const pathToNode = React.useMemo(
|
||||||
|
() =>
|
||||||
|
collection && collection.pathToDocument(node.id).map((entry) => entry.id),
|
||||||
|
[collection, node]
|
||||||
|
);
|
||||||
|
|
||||||
const showChildren = React.useMemo(() => {
|
const showChildren = React.useMemo(() => {
|
||||||
return !!(
|
return !!(
|
||||||
hasChildDocuments &&
|
hasChildDocuments &&
|
||||||
activeDocument &&
|
activeDocument &&
|
||||||
collection &&
|
collection &&
|
||||||
(collection
|
(collection
|
||||||
.pathToDocument(activeDocument)
|
.pathToDocument(activeDocument.id)
|
||||||
.map((entry) => entry.id)
|
.map((entry) => entry.id)
|
||||||
.includes(node.id) ||
|
.includes(node.id) ||
|
||||||
isActiveDocument)
|
isActiveDocument)
|
||||||
|
@ -100,9 +107,43 @@ function DocumentLink({
|
||||||
);
|
);
|
||||||
|
|
||||||
const [menuOpen, setMenuOpen] = React.useState(false);
|
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||||
|
const isMoving = documents.movingDocumentId === node.id;
|
||||||
|
|
||||||
|
// Draggable
|
||||||
|
const [{ isDragging }, drag] = useDrag({
|
||||||
|
item: { type: "document", ...node, depth, active: isActiveDocument },
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isDragging: !!monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
canDrag: (monitor) => {
|
||||||
|
return policies.abilities(node.id).move;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Droppable
|
||||||
|
const [{ isOver, canDrop }, drop] = useDrop({
|
||||||
|
accept: "document",
|
||||||
|
drop: async (item, monitor) => {
|
||||||
|
if (!collection) return;
|
||||||
|
documents.move(item.id, collection.id, node.id);
|
||||||
|
},
|
||||||
|
canDrop: (item, monitor) =>
|
||||||
|
pathToNode && !pathToNode.includes(monitor.getItem().id),
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isOver: !!monitor.isOver(),
|
||||||
|
canDrop: monitor.canDrop(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={node.id}>
|
<>
|
||||||
|
<Draggable
|
||||||
|
key={node.id}
|
||||||
|
ref={drag}
|
||||||
|
$isDragging={isDragging}
|
||||||
|
$isMoving={isMoving}
|
||||||
|
>
|
||||||
|
<div ref={drop}>
|
||||||
<DropToImport documentId={node.id} activeClassName="activeDropZone">
|
<DropToImport documentId={node.id} activeClassName="activeDropZone">
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
innerRef={isActiveDocument ? activeDocumentRef : undefined}
|
innerRef={isActiveDocument ? activeDocumentRef : undefined}
|
||||||
|
@ -115,7 +156,7 @@ function DocumentLink({
|
||||||
<>
|
<>
|
||||||
{hasChildDocuments && (
|
{hasChildDocuments && (
|
||||||
<Disclosure
|
<Disclosure
|
||||||
expanded={expanded}
|
expanded={expanded && !isDragging}
|
||||||
onClick={handleDisclosureClick}
|
onClick={handleDisclosureClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -126,11 +167,12 @@ function DocumentLink({
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
isActiveDrop={isOver && canDrop}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
exact={false}
|
exact={false}
|
||||||
menuOpen={menuOpen}
|
menuOpen={menuOpen}
|
||||||
menu={
|
menu={
|
||||||
document ? (
|
document && !isMoving ? (
|
||||||
<Fade>
|
<Fade>
|
||||||
<DocumentMenu
|
<DocumentMenu
|
||||||
position="right"
|
position="right"
|
||||||
|
@ -141,10 +183,12 @@ function DocumentLink({
|
||||||
</Fade>
|
</Fade>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
></SidebarLink>
|
/>
|
||||||
</DropToImport>
|
</DropToImport>
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
|
||||||
{expanded && (
|
{expanded && !isDragging && (
|
||||||
<>
|
<>
|
||||||
{node.children.map((childNode) => (
|
{node.children.map((childNode) => (
|
||||||
<ObservedDocumentLink
|
<ObservedDocumentLink
|
||||||
|
@ -159,10 +203,15 @@ function DocumentLink({
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Draggable = styled("div")`
|
||||||
|
opacity: ${(props) => (props.$isDragging || props.$isMoving ? 0.5 : 1)};
|
||||||
|
pointer-events: ${(props) => (props.$isMoving ? "none" : "all")};
|
||||||
|
`;
|
||||||
|
|
||||||
const Disclosure = styled(CollapsedIcon)`
|
const Disclosure = styled(CollapsedIcon)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -24px;
|
left: -24px;
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Props = {
|
||||||
menuOpen?: boolean,
|
menuOpen?: boolean,
|
||||||
iconColor?: string,
|
iconColor?: string,
|
||||||
active?: boolean,
|
active?: boolean,
|
||||||
|
isActiveDrop?: boolean,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
exact?: boolean,
|
exact?: boolean,
|
||||||
depth?: number,
|
depth?: number,
|
||||||
|
@ -30,6 +31,7 @@ function SidebarLink({
|
||||||
to,
|
to,
|
||||||
label,
|
label,
|
||||||
active,
|
active,
|
||||||
|
isActiveDrop,
|
||||||
menu,
|
menu,
|
||||||
menuOpen,
|
menuOpen,
|
||||||
theme,
|
theme,
|
||||||
|
@ -54,7 +56,8 @@ function SidebarLink({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledNavLink
|
<StyledNavLink
|
||||||
activeStyle={activeStyle}
|
$isActiveDrop={isActiveDrop}
|
||||||
|
activeStyle={isActiveDrop ? undefined : activeStyle}
|
||||||
style={active ? activeStyle : style}
|
style={active ? activeStyle : style}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
|
@ -103,12 +106,20 @@ const StyledNavLink = styled(NavLink)`
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: ${(props) => props.theme.sidebarText};
|
background: ${(props) =>
|
||||||
|
props.$isActiveDrop ? props.theme.slateDark : "inherit"};
|
||||||
|
color: ${(props) =>
|
||||||
|
props.$isActiveDrop ? props.theme.white : props.theme.sidebarText};
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
${(props) => (props.$isActiveDrop ? `fill: ${props.theme.white};` : "")}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${(props) => props.theme.text};
|
color: ${(props) =>
|
||||||
|
props.$isActiveDrop ? props.theme.white : props.theme.text};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
|
@ -3,9 +3,10 @@ import "mobx-react-lite/batchingForReactDom";
|
||||||
import "focus-visible";
|
import "focus-visible";
|
||||||
import { Provider } from "mobx-react";
|
import { Provider } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { DndProvider } from "react-dnd";
|
||||||
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import { BrowserRouter as Router } from "react-router-dom";
|
import { BrowserRouter as Router } from "react-router-dom";
|
||||||
|
|
||||||
import { initI18n } from "shared/i18n";
|
import { initI18n } from "shared/i18n";
|
||||||
import stores from "stores";
|
import stores from "stores";
|
||||||
import ErrorBoundary from "components/ErrorBoundary";
|
import ErrorBoundary from "components/ErrorBoundary";
|
||||||
|
@ -24,6 +25,7 @@ if (element) {
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Provider {...stores}>
|
<Provider {...stores}>
|
||||||
<Theme>
|
<Theme>
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Router>
|
<Router>
|
||||||
<>
|
<>
|
||||||
<ScrollToTop>
|
<ScrollToTop>
|
||||||
|
@ -32,6 +34,7 @@ if (element) {
|
||||||
<Toasts />
|
<Toasts />
|
||||||
</>
|
</>
|
||||||
</Router>
|
</Router>
|
||||||
|
</DndProvider>
|
||||||
</Theme>
|
</Theme>
|
||||||
</Provider>
|
</Provider>
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
|
|
|
@ -79,12 +79,12 @@ export default class Collection extends BaseModel {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pathToDocument(document: Document) {
|
pathToDocument(documentId: string) {
|
||||||
let path;
|
let path;
|
||||||
const traveler = (nodes, previousPath) => {
|
const traveler = (nodes, previousPath) => {
|
||||||
nodes.forEach((childNode) => {
|
nodes.forEach((childNode) => {
|
||||||
const newPath = [...previousPath, childNode];
|
const newPath = [...previousPath, childNode];
|
||||||
if (childNode.id === document.id) {
|
if (childNode.id === documentId) {
|
||||||
path = newPath;
|
path = newPath;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,7 +268,7 @@ export default class Document extends BaseModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
move = (collectionId: string, parentDocumentId: ?string) => {
|
move = (collectionId: string, parentDocumentId: ?string) => {
|
||||||
return this.store.move(this, collectionId, parentDocumentId);
|
return this.store.move(this.id, collectionId, parentDocumentId);
|
||||||
};
|
};
|
||||||
|
|
||||||
duplicate = () => {
|
duplicate = () => {
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
@observable searchCache: Map<string, SearchResult[]> = new Map();
|
@observable searchCache: Map<string, SearchResult[]> = new Map();
|
||||||
@observable starredIds: Map<string, boolean> = new Map();
|
@observable starredIds: Map<string, boolean> = new Map();
|
||||||
@observable backlinks: Map<string, string[]> = new Map();
|
@observable backlinks: Map<string, string[]> = new Map();
|
||||||
|
@observable movingDocumentId: ?string;
|
||||||
|
|
||||||
importFileTypes: string[] = [
|
importFileTypes: string[] = [
|
||||||
"text/markdown",
|
"text/markdown",
|
||||||
|
@ -450,12 +451,15 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
move = async (
|
move = async (
|
||||||
document: Document,
|
documentId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
parentDocumentId: ?string
|
parentDocumentId: ?string
|
||||||
) => {
|
) => {
|
||||||
|
this.movingDocumentId = documentId;
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await client.post("/documents.move", {
|
const res = await client.post("/documents.move", {
|
||||||
id: document.id,
|
id: documentId,
|
||||||
collectionId,
|
collectionId,
|
||||||
parentDocumentId,
|
parentDocumentId,
|
||||||
});
|
});
|
||||||
|
@ -464,6 +468,9 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
res.data.documents.forEach(this.add);
|
res.data.documents.forEach(this.add);
|
||||||
res.data.collections.forEach(this.rootStore.collections.add);
|
res.data.collections.forEach(this.rootStore.collections.add);
|
||||||
this.addPolicies(res.policies);
|
this.addPolicies(res.policies);
|
||||||
|
} finally {
|
||||||
|
this.movingDocumentId = undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
// flow-typed signature: c69369aa4bc769d5f1d4f6ec9c76d8f2
|
|
||||||
// flow-typed version: c6154227d1/react-dropzone_v4.x.x/flow_>=v0.104.x
|
|
||||||
|
|
||||||
declare module "react-dropzone" {
|
|
||||||
declare type ChildrenProps = {
|
|
||||||
draggedFiles: Array<File>,
|
|
||||||
acceptedFiles: Array<File>,
|
|
||||||
rejectedFiles: Array<File>,
|
|
||||||
isDragActive: boolean,
|
|
||||||
isDragAccept: boolean,
|
|
||||||
isDragReject: boolean,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
declare type DropzoneFile = File & { preview?: string, ... }
|
|
||||||
|
|
||||||
declare type DropzoneProps = {
|
|
||||||
accept?: string,
|
|
||||||
children?: React$Node | (ChildrenProps) => React$Node,
|
|
||||||
disableClick?: boolean,
|
|
||||||
disabled?: boolean,
|
|
||||||
disablePreview?: boolean,
|
|
||||||
preventDropOnDocument?: boolean,
|
|
||||||
inputProps?: Object,
|
|
||||||
multiple?: boolean,
|
|
||||||
name?: string,
|
|
||||||
maxSize?: number,
|
|
||||||
minSize?: number,
|
|
||||||
className?: string,
|
|
||||||
activeClassName?: string,
|
|
||||||
acceptClassName?: string,
|
|
||||||
rejectClassName?: string,
|
|
||||||
disabledClassName?: string,
|
|
||||||
style?: Object,
|
|
||||||
activeStyle?: Object,
|
|
||||||
acceptStyle?: Object,
|
|
||||||
rejectStyle?: Object,
|
|
||||||
disabledStyle?: Object,
|
|
||||||
onClick?: (event: SyntheticMouseEvent<>) => mixed,
|
|
||||||
onDrop?: (acceptedFiles: Array<DropzoneFile>, rejectedFiles: Array<DropzoneFile>, event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onDropAccepted?: (acceptedFiles: Array<DropzoneFile>, event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onDropRejected?: (rejectedFiles: Array<DropzoneFile>, event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onDragStart?: (event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onDragEnter?: (event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onDragOver?: (event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onDragLeave?: (event: SyntheticDragEvent<>) => mixed,
|
|
||||||
onFileDialogCancel?: () => mixed,
|
|
||||||
...
|
|
||||||
};
|
|
||||||
|
|
||||||
declare class Dropzone extends React$Component<DropzoneProps> {
|
|
||||||
open(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module.exports: typeof Dropzone;
|
|
||||||
}
|
|
|
@ -137,6 +137,8 @@
|
||||||
"react-autosize-textarea": "^6.0.0",
|
"react-autosize-textarea": "^6.0.0",
|
||||||
"react-avatar-editor": "^10.3.0",
|
"react-avatar-editor": "^10.3.0",
|
||||||
"react-color": "^2.17.3",
|
"react-color": "^2.17.3",
|
||||||
|
"react-dnd": "^11.1.3",
|
||||||
|
"react-dnd-html5-backend": "^11.1.3",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-dropzone": "^11.2.4",
|
"react-dropzone": "^11.2.4",
|
||||||
"react-helmet": "^5.2.0",
|
"react-helmet": "^5.2.0",
|
||||||
|
|
|
@ -28,6 +28,8 @@ export default async function documentMover({
|
||||||
|
|
||||||
document.collectionId = collectionId;
|
document.collectionId = collectionId;
|
||||||
document.parentDocumentId = null;
|
document.parentDocumentId = null;
|
||||||
|
document.lastModifiedById = user.id;
|
||||||
|
document.updatedBy = user;
|
||||||
|
|
||||||
await document.save();
|
await document.save();
|
||||||
result.documents.push(document);
|
result.documents.push(document);
|
||||||
|
@ -54,6 +56,8 @@ export default async function documentMover({
|
||||||
// add to new collection (may be the same)
|
// add to new collection (may be the same)
|
||||||
document.collectionId = collectionId;
|
document.collectionId = collectionId;
|
||||||
document.parentDocumentId = parentDocumentId;
|
document.parentDocumentId = parentDocumentId;
|
||||||
|
document.lastModifiedById = user.id;
|
||||||
|
document.updatedBy = user;
|
||||||
|
|
||||||
const newCollection: Collection = collectionChanged
|
const newCollection: Collection = collectionChanged
|
||||||
? await Collection.findByPk(collectionId, { transaction })
|
? await Collection.findByPk(collectionId, { transaction })
|
||||||
|
|
82
yarn.lock
82
yarn.lock
|
@ -1365,6 +1365,21 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" ">= 8"
|
"@types/node" ">= 8"
|
||||||
|
|
||||||
|
"@react-dnd/asap@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.0.tgz#b300eeed83e9801f51bd66b0337c9a6f04548651"
|
||||||
|
integrity sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==
|
||||||
|
|
||||||
|
"@react-dnd/invariant@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e"
|
||||||
|
integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==
|
||||||
|
|
||||||
|
"@react-dnd/shallowequal@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
|
||||||
|
integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
|
||||||
|
|
||||||
"@rehooks/window-scroll-position@^1.0.1":
|
"@rehooks/window-scroll-position@^1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@rehooks/window-scroll-position/-/window-scroll-position-1.0.1.tgz#3cb80f22cbf9cdbd2041b5236ae1fce8245b2f1c"
|
resolved "https://registry.yarnpkg.com/@rehooks/window-scroll-position/-/window-scroll-position-1.0.1.tgz#3cb80f22cbf9cdbd2041b5236ae1fce8245b2f1c"
|
||||||
|
@ -1555,6 +1570,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@types/unist" "*"
|
||||||
|
|
||||||
|
"@types/hoist-non-react-statics@^3.3.1":
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||||
|
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
|
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
|
||||||
|
@ -1604,6 +1627,19 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00"
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00"
|
||||||
integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==
|
integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==
|
||||||
|
|
||||||
|
"@types/prop-types@*":
|
||||||
|
version "15.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||||
|
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||||
|
|
||||||
|
"@types/react@*":
|
||||||
|
version "17.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
||||||
|
integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/stack-utils@^2.0.0":
|
"@types/stack-utils@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
|
||||||
|
@ -3765,6 +3801,11 @@ cssstyle@^2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssom "~0.3.6"
|
cssom "~0.3.6"
|
||||||
|
|
||||||
|
csstype@^3.0.2:
|
||||||
|
version "3.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8"
|
||||||
|
integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==
|
||||||
|
|
||||||
cyclist@^1.0.1:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
|
@ -4002,6 +4043,15 @@ direction@^0.1.5:
|
||||||
resolved "https://registry.yarnpkg.com/direction/-/direction-0.1.5.tgz#ce5d797f97e26f8be7beff53f7dc40e1c1a9ec4c"
|
resolved "https://registry.yarnpkg.com/direction/-/direction-0.1.5.tgz#ce5d797f97e26f8be7beff53f7dc40e1c1a9ec4c"
|
||||||
integrity sha1-zl15f5fib4vnvv9T99xA4cGp7Ew=
|
integrity sha1-zl15f5fib4vnvv9T99xA4cGp7Ew=
|
||||||
|
|
||||||
|
dnd-core@^11.1.3:
|
||||||
|
version "11.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-11.1.3.tgz#f92099ba7245e49729d2433157031a6267afcc98"
|
||||||
|
integrity sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==
|
||||||
|
dependencies:
|
||||||
|
"@react-dnd/asap" "^4.0.0"
|
||||||
|
"@react-dnd/invariant" "^2.0.0"
|
||||||
|
redux "^4.0.4"
|
||||||
|
|
||||||
doctrine@1.5.0:
|
doctrine@1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
|
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
|
||||||
|
@ -5753,7 +5803,7 @@ hmac-drbg@^1.0.0:
|
||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0:
|
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
|
@ -9759,6 +9809,23 @@ react-color@^2.17.3:
|
||||||
reactcss "^1.2.0"
|
reactcss "^1.2.0"
|
||||||
tinycolor2 "^1.4.1"
|
tinycolor2 "^1.4.1"
|
||||||
|
|
||||||
|
react-dnd-html5-backend@^11.1.3:
|
||||||
|
version "11.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz#2749f04f416ec230ea193f5c1fbea2de7dffb8f7"
|
||||||
|
integrity sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==
|
||||||
|
dependencies:
|
||||||
|
dnd-core "^11.1.3"
|
||||||
|
|
||||||
|
react-dnd@^11.1.3:
|
||||||
|
version "11.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-11.1.3.tgz#f9844f5699ccc55dfc81462c2c19f726e670c1af"
|
||||||
|
integrity sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==
|
||||||
|
dependencies:
|
||||||
|
"@react-dnd/shallowequal" "^2.0.0"
|
||||||
|
"@types/hoist-non-react-statics" "^3.3.1"
|
||||||
|
dnd-core "^11.1.3"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
react-dom@^16.8.6:
|
react-dom@^16.8.6:
|
||||||
version "16.14.0"
|
version "16.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
|
||||||
|
@ -10027,6 +10094,14 @@ redis@^3.0.0:
|
||||||
redis-errors "^1.2.0"
|
redis-errors "^1.2.0"
|
||||||
redis-parser "^3.0.0"
|
redis-parser "^3.0.0"
|
||||||
|
|
||||||
|
redux@^4.0.4:
|
||||||
|
version "4.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||||
|
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
symbol-observable "^1.2.0"
|
||||||
|
|
||||||
referrer-policy@1.2.0:
|
referrer-policy@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e"
|
resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e"
|
||||||
|
@ -11387,6 +11462,11 @@ supports-hyperlinks@^2.0.0:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
supports-color "^7.0.0"
|
supports-color "^7.0.0"
|
||||||
|
|
||||||
|
symbol-observable@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||||
|
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||||
|
|
||||||
symbol-tree@^3.2.4:
|
symbol-tree@^3.2.4:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||||
|
|
Reference in New Issue