mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 00:27:01 +00:00
* build(deps): bump the npm_and_yarn group across 5 directories with 22 updates Bumps the npm_and_yarn group with 18 updates in the / directory: | Package | From | To | | --- | --- | --- | | [axios](https://github.com/axios/axios) | `1.5.1` | `1.12.0` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.0.6` | `3.2.4` | | [formidable](https://github.com/node-formidable/formidable) | `3.5.1` | `3.5.4` | | [next](https://github.com/vercel/next.js) | `13.4.12` | `14.2.35` | | [next-auth](https://github.com/nextauthjs/next-auth) | `4.22.1` | `4.24.12` | | [playwright](https://github.com/microsoft/playwright) | `1.55.0` | `1.55.1` | | [@mozilla/readability](https://github.com/mozilla/readability) | `0.4.4` | `0.6.0` | | [ai](https://github.com/vercel/ai) | `4.3.9` | `5.0.52` | | [nodemailer](https://github.com/nodemailer/nodemailer) | `6.9.3` | `7.0.11` | | [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.11` | `1.1.12` | | [braces](https://github.com/micromatch/braces) | `3.0.2` | `3.0.3` | | [form-data](https://github.com/form-data/form-data) | `3.0.3` | `3.0.4` | | [js-yaml](https://github.com/nodeca/js-yaml) | `3.14.1` | `3.14.2` | | [micromatch](https://github.com/micromatch/micromatch) | `4.0.5` | `4.0.8` | | [min-document](https://github.com/Raynos/min-document) | `2.19.0` | `2.19.2` | | [nanoid](https://github.com/ai/nanoid) | `3.3.6` | `3.3.8` | | [node-forge](https://github.com/digitalbazaar/forge) | `1.3.1` | `1.3.3` | | [tar](https://github.com/isaacs/node-tar) | `6.1.13` | `6.2.1` | Bumps the npm_and_yarn group with 1 update in the /apps/web directory: [next](https://github.com/vercel/next.js). Bumps the npm_and_yarn group with 2 updates in the /apps/worker directory: [@mozilla/readability](https://github.com/mozilla/readability) and [ai](https://github.com/vercel/ai). Bumps the npm_and_yarn group with 1 update in the /packages/lib directory: [nodemailer](https://github.com/nodemailer/nodemailer). Bumps the npm_and_yarn group with 1 update in the /packages/router directory: [next](https://github.com/vercel/next.js). Updates `axios` from 1.5.1 to 1.12.0 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.5.1...v1.12.0) Updates `dompurify` from 3.0.6 to 3.2.4 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.0.6...3.2.4) Updates `formidable` from 3.5.1 to 3.5.4 - [Release notes](https://github.com/node-formidable/formidable/releases) - [Changelog](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md) - [Commits](https://github.com/node-formidable/formidable/commits) Updates `next` from 13.4.12 to 14.2.35 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.12...v14.2.35) Updates `next-auth` from 4.22.1 to 4.24.12 - [Release notes](https://github.com/nextauthjs/next-auth/releases) - [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.22.1...next-auth@4.24.12) Updates `playwright` from 1.55.0 to 1.55.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.55.0...v1.55.1) Updates `postcss` from 8.4.26 to 8.5.3 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.26...8.5.3) Updates `@mozilla/readability` from 0.4.4 to 0.6.0 - [Changelog](https://github.com/mozilla/readability/blob/main/CHANGELOG.md) - [Commits](https://github.com/mozilla/readability/compare/0.4.4...0.6.0) Updates `ai` from 4.3.9 to 5.0.52 - [Release notes](https://github.com/vercel/ai/releases) - [Changelog](https://github.com/vercel/ai/blob/main/CHANGELOG.md) - [Commits](https://github.com/vercel/ai/compare/ai@4.3.9...ai@5.0.52) Updates `nodemailer` from 6.9.3 to 7.0.11 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.3...v7.0.11) Updates `@babel/runtime` from 7.21.5 to 7.27.0 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime) Updates `brace-expansion` from 1.1.11 to 1.1.12 - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12) Updates `braces` from 3.0.2 to 3.0.3 - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) Updates `follow-redirects` from 1.15.3 to 1.15.11 - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.11) Updates `form-data` from 3.0.3 to 3.0.4 - [Release notes](https://github.com/form-data/form-data/releases) - [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v3.0.3...v3.0.4) Updates `jose` from 4.14.4 to 4.15.9 - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/v4.15.9/CHANGELOG.md) - [Commits](https://github.com/panva/jose/compare/v4.14.4...v4.15.9) Updates `js-yaml` from 3.14.1 to 3.14.2 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2) Updates `micromatch` from 4.0.5 to 4.0.8 - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) Updates `min-document` from 2.19.0 to 2.19.2 - [Commits](https://github.com/Raynos/min-document/compare/v2.19.0...v2.19.2) Updates `nanoid` from 3.3.6 to 3.3.8 - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.6...3.3.8) Updates `node-forge` from 1.3.1 to 1.3.3 - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.3) Updates `tar` from 6.1.13 to 6.2.1 - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v6.1.13...v6.2.1) Updates `next` from 13.4.12 to 14.2.35 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.12...v14.2.35) Updates `@mozilla/readability` from 0.4.4 to 0.6.0 - [Changelog](https://github.com/mozilla/readability/blob/main/CHANGELOG.md) - [Commits](https://github.com/mozilla/readability/compare/0.4.4...0.6.0) Updates `ai` from 4.3.19 to 5.0.113 - [Release notes](https://github.com/vercel/ai/releases) - [Changelog](https://github.com/vercel/ai/blob/main/CHANGELOG.md) - [Commits](https://github.com/vercel/ai/compare/ai@4.3.9...ai@5.0.52) Updates `nodemailer` from 6.10.1 to 7.0.11 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.3...v7.0.11) Updates `next` from 13.4.12 to 14.2.35 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.12...v14.2.35) --- updated-dependencies: - dependency-name: axios dependency-version: 1.12.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: dompurify dependency-version: 3.2.4 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: formidable dependency-version: 3.5.4 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: next dependency-version: 14.2.35 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: next-auth dependency-version: 4.24.12 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: playwright dependency-version: 1.55.1 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: postcss dependency-version: 8.5.3 dependency-type: direct:development dependency-group: npm_and_yarn - dependency-name: "@mozilla/readability" dependency-version: 0.6.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: ai dependency-version: 5.0.52 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: nodemailer dependency-version: 7.0.11 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: "@babel/runtime" dependency-version: 7.27.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: brace-expansion dependency-version: 1.1.12 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: braces dependency-version: 3.0.3 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: follow-redirects dependency-version: 1.15.11 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: form-data dependency-version: 3.0.4 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: jose dependency-version: 4.15.9 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: micromatch dependency-version: 4.0.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: min-document dependency-version: 2.19.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: nanoid dependency-version: 3.3.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: node-forge dependency-version: 1.3.3 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: tar dependency-version: 6.2.1 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: next dependency-version: 14.2.35 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: "@mozilla/readability" dependency-version: 0.6.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: ai dependency-version: 5.0.113 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: nodemailer dependency-version: 7.0.11 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: next dependency-version: 14.2.35 dependency-type: direct:production dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> * bug fixes and improvements * always show navbar in reader view * bug fix and small performance improvement * minor fix * Refactor link selection management and bulk actions - Replaced the use of selectedLinks with selectedIds in the link store for better performance and clarity. - Updated LinkListOptions, BulkDeleteLinksModal, and BulkEditLinksModal components to utilize the new selection management. - Modified LinkCard, LinkMasonry, and LinkList components to handle selection state through props. - Enhanced updateLinks API to support bulk updates with improved tag management. - Cleaned up unused imports and code related to previous selection methods. * move refetching logic to Links component * move disableDraggable and user hook out of each card to improve efficiency * cleaner code * memoize components and increase performance * fix: update announcement links to use the correct domain * feat: add favicon field to Link model + update packages + bug fix * feat: implement favicon fetching API and update Link model for favicon support * feat: add priority attribute to Image components in Sidebar * Refactor pages to use consistent layout handling (yes, I forgot to do that until now :P) * bump version * Refactor setting pages to use consistent layout handling * upgrade yarn to 4.12.0 * fix DnD bug * Enhance announcement handling by adding support for announcement messages * slimmed down the docker image size * update Node and yarn versions in playwright tests workflow * small fix * fix attempt 2 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
498 lines
14 KiB
TypeScript
498 lines
14 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
|
import Tree, {
|
|
mutateTree,
|
|
moveItemOnTree,
|
|
RenderItemParams,
|
|
TreeItem,
|
|
TreeData,
|
|
ItemId,
|
|
TreeSourcePosition,
|
|
TreeDestinationPosition,
|
|
} from "@atlaskit/tree";
|
|
import { Collection } from "@linkwarden/prisma/client";
|
|
import Link from "next/link";
|
|
import { CollectionIncludingMembersAndLinkCount } from "@linkwarden/types";
|
|
import { useRouter } from "next/router";
|
|
import toast from "react-hot-toast";
|
|
import { useTranslation } from "next-i18next";
|
|
import {
|
|
useCollections,
|
|
useUpdateCollection,
|
|
} from "@linkwarden/router/collections";
|
|
import { useUpdateUser, useUser } from "@linkwarden/router/user";
|
|
import Icon from "./Icon";
|
|
import { IconWeight } from "@phosphor-icons/react";
|
|
import Droppable from "./Droppable";
|
|
import { cn } from "@linkwarden/lib";
|
|
import { Active, useDndContext } from "@dnd-kit/core";
|
|
|
|
interface ExtendedTreeItem extends TreeItem {
|
|
data: Collection;
|
|
}
|
|
|
|
const CollectionListing = () => {
|
|
const { active: droppableActive } = useDndContext();
|
|
const { t } = useTranslation();
|
|
const updateCollection = useUpdateCollection();
|
|
const { data: collections = [], isLoading } = useCollections();
|
|
|
|
const { data: user } = useUser();
|
|
const updateUser = useUpdateUser();
|
|
|
|
const router = useRouter();
|
|
|
|
const [tree, setTree] = useState<TreeData | undefined>();
|
|
|
|
const initialTree = useMemo(() => {
|
|
if (collections.length > 0) {
|
|
return buildTreeFromCollections(
|
|
collections,
|
|
router,
|
|
tree,
|
|
user?.collectionOrder
|
|
);
|
|
} else return undefined;
|
|
}, [collections, user]);
|
|
|
|
useEffect(() => {
|
|
setTree(initialTree);
|
|
}, [initialTree]);
|
|
|
|
useEffect(() => {
|
|
if (user?.username) {
|
|
// refetch();
|
|
if (
|
|
(!user.collectionOrder || user.collectionOrder.length === 0) &&
|
|
collections.length > 0
|
|
)
|
|
updateUser.mutate({
|
|
...user,
|
|
collectionOrder: collections
|
|
.filter((e) => e.parentId === null)
|
|
.map((e) => e.id as number),
|
|
});
|
|
else {
|
|
const newCollectionOrder: number[] = [...(user.collectionOrder || [])];
|
|
|
|
// Start with collections that are in both account.collectionOrder and collections
|
|
const existingCollectionIds = collections.map((c) => c.id as number);
|
|
const filteredCollectionOrder = user.collectionOrder.filter((id: any) =>
|
|
existingCollectionIds.includes(id)
|
|
);
|
|
|
|
// Add new collections that are not in account.collectionOrder and meet the specific conditions
|
|
collections.forEach((collection) => {
|
|
if (
|
|
!filteredCollectionOrder.includes(collection.id as number) &&
|
|
(!collection.parentId || collection.ownerId === user.id)
|
|
) {
|
|
filteredCollectionOrder.push(collection.id as number);
|
|
}
|
|
});
|
|
|
|
// check if the newCollectionOrder is the same as the old one
|
|
if (
|
|
JSON.stringify(newCollectionOrder) !==
|
|
JSON.stringify(user.collectionOrder)
|
|
) {
|
|
updateUser.mutateAsync({
|
|
...user,
|
|
collectionOrder: newCollectionOrder,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}, [user, collections]);
|
|
|
|
const onExpand = (movedCollectionId: ItemId) => {
|
|
setTree((currentTree) =>
|
|
mutateTree(currentTree!, movedCollectionId, { isExpanded: true })
|
|
);
|
|
};
|
|
|
|
const onCollapse = (movedCollectionId: ItemId) => {
|
|
setTree((currentTree) =>
|
|
mutateTree(currentTree as TreeData, movedCollectionId, {
|
|
isExpanded: false,
|
|
})
|
|
);
|
|
};
|
|
|
|
function reorderTreeItems(
|
|
tree: TreeData,
|
|
movedCollectionId: ItemId,
|
|
source: TreeSourcePosition,
|
|
destination: TreeDestinationPosition
|
|
) {
|
|
// Same parent reordering
|
|
if (source.parentId === destination.parentId) {
|
|
const parent = tree.items[source.parentId];
|
|
const children = [...parent.children];
|
|
|
|
// Remove from source index
|
|
children.splice(source.index, 1);
|
|
// Insert at destination index
|
|
if (destination.index !== undefined) {
|
|
children.splice(destination.index, 0, movedCollectionId);
|
|
}
|
|
|
|
parent.children = children;
|
|
return tree;
|
|
}
|
|
|
|
// Different parent move
|
|
const sourceParent = tree.items[source.parentId];
|
|
const destinationParent = tree.items[destination.parentId];
|
|
|
|
// Remove from source parent
|
|
sourceParent.children = sourceParent.children.filter(
|
|
(id) => id !== movedCollectionId
|
|
);
|
|
|
|
// Initialize children array if it doesn't exist
|
|
if (!destinationParent.children) {
|
|
destinationParent.children = [];
|
|
}
|
|
|
|
// If destination index is not specified, add to the end
|
|
const destinationIndex =
|
|
destination.index !== undefined
|
|
? destination.index
|
|
: destinationParent.children.length;
|
|
|
|
// Add to destination parent
|
|
destinationParent.children.splice(destinationIndex, 0, movedCollectionId);
|
|
|
|
// Update destination parent properties
|
|
destinationParent.hasChildren = true;
|
|
destinationParent.isExpanded = true;
|
|
|
|
// Update the moved item's parent ID
|
|
tree.items[movedCollectionId].data.parentId = destination.parentId;
|
|
|
|
return tree;
|
|
}
|
|
|
|
function flattenTreeIds(
|
|
tree: TreeData,
|
|
nodeId: ItemId = "root",
|
|
result: Array<ItemId> = []
|
|
) {
|
|
const node = tree.items[nodeId];
|
|
|
|
if (nodeId !== "root") {
|
|
result.push(node.id);
|
|
}
|
|
|
|
if (node.children && node.children.length > 0) {
|
|
node.children.forEach((childId) => {
|
|
flattenTreeIds(tree, childId, result);
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const onDragEnd = async (
|
|
source: TreeSourcePosition,
|
|
destination: TreeDestinationPosition | undefined
|
|
) => {
|
|
if (!destination || !tree) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
source.index === destination.index &&
|
|
source.parentId === destination.parentId
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const movedCollectionId = Number(
|
|
tree.items[source.parentId].children[source.index]
|
|
);
|
|
|
|
const movedCollection = collections.find((c) => c.id === movedCollectionId);
|
|
|
|
const destinationCollection = collections.find(
|
|
(c) => c.id === Number(destination.parentId)
|
|
);
|
|
|
|
if (
|
|
(movedCollection?.ownerId !== user?.id &&
|
|
destination.parentId !== source.parentId) ||
|
|
(destinationCollection?.ownerId !== user?.id &&
|
|
destination.parentId !== "root")
|
|
) {
|
|
return toast.error(t("cant_change_collection_you_dont_own"));
|
|
}
|
|
|
|
setTree((currentTree) => moveItemOnTree(currentTree!, source, destination));
|
|
|
|
const newTree = reorderTreeItems(
|
|
tree,
|
|
movedCollectionId,
|
|
source,
|
|
destination
|
|
);
|
|
|
|
if (source.parentId !== destination.parentId) {
|
|
await updateCollection.mutateAsync(
|
|
{
|
|
...movedCollection,
|
|
parentId:
|
|
destination.parentId && destination.parentId !== "root"
|
|
? Number(destination.parentId)
|
|
: destination.parentId === "root"
|
|
? "root"
|
|
: null,
|
|
},
|
|
{
|
|
onError: (error) => {
|
|
toast.error(error.message);
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
await updateUser.mutateAsync({
|
|
...user,
|
|
collectionOrder: flattenTreeIds(newTree),
|
|
});
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="skeleton h-4 w-full"></div>
|
|
<div className="skeleton h-4 w-full"></div>
|
|
<div className="skeleton h-4 w-full"></div>
|
|
</div>
|
|
);
|
|
} else if (!tree) {
|
|
return (
|
|
<p className="text-neutral text-xs font-semibold truncate w-full px-2 mt-5 mb-8">
|
|
{t("you_have_no_collections")}
|
|
</p>
|
|
);
|
|
} else
|
|
return (
|
|
<Tree
|
|
tree={tree}
|
|
renderItem={(itemProps) =>
|
|
renderItem({ ...itemProps }, router.asPath, droppableActive)
|
|
}
|
|
onExpand={onExpand}
|
|
onCollapse={onCollapse}
|
|
onDragEnd={onDragEnd}
|
|
isDragEnabled
|
|
isNestingEnabled
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default CollectionListing;
|
|
|
|
const renderItem = (
|
|
{ item, onExpand, onCollapse, provided }: RenderItemParams,
|
|
currentPath: string,
|
|
droppableActive: Active | null
|
|
) => {
|
|
const collection = item.data;
|
|
|
|
return (
|
|
<Droppable
|
|
id={`side-bar-collection-${collection.id}`}
|
|
data={{
|
|
name: collection.name,
|
|
id: collection.id,
|
|
ownerId: collection.ownerId,
|
|
}}
|
|
className="group"
|
|
>
|
|
<div
|
|
ref={provided.innerRef}
|
|
{...provided.draggableProps}
|
|
className="mb-1"
|
|
>
|
|
<div
|
|
className={cn(
|
|
currentPath === `/collections/${collection.id}`
|
|
? "bg-primary/20 is-active"
|
|
: droppableActive
|
|
? "select-none"
|
|
: "hover:bg-neutral/20",
|
|
"duration-100 flex gap-1 items-center pr-2 pl-1 rounded-md"
|
|
)}
|
|
>
|
|
{Dropdown(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`}
|
|
>
|
|
{collection.icon ? (
|
|
<Icon
|
|
icon={collection.icon}
|
|
size={30}
|
|
weight={(collection.iconWeight || "regular") as IconWeight}
|
|
color={collection.color}
|
|
className="-mr-[0.15rem]"
|
|
/>
|
|
) : (
|
|
<i
|
|
className="bi-folder-fill text-xl"
|
|
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>
|
|
)}
|
|
<div className="drop-shadow text-neutral text-xs">
|
|
{collection._count?.links}
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</Droppable>
|
|
);
|
|
};
|
|
|
|
const Dropdown = (
|
|
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></div>;
|
|
};
|
|
|
|
const buildTreeFromCollections = (
|
|
collections: CollectionIncludingMembersAndLinkCount[],
|
|
router: ReturnType<typeof useRouter>,
|
|
tree?: TreeData,
|
|
order?: number[]
|
|
): TreeData => {
|
|
if (order) {
|
|
collections.sort((a: any, b: any) => {
|
|
return order.indexOf(a.id) - order.indexOf(b.id);
|
|
});
|
|
}
|
|
|
|
function getTotalLinkCount(collectionId: number): number {
|
|
const collection = items[collectionId];
|
|
if (!collection) {
|
|
return 0;
|
|
}
|
|
|
|
let totalLinkCount = (collection.data as any)._count?.links || 0;
|
|
|
|
if (collection.hasChildren) {
|
|
collection.children.forEach((childId) => {
|
|
totalLinkCount += getTotalLinkCount(childId as number);
|
|
});
|
|
}
|
|
|
|
return totalLinkCount;
|
|
}
|
|
|
|
const items: { [key: string]: ExtendedTreeItem } = collections.reduce(
|
|
(acc: any, collection) => {
|
|
acc[collection.id as number] = {
|
|
id: collection.id,
|
|
children: [],
|
|
hasChildren: false,
|
|
isExpanded: tree?.items[collection.id as number]?.isExpanded || false,
|
|
data: {
|
|
id: collection.id,
|
|
parentId: collection.parentId,
|
|
name: collection.name,
|
|
description: collection.description,
|
|
color: collection.color,
|
|
icon: collection.icon,
|
|
iconWeight: collection.iconWeight,
|
|
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]) {
|
|
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;
|
|
}
|
|
});
|
|
|
|
collections.forEach((collection) => {
|
|
const collectionId = collection.id;
|
|
if (items[collectionId as number] && collection.id) {
|
|
const linkCount = getTotalLinkCount(collectionId as number);
|
|
(items[collectionId as number].data as any)._count.links = linkCount;
|
|
}
|
|
});
|
|
|
|
const rootId = "root";
|
|
items[rootId] = {
|
|
id: rootId,
|
|
children: (collections
|
|
.filter(
|
|
(c) =>
|
|
c.parentId === null || !collections.find((i) => i.id === c.parentId)
|
|
)
|
|
.map((c) => c.id) || "") as unknown as string[],
|
|
hasChildren: true,
|
|
isExpanded: true,
|
|
data: { name: "Root" } as Collection,
|
|
};
|
|
|
|
return { rootId, items };
|
|
};
|