Merge pull request #483 from IsaacWise06/reorder-collections
feat(collections): Reorder top-level collections in the sidebar
This commit is contained in:
commit
281b376eac
|
@ -1,169 +1,358 @@
|
||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import Tree, {
|
||||||
|
mutateTree,
|
||||||
|
moveItemOnTree,
|
||||||
|
RenderItemParams,
|
||||||
|
TreeItem,
|
||||||
|
TreeData,
|
||||||
|
ItemId,
|
||||||
|
TreeSourcePosition,
|
||||||
|
TreeDestinationPosition,
|
||||||
|
} from "@atlaskit/tree";
|
||||||
import useCollectionStore from "@/store/collections";
|
import useCollectionStore from "@/store/collections";
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
import { Collection } from "@prisma/client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { useEffect, useState } from "react";
|
import useAccountStore from "@/store/account";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
type Props = {
|
interface ExtendedTreeItem extends TreeItem {
|
||||||
links: boolean;
|
data: Collection;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const CollectionListing = () => {
|
||||||
|
const { collections, updateCollection } = useCollectionStore();
|
||||||
|
const { account, updateAccount } = useAccountStore();
|
||||||
|
|
||||||
const CollectionSelection = ({ links }: Props) => {
|
|
||||||
const { collections } = useCollectionStore();
|
|
||||||
const [active, setActive] = useState("");
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const currentPath = router.asPath;
|
||||||
|
|
||||||
|
const initialTree = useMemo(() => {
|
||||||
|
if (collections.length > 0) {
|
||||||
|
return buildTreeFromCollections(
|
||||||
|
collections,
|
||||||
|
router,
|
||||||
|
account.collectionOrder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [collections, router]);
|
||||||
|
|
||||||
|
const [tree, setTree] = useState(initialTree);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActive(router.asPath);
|
setTree(initialTree);
|
||||||
}, [router, collections]);
|
}, [initialTree]);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
if (account.username) {
|
||||||
{collections[0] ? (
|
if (!account.collectionOrder || account.collectionOrder.length === 0)
|
||||||
collections
|
updateAccount({
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
...account,
|
||||||
.filter((e) => e.parentId === null)
|
collectionOrder: collections
|
||||||
.map((e, i) => (
|
.filter((e) => e.parentId === null) // Filter out collections with non-null parentId
|
||||||
<CollectionItem
|
.map((e) => e.id as number), // Use "as number" to assert that e.id is a number
|
||||||
key={i}
|
|
||||||
collection={e}
|
|
||||||
active={active}
|
|
||||||
collections={collections}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={`duration-100 py-1 px-2 flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
|
||||||
>
|
|
||||||
<p className="text-neutral text-xs font-semibold truncate w-full pr-7">
|
|
||||||
You Have No Collections...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CollectionSelection;
|
|
||||||
|
|
||||||
const CollectionItem = ({
|
|
||||||
collection,
|
|
||||||
active,
|
|
||||||
collections,
|
|
||||||
}: {
|
|
||||||
collection: CollectionIncludingMembersAndLinkCount;
|
|
||||||
active: string;
|
|
||||||
collections: CollectionIncludingMembersAndLinkCount[];
|
|
||||||
}) => {
|
|
||||||
const hasChildren = collections.some((e) => e.parentId === collection.id);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Check if the current collection or any of its subcollections is active
|
|
||||||
const isActiveOrParentOfActive = React.useMemo(() => {
|
|
||||||
const isActive = active === `/collections/${collection.id}`;
|
|
||||||
if (isActive) return true;
|
|
||||||
|
|
||||||
const checkIfParentOfActive = (parentId: number): boolean => {
|
|
||||||
return collections.some((e) => {
|
|
||||||
if (e.id === parseInt(active.split("/collections/")[1])) {
|
|
||||||
if (e.parentId === parentId) return true;
|
|
||||||
if (e.parentId) return checkIfParentOfActive(e.parentId);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
else {
|
||||||
|
// const collectionsIds = collections.map((c) => c.id);
|
||||||
|
// const orderIds = [...account.collectionOrder];
|
||||||
|
// const missingInOrder = collectionsIds.filter(
|
||||||
|
// (id) => !orderIds.includes(id)
|
||||||
|
// );
|
||||||
|
// if (missingInOrder.length > 0) {
|
||||||
|
// updateAccount({
|
||||||
|
// ...account,
|
||||||
|
// collectionOrder: [...account.collectionOrder, ...missingInOrder],
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [account, collections]);
|
||||||
|
|
||||||
|
const onExpand = (movedCollectionId: ItemId) => {
|
||||||
|
setTree((currentTree) =>
|
||||||
|
mutateTree(currentTree!, movedCollectionId, { isExpanded: true })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return checkIfParentOfActive(collection.id as number);
|
const onCollapse = (movedCollectionId: ItemId) => {
|
||||||
}, [active, collection.id, collections]);
|
setTree((currentTree) =>
|
||||||
|
mutateTree(currentTree as TreeData, movedCollectionId, {
|
||||||
|
isExpanded: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(isActiveOrParentOfActive);
|
const onDragEnd = async (
|
||||||
|
source: TreeSourcePosition,
|
||||||
|
destination: TreeDestinationPosition | undefined
|
||||||
|
) => {
|
||||||
|
if (!destination || !tree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
if (
|
||||||
setIsOpen(isActiveOrParentOfActive);
|
source.index === destination.index &&
|
||||||
}, [isActiveOrParentOfActive]);
|
source.parentId === destination.parentId
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return hasChildren ? (
|
const movedCollectionId = Number(
|
||||||
<>
|
tree.items[source.parentId].children[source.index]
|
||||||
<div
|
);
|
||||||
className={`${
|
|
||||||
active === `/collections/${collection.id}`
|
|
||||||
? "bg-primary/20"
|
|
||||||
: "hover:bg-neutral/20"
|
|
||||||
} duration-100 rounded-md flex w-full items-center cursor-pointer mb-1 px-2 gap-1`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className={`bi-chevron-down ${
|
|
||||||
isOpen ? "rotate-reverse" : "rotate"
|
|
||||||
}`}
|
|
||||||
></i>
|
|
||||||
</button>
|
|
||||||
<Link href={`/collections/${collection.id}`} className="w-full">
|
|
||||||
<div
|
|
||||||
className={`py-1 cursor-pointer flex items-center gap-2 w-full h-8 capitalize`}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="bi-folder-fill text-2xl drop-shadow"
|
|
||||||
style={{ color: collection.color }}
|
|
||||||
></i>
|
|
||||||
<p className="truncate w-full">{collection.name}</p>
|
|
||||||
|
|
||||||
{collection.isPublic ? (
|
const movedCollection = collections.find((c) => c.id === movedCollectionId);
|
||||||
<i
|
|
||||||
className="bi-globe2 text-sm text-black/50 dark:text-white/50 drop-shadow"
|
const destinationCollection = collections.find(
|
||||||
title="This collection is being shared publicly."
|
(c) => c.id === Number(destination.parentId)
|
||||||
></i>
|
);
|
||||||
) : undefined}
|
|
||||||
<div className="drop-shadow text-neutral text-xs">
|
console.log(
|
||||||
{collection._count?.links}
|
"Moved:",
|
||||||
</div>
|
movedCollection,
|
||||||
</div>
|
"Destination:",
|
||||||
</Link>
|
destinationCollection
|
||||||
</div>
|
);
|
||||||
{isOpen && hasChildren && (
|
if (
|
||||||
<div className="ml-4 pl-1 border-l border-neutral-content">
|
(movedCollection?.ownerId !== account.id &&
|
||||||
{collections
|
destination.parentId !== source.parentId) ||
|
||||||
.filter((e) => e.parentId === collection.id)
|
(destinationCollection?.ownerId !== account.id &&
|
||||||
.map((subCollection) => (
|
destination.parentId !== "root")
|
||||||
<CollectionItem
|
) {
|
||||||
key={subCollection.id}
|
return toast.error(
|
||||||
collection={subCollection}
|
"You can't make change to a collection you don't own."
|
||||||
active={active}
|
);
|
||||||
collections={collections}
|
}
|
||||||
|
|
||||||
|
console.log("source:", source, "destination:", destination);
|
||||||
|
setTree((currentTree) => moveItemOnTree(currentTree!, source, destination));
|
||||||
|
|
||||||
|
const updatedCollectionOrder = [...account.collectionOrder];
|
||||||
|
|
||||||
|
if (source.parentId !== destination.parentId) {
|
||||||
|
await updateCollection({
|
||||||
|
...movedCollection,
|
||||||
|
parentId:
|
||||||
|
destination.parentId && destination.parentId !== "root"
|
||||||
|
? Number(destination.parentId)
|
||||||
|
: destination.parentId === "root"
|
||||||
|
? "root"
|
||||||
|
: null,
|
||||||
|
} as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
destination.index !== undefined &&
|
||||||
|
destination.parentId === source.parentId &&
|
||||||
|
source.parentId === "root"
|
||||||
|
) {
|
||||||
|
updatedCollectionOrder.splice(source.index, 1);
|
||||||
|
|
||||||
|
console.log("Order1", updatedCollectionOrder);
|
||||||
|
|
||||||
|
updatedCollectionOrder.splice(destination.index, 0, movedCollectionId);
|
||||||
|
|
||||||
|
console.log("Order2", updatedCollectionOrder);
|
||||||
|
|
||||||
|
console.log("Moved id:", movedCollectionId);
|
||||||
|
console.log("Order:", updatedCollectionOrder);
|
||||||
|
|
||||||
|
await updateAccount({
|
||||||
|
...account,
|
||||||
|
collectionOrder: updatedCollectionOrder,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
destination.index !== undefined &&
|
||||||
|
destination.parentId === "root"
|
||||||
|
) {
|
||||||
|
console.log("Order1", updatedCollectionOrder);
|
||||||
|
|
||||||
|
updatedCollectionOrder.splice(destination.index, 0, movedCollectionId);
|
||||||
|
|
||||||
|
console.log("Order2", updatedCollectionOrder);
|
||||||
|
|
||||||
|
console.log("Moved id:", movedCollectionId);
|
||||||
|
console.log("Order:", updatedCollectionOrder);
|
||||||
|
|
||||||
|
await updateAccount({
|
||||||
|
...account,
|
||||||
|
collectionOrder: updatedCollectionOrder,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
source.parentId === "root" &&
|
||||||
|
destination.parentId &&
|
||||||
|
destination.parentId !== "root"
|
||||||
|
) {
|
||||||
|
updatedCollectionOrder.splice(source.index, 1);
|
||||||
|
|
||||||
|
console.log("Order", updatedCollectionOrder);
|
||||||
|
|
||||||
|
await updateAccount({
|
||||||
|
...account,
|
||||||
|
collectionOrder: updatedCollectionOrder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!tree) {
|
||||||
|
return <></>;
|
||||||
|
} else
|
||||||
|
return (
|
||||||
|
<Tree
|
||||||
|
tree={tree}
|
||||||
|
renderItem={(itemProps) => renderItem({ ...itemProps }, currentPath)}
|
||||||
|
onExpand={onExpand}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
isDragEnabled
|
||||||
|
isNestingEnabled
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Link href={`/collections/${collection.id}`} className="w-full">
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
active === `/collections/${collection.id}`
|
|
||||||
? "bg-primary/20"
|
|
||||||
: "hover:bg-neutral/20"
|
|
||||||
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize mb-1`}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="bi-folder-fill text-2xl drop-shadow"
|
|
||||||
style={{ color: collection.color }}
|
|
||||||
></i>
|
|
||||||
<p className="truncate w-full">{collection.name}</p>
|
|
||||||
|
|
||||||
{collection.isPublic ? (
|
|
||||||
<i
|
|
||||||
className="bi-globe2 text-sm text-black/50 dark:text-white/50 drop-shadow"
|
|
||||||
title="This collection is being shared publicly."
|
|
||||||
></i>
|
|
||||||
) : undefined}
|
|
||||||
<div className="drop-shadow text-neutral text-xs">
|
|
||||||
{collection._count?.links}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default CollectionListing;
|
||||||
|
|
||||||
|
const renderItem = (
|
||||||
|
{ item, onExpand, onCollapse, provided }: RenderItemParams,
|
||||||
|
currentPath: string
|
||||||
|
) => {
|
||||||
|
const collection = item.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={provided.innerRef} {...provided.draggableProps} className="mb-1">
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
currentPath === `/collections/${collection.id}`
|
||||||
|
? "bg-primary/20 is-active"
|
||||||
|
: "hover:bg-neutral/20"
|
||||||
|
} duration-100 flex gap-1 items-center pr-2 pl-1 rounded-md`}
|
||||||
|
>
|
||||||
|
{Icon(item as ExtendedTreeItem, onExpand, onCollapse)}
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={`/collections/${collection.id}`}
|
||||||
|
className="w-full"
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`py-1 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="bi-folder-fill text-2xl drop-shadow"
|
||||||
|
style={{ color: collection.color }}
|
||||||
|
></i>
|
||||||
|
<p className="truncate w-full">{collection.name}</p>
|
||||||
|
|
||||||
|
{collection.isPublic ? (
|
||||||
|
<i
|
||||||
|
className="bi-globe2 text-sm text-black/50 dark:text-white/50 drop-shadow"
|
||||||
|
title="This collection is being shared publicly."
|
||||||
|
></i>
|
||||||
|
) : undefined}
|
||||||
|
<div className="drop-shadow text-neutral text-xs">
|
||||||
|
{collection._count?.links}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Icon = (
|
||||||
|
item: ExtendedTreeItem,
|
||||||
|
onExpand: (id: ItemId) => void,
|
||||||
|
onCollapse: (id: ItemId) => void
|
||||||
|
) => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
return item.isExpanded ? (
|
||||||
|
<button onClick={() => onCollapse(item.id)}>
|
||||||
|
<div className="bi-caret-down-fill opacity-50 hover:opacity-100 duration-200"></div>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button onClick={() => onExpand(item.id)}>
|
||||||
|
<div className="bi-caret-right-fill opacity-40 hover:opacity-100 duration-200"></div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// return <span>•</span>;
|
||||||
|
return <div className="pl-1"></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTreeFromCollections = (
|
||||||
|
collections: CollectionIncludingMembersAndLinkCount[],
|
||||||
|
router: ReturnType<typeof useRouter>,
|
||||||
|
order?: number[]
|
||||||
|
): TreeData => {
|
||||||
|
if (order) {
|
||||||
|
collections.sort((a: any, b: any) => {
|
||||||
|
return order.indexOf(a.id) - order.indexOf(b.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: { [key: string]: ExtendedTreeItem } = collections.reduce(
|
||||||
|
(acc: any, collection) => {
|
||||||
|
acc[collection.id as number] = {
|
||||||
|
id: collection.id,
|
||||||
|
children: [],
|
||||||
|
hasChildren: false,
|
||||||
|
isExpanded: false,
|
||||||
|
data: {
|
||||||
|
id: collection.id,
|
||||||
|
parentId: collection.parentId,
|
||||||
|
name: collection.name,
|
||||||
|
description: collection.description,
|
||||||
|
color: collection.color,
|
||||||
|
isPublic: collection.isPublic,
|
||||||
|
ownerId: collection.ownerId,
|
||||||
|
createdAt: collection.createdAt,
|
||||||
|
updatedAt: collection.updatedAt,
|
||||||
|
_count: {
|
||||||
|
links: collection._count?.links,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeCollectionId = Number(router.asPath.split("/collections/")[1]);
|
||||||
|
|
||||||
|
if (activeCollectionId) {
|
||||||
|
for (const item in items) {
|
||||||
|
const collection = items[item];
|
||||||
|
if (Number(item) === activeCollectionId && collection.data.parentId) {
|
||||||
|
// get all the parents of the active collection recursively until root and set isExpanded to true
|
||||||
|
let parentId = collection.data.parentId || null;
|
||||||
|
while (parentId) {
|
||||||
|
items[parentId].isExpanded = true;
|
||||||
|
parentId = items[parentId].data.parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collections.forEach((collection) => {
|
||||||
|
const parentId = collection.parentId;
|
||||||
|
if (parentId && items[parentId] && collection.id) {
|
||||||
|
items[parentId].children.push(collection.id);
|
||||||
|
items[parentId].hasChildren = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootId = "root";
|
||||||
|
items[rootId] = {
|
||||||
|
id: rootId,
|
||||||
|
children: (collections
|
||||||
|
.filter((c) => c.parentId === null)
|
||||||
|
.map((c) => c.id) || "") as unknown as string[],
|
||||||
|
hasChildren: true,
|
||||||
|
isExpanded: true,
|
||||||
|
data: { name: "Root" } as Collection,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { rootId, items };
|
||||||
|
};
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { HexColorPicker } from "react-colorful";
|
||||||
import { Collection } from "@prisma/client";
|
import { Collection } from "@prisma/client";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||||
|
import useAccountStore from "@/store/account";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
|
@ -21,6 +23,8 @@ export default function NewCollectionModal({ onClose, parent }: Props) {
|
||||||
} as Partial<Collection>;
|
} as Partial<Collection>;
|
||||||
|
|
||||||
const [collection, setCollection] = useState<Partial<Collection>>(initial);
|
const [collection, setCollection] = useState<Partial<Collection>>(initial);
|
||||||
|
const { setAccount } = useAccountStore();
|
||||||
|
const { data } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCollection(initial);
|
setCollection(initial);
|
||||||
|
@ -42,7 +46,11 @@ export default function NewCollectionModal({ onClose, parent }: Props) {
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success("Created!");
|
toast.success("Created!");
|
||||||
|
if (response.data) {
|
||||||
|
// If the collection was created successfully, we need to get the new collection order
|
||||||
|
setAccount(data?.user.id as number);
|
||||||
onClose();
|
onClose();
|
||||||
|
}
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default function Navbar() {
|
||||||
setSidebar(true);
|
setSidebar(true);
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
}}
|
}}
|
||||||
className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden hidden sm:inline-flex"
|
className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden sm:inline-flex"
|
||||||
>
|
>
|
||||||
<i className="bi-list text-2xl leading-none"></i>
|
<i className="bi-list text-2xl leading-none"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,7 +65,7 @@ export default function Navbar() {
|
||||||
<ToggleDarkMode className="hidden sm:inline-grid" />
|
<ToggleDarkMode className="hidden sm:inline-grid" />
|
||||||
|
|
||||||
<div className="dropdown dropdown-end sm:inline-block hidden">
|
<div className="dropdown dropdown-end sm:inline-block hidden">
|
||||||
<div className="tooltip tooltip-bottom" data-tip="Create New...">
|
<div className="tooltip tooltip-bottom z-10" data-tip="Create New...">
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
|
|
|
@ -22,11 +22,10 @@ export default function Sidebar({ className }: { className?: string }) {
|
||||||
|
|
||||||
const { collections } = useCollectionStore();
|
const { collections } = useCollectionStore();
|
||||||
const { tags } = useTagStore();
|
const { tags } = useTagStore();
|
||||||
|
const [active, setActive] = useState("");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [active, setActive] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem("tagDisclosure", tagDisclosure ? "true" : "false");
|
localStorage.setItem("tagDisclosure", tagDisclosure ? "true" : "false");
|
||||||
}, [tagDisclosure]);
|
}, [tagDisclosure]);
|
||||||
|
@ -99,7 +98,7 @@ export default function Sidebar({ className }: { className?: string }) {
|
||||||
leaveTo="transform opacity-0 -translate-y-3"
|
leaveTo="transform opacity-0 -translate-y-3"
|
||||||
>
|
>
|
||||||
<Disclosure.Panel>
|
<Disclosure.Panel>
|
||||||
<CollectionListing links={true} />
|
<CollectionListing />
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
// StrictModeDroppable.tsx
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Droppable, DroppableProps } from "react-beautiful-dnd";
|
||||||
|
|
||||||
|
export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
|
||||||
|
const [enabled, setEnabled] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const animation = requestAnimationFrame(() => setEnabled(true));
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(animation);
|
||||||
|
setEnabled(false);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
if (!enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <Droppable {...props}>{children}</Droppable>;
|
||||||
|
};
|
|
@ -31,6 +31,8 @@ export default async function deleteCollection(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await removeFromOrders(userId, collectionId);
|
||||||
|
|
||||||
return { response: deletedUsersAndCollectionsRelation, status: 200 };
|
return { response: deletedUsersAndCollectionsRelation, status: 200 };
|
||||||
} else if (collectionIsAccessible?.ownerId !== userId) {
|
} else if (collectionIsAccessible?.ownerId !== userId) {
|
||||||
return { response: "Collection is not accessible.", status: 401 };
|
return { response: "Collection is not accessible.", status: 401 };
|
||||||
|
@ -57,6 +59,8 @@ export default async function deleteCollection(
|
||||||
|
|
||||||
await removeFolder({ filePath: `archives/${collectionId}` });
|
await removeFolder({ filePath: `archives/${collectionId}` });
|
||||||
|
|
||||||
|
await removeFromOrders(userId, collectionId);
|
||||||
|
|
||||||
return await prisma.collection.delete({
|
return await prisma.collection.delete({
|
||||||
where: {
|
where: {
|
||||||
id: collectionId,
|
id: collectionId,
|
||||||
|
@ -98,3 +102,28 @@ async function deleteSubCollections(collectionId: number) {
|
||||||
await removeFolder({ filePath: `archives/${subCollection.id}` });
|
await removeFolder({ filePath: `archives/${subCollection.id}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeFromOrders(userId: number, collectionId: number) {
|
||||||
|
const userCollectionOrder = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
collectionOrder: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userCollectionOrder)
|
||||||
|
await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
collectionOrder: {
|
||||||
|
set: userCollectionOrder.collectionOrder.filter(
|
||||||
|
(e: number) => e !== collectionId
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -18,25 +18,31 @@ export default async function updateCollection(
|
||||||
if (!(collectionIsAccessible?.ownerId === userId))
|
if (!(collectionIsAccessible?.ownerId === userId))
|
||||||
return { response: "Collection is not accessible.", status: 401 };
|
return { response: "Collection is not accessible.", status: 401 };
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
if (data.parentId) {
|
if (data.parentId) {
|
||||||
|
if (data.parentId !== ("root" as any)) {
|
||||||
const findParentCollection = await prisma.collection.findUnique({
|
const findParentCollection = await prisma.collection.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: data.parentId,
|
id: data.parentId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
ownerId: true,
|
ownerId: true,
|
||||||
|
parentId: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
findParentCollection?.ownerId !== userId ||
|
findParentCollection?.ownerId !== userId ||
|
||||||
typeof data.parentId !== "number"
|
typeof data.parentId !== "number" ||
|
||||||
|
findParentCollection?.parentId === data.parentId
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
response: "You are not authorized to create a sub-collection here.",
|
response: "You are not authorized to create a sub-collection here.",
|
||||||
status: 403,
|
status: 403,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedCollection = await prisma.$transaction(async () => {
|
const updatedCollection = await prisma.$transaction(async () => {
|
||||||
await prisma.usersAndCollections.deleteMany({
|
await prisma.usersAndCollections.deleteMany({
|
||||||
|
@ -56,12 +62,17 @@ export default async function updateCollection(
|
||||||
description: data.description,
|
description: data.description,
|
||||||
color: data.color,
|
color: data.color,
|
||||||
isPublic: data.isPublic,
|
isPublic: data.isPublic,
|
||||||
parent: data.parentId
|
parent:
|
||||||
|
data.parentId && data.parentId !== ("root" as any)
|
||||||
? {
|
? {
|
||||||
connect: {
|
connect: {
|
||||||
id: data.parentId,
|
id: data.parentId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
: data.parentId === ("root" as any)
|
||||||
|
? {
|
||||||
|
disconnect: true,
|
||||||
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
members: {
|
members: {
|
||||||
create: data.members.map((e) => ({
|
create: data.members.map((e) => ({
|
||||||
|
|
|
@ -67,6 +67,17 @@ export default async function postCollection(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
collectionOrder: {
|
||||||
|
push: newCollection.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
createFolder({ filePath: `archives/${newCollection.id}` });
|
createFolder({ filePath: `archives/${newCollection.id}` });
|
||||||
|
|
||||||
return { response: newCollection, status: 200 };
|
return { response: newCollection, status: 200 };
|
||||||
|
|
|
@ -183,6 +183,9 @@ export default async function updateUserById(
|
||||||
email: data.email?.toLowerCase().trim(),
|
email: data.email?.toLowerCase().trim(),
|
||||||
isPrivate: data.isPrivate,
|
isPrivate: data.isPrivate,
|
||||||
image: data.image ? `uploads/avatar/${userId}.jpg` : "",
|
image: data.image ? `uploads/avatar/${userId}.jpg` : "",
|
||||||
|
collectionOrder: data.collectionOrder.filter(
|
||||||
|
(value, index, self) => self.indexOf(value) === index
|
||||||
|
),
|
||||||
archiveAsScreenshot: data.archiveAsScreenshot,
|
archiveAsScreenshot: data.archiveAsScreenshot,
|
||||||
archiveAsPDF: data.archiveAsPDF,
|
archiveAsPDF: data.archiveAsPDF,
|
||||||
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,js,json,md}\""
|
"format": "prettier --write \"**/*.{ts,tsx,js,json,md}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@atlaskit/tree": "^8.8.7",
|
||||||
"@auth/prisma-adapter": "^1.0.1",
|
"@auth/prisma-adapter": "^1.0.1",
|
||||||
"@aws-sdk/client-s3": "^3.379.1",
|
"@aws-sdk/client-s3": "^3.379.1",
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"@types/node": "^20.10.4",
|
"@types/node": "^20.10.4",
|
||||||
"@types/nodemailer": "^6.4.8",
|
"@types/nodemailer": "^6.4.8",
|
||||||
"@types/react": "18.2.14",
|
"@types/react": "18.2.14",
|
||||||
|
"@types/react-beautiful-dnd": "^13.1.8",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
|
@ -55,6 +57,7 @@
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
"playwright": "^1.35.1",
|
"playwright": "^1.35.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "collectionOrder" INTEGER[] DEFAULT ARRAY[]::INTEGER[];
|
|
@ -38,6 +38,7 @@ model User {
|
||||||
tags Tag[]
|
tags Tag[]
|
||||||
pinnedLinks Link[]
|
pinnedLinks Link[]
|
||||||
collectionsJoined UsersAndCollections[]
|
collectionsJoined UsersAndCollections[]
|
||||||
|
collectionOrder Int[] @default([])
|
||||||
whitelistedUsers WhitelistedUser[]
|
whitelistedUsers WhitelistedUser[]
|
||||||
accessTokens AccessToken[]
|
accessTokens AccessToken[]
|
||||||
subscriptions Subscription?
|
subscriptions Subscription?
|
||||||
|
|
|
@ -33,6 +33,7 @@ export interface CollectionIncludingMembersAndLinkCount
|
||||||
id?: number;
|
id?: number;
|
||||||
ownerId?: number;
|
ownerId?: number;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
_count?: { links: number };
|
_count?: { links: number };
|
||||||
members: Member[];
|
members: Member[];
|
||||||
}
|
}
|
||||||
|
|
148
yarn.lock
148
yarn.lock
|
@ -12,6 +12,15 @@
|
||||||
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
||||||
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
||||||
|
|
||||||
|
"@atlaskit/tree@^8.8.7":
|
||||||
|
version "8.8.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@atlaskit/tree/-/tree-8.8.7.tgz#f895137b063f676a490abb0b5deb939a96f51fd7"
|
||||||
|
integrity sha512-ftbFCzZoa5tZh35EdwMEP9lPuBfw19vtB1CcBmDDMP0AnyEXLjUVfVo8kIls6oI4wivYfIWkZgrUlgN+Jk1b0Q==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.0.0"
|
||||||
|
css-box-model "^1.2.0"
|
||||||
|
react-beautiful-dnd-next "11.0.5"
|
||||||
|
|
||||||
"@auth/core@0.9.0":
|
"@auth/core@0.9.0":
|
||||||
version "0.9.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.9.0.tgz#7a5d66eea0bc059cef072734698547ae2a0c86a6"
|
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.9.0.tgz#7a5d66eea0bc059cef072734698547ae2a0c86a6"
|
||||||
|
@ -614,6 +623,21 @@
|
||||||
chalk "^2.0.0"
|
chalk "^2.0.0"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
|
"@babel/runtime-corejs2@^7.4.5":
|
||||||
|
version "7.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.24.0.tgz#23c12d76ac8a7a0ec223c4b0c3b937f9c203fa33"
|
||||||
|
integrity sha512-RZVGq1it0GA1K8rb+z7v7NzecP6VYCMedN7yHsCCIQUMmRXFCPJD8GISdf6uIGj7NDDihg7ieQEzpdpQbUL75Q==
|
||||||
|
dependencies:
|
||||||
|
core-js "^2.6.12"
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.0.0":
|
||||||
|
version "7.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e"
|
||||||
|
integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
|
"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
|
||||||
version "7.21.5"
|
version "7.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
|
||||||
|
@ -628,6 +652,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
|
||||||
|
version "7.23.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||||
|
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.21.0":
|
"@babel/runtime@^7.21.0":
|
||||||
version "7.23.6"
|
version "7.23.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
|
||||||
|
@ -1925,6 +1956,14 @@
|
||||||
"@types/minimatch" "*"
|
"@types/minimatch" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/hoist-non-react-statics@^3.3.0":
|
||||||
|
version "3.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
|
||||||
|
integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
"@types/jsdom@^21.1.3":
|
"@types/jsdom@^21.1.3":
|
||||||
version "21.1.3"
|
version "21.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.3.tgz#a88c5dc65703e1b10b2a7839c12db49662b43ff0"
|
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.3.tgz#a88c5dc65703e1b10b2a7839c12db49662b43ff0"
|
||||||
|
@ -1986,6 +2025,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||||
|
|
||||||
|
"@types/react-beautiful-dnd@^13.1.8":
|
||||||
|
version "13.1.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz#f52d3ea07e1e19159d6c3c4a48c8da3d855e60b4"
|
||||||
|
integrity sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-dom@18.2.7":
|
"@types/react-dom@18.2.7":
|
||||||
version "18.2.7"
|
version "18.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
|
||||||
|
@ -1993,6 +2039,16 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-redux@^7.1.20":
|
||||||
|
version "7.1.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15"
|
||||||
|
integrity sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==
|
||||||
|
dependencies:
|
||||||
|
"@types/hoist-non-react-statics" "^3.3.0"
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
redux "^4.0.0"
|
||||||
|
|
||||||
"@types/react-transition-group@^4.4.0":
|
"@types/react-transition-group@^4.4.0":
|
||||||
version "4.4.5"
|
version "4.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
|
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
|
||||||
|
@ -2608,6 +2664,11 @@ cookie@0.5.0, cookie@^0.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||||
|
|
||||||
|
core-js@^2.6.12:
|
||||||
|
version "2.6.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
||||||
|
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||||
|
|
||||||
core-util-is@1.0.2:
|
core-util-is@1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
@ -2643,6 +2704,13 @@ crypto-js@^4.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
||||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||||
|
|
||||||
|
css-box-model@^1.1.2, css-box-model@^1.2.0:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||||
|
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||||
|
dependencies:
|
||||||
|
tiny-invariant "^1.0.6"
|
||||||
|
|
||||||
css-selector-tokenizer@^0.8:
|
css-selector-tokenizer@^0.8:
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd"
|
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd"
|
||||||
|
@ -3725,7 +3793,7 @@ himalaya@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/himalaya/-/himalaya-1.1.0.tgz#31724ae9d35714cd7c6f4be94888953f3604606a"
|
resolved "https://registry.yarnpkg.com/himalaya/-/himalaya-1.1.0.tgz#31724ae9d35714cd7c6f4be94888953f3604606a"
|
||||||
integrity sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw==
|
integrity sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw==
|
||||||
|
|
||||||
hoist-non-react-statics@^3.3.1:
|
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||||
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==
|
||||||
|
@ -4312,6 +4380,11 @@ make-error@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||||
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||||
|
|
||||||
|
memoize-one@^5.0.4, memoize-one@^5.1.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||||
|
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||||
|
|
||||||
memoize-one@^6.0.0:
|
memoize-one@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||||
|
@ -4984,7 +5057,7 @@ process@^0.11.10:
|
||||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||||
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||||
|
|
||||||
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1:
|
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
|
@ -5035,6 +5108,11 @@ queue-microtask@^1.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
raf-schd@^4.0.0, raf-schd@^4.0.2:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||||
|
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||||
|
|
||||||
raw-body@2.4.1:
|
raw-body@2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
||||||
|
@ -5045,6 +5123,33 @@ raw-body@2.4.1:
|
||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
react-beautiful-dnd-next@11.0.5:
|
||||||
|
version "11.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-beautiful-dnd-next/-/react-beautiful-dnd-next-11.0.5.tgz#41e693733bbdeb6269b9e4b923a36de2e99ed761"
|
||||||
|
integrity sha512-kM5Mob41HkA3ShS9uXqeMkW51L5bVsfttxfrwwHucu7I6SdnRKCyN78t6QiLH/UJQQ8T4ukI6NeQAQQpGwolkg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime-corejs2" "^7.4.5"
|
||||||
|
css-box-model "^1.1.2"
|
||||||
|
memoize-one "^5.0.4"
|
||||||
|
raf-schd "^4.0.0"
|
||||||
|
react-redux "^7.0.3"
|
||||||
|
redux "^4.0.1"
|
||||||
|
tiny-invariant "^1.0.4"
|
||||||
|
use-memo-one "^1.1.0"
|
||||||
|
|
||||||
|
react-beautiful-dnd@^13.1.1:
|
||||||
|
version "13.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
|
||||||
|
integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
css-box-model "^1.2.0"
|
||||||
|
memoize-one "^5.1.1"
|
||||||
|
raf-schd "^4.0.2"
|
||||||
|
react-redux "^7.2.0"
|
||||||
|
redux "^4.0.4"
|
||||||
|
use-memo-one "^1.1.1"
|
||||||
|
|
||||||
react-colorful@^5.6.1:
|
react-colorful@^5.6.1:
|
||||||
version "5.6.1"
|
version "5.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
|
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
|
||||||
|
@ -5075,6 +5180,23 @@ react-is@^16.13.1, react-is@^16.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
|
react-is@^17.0.2:
|
||||||
|
version "17.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|
||||||
|
react-redux@^7.0.3, react-redux@^7.2.0:
|
||||||
|
version "7.2.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
|
||||||
|
integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.15.4"
|
||||||
|
"@types/react-redux" "^7.1.20"
|
||||||
|
hoist-non-react-statics "^3.3.2"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^17.0.2"
|
||||||
|
|
||||||
react-remove-scroll-bar@^2.3.3:
|
react-remove-scroll-bar@^2.3.3:
|
||||||
version "2.3.4"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9"
|
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9"
|
||||||
|
@ -5165,6 +5287,13 @@ readdirp@~3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
|
redux@^4.0.0, redux@^4.0.1, redux@^4.0.4:
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||||
|
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
|
||||||
regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3:
|
regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3:
|
||||||
version "0.13.11"
|
version "0.13.11"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||||
|
@ -5688,6 +5817,16 @@ tiny-glob@^0.2.9:
|
||||||
globalyzer "0.1.0"
|
globalyzer "0.1.0"
|
||||||
globrex "^0.1.2"
|
globrex "^0.1.2"
|
||||||
|
|
||||||
|
tiny-invariant@^1.0.4:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
||||||
|
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
||||||
|
|
||||||
|
tiny-invariant@^1.0.6:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
|
||||||
|
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
|
||||||
|
|
||||||
tinycolor2@^1.6.0:
|
tinycolor2@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
|
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
|
||||||
|
@ -5924,6 +6063,11 @@ use-isomorphic-layout-effect@^1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
|
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
|
||||||
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
|
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
|
||||||
|
|
||||||
|
use-memo-one@^1.1.0, use-memo-one@^1.1.1:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
|
||||||
|
integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
|
||||||
|
|
||||||
use-sidecar@^1.1.2:
|
use-sidecar@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
|
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
|
||||||
|
|
Ŝarĝante…
Reference in New Issue