Merge pull request #636 from bjoerndot/tags-in-public-collection
Tags in public collection
This commit is contained in:
commit
d12d12518e
|
@ -0,0 +1,30 @@
|
||||||
|
import { Tag } from "@prisma/client";
|
||||||
|
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
type TagIncludingCount = Tag & { _count: { links: number } };
|
||||||
|
|
||||||
|
const usePublicTags = (): UseQueryResult<TagIncludingCount[]> => {
|
||||||
|
const { status } = useSession();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["tags"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(
|
||||||
|
"/api/v1/public/collections/tags" +
|
||||||
|
"?collectionId=" +
|
||||||
|
router.query.id || ""
|
||||||
|
);
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch tags.");
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.response;
|
||||||
|
},
|
||||||
|
enabled: status === "authenticated",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { usePublicTags };
|
|
@ -1,7 +1,14 @@
|
||||||
import { prisma } from "@/lib/api/db";
|
import { prisma } from "@/lib/api/db";
|
||||||
|
|
||||||
export default async function getTags(userId: number) {
|
export default async function getTags({
|
||||||
|
userId,
|
||||||
|
collectionId,
|
||||||
|
}: {
|
||||||
|
userId?: number;
|
||||||
|
collectionId?: number;
|
||||||
|
}) {
|
||||||
// Remove empty tags
|
// Remove empty tags
|
||||||
|
if (userId)
|
||||||
await prisma.tag.deleteMany({
|
await prisma.tag.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
|
@ -28,6 +35,13 @@ export default async function getTags(userId: number) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
links: {
|
||||||
|
some: {
|
||||||
|
collectionId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import getTags from "@/lib/api/controllers/tags/getTags";
|
||||||
|
import { prisma } from "@/lib/api/db";
|
||||||
|
import { LinkRequestQuery } from "@/types/global";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
export default async function collections(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
if (req.method === "GET") {
|
||||||
|
// Convert the type of the request query to "LinkRequestQuery"
|
||||||
|
const convertedData: Omit<LinkRequestQuery, "tagId"> = {
|
||||||
|
sort: Number(req.query.sort as string),
|
||||||
|
collectionId: req.query.collectionId
|
||||||
|
? Number(req.query.collectionId as string)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!convertedData.collectionId) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ response: "Please choose a valid collection." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = await prisma.collection.findFirst({
|
||||||
|
where: {
|
||||||
|
id: convertedData.collectionId,
|
||||||
|
isPublic: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return res.status(404).json({ response: "Collection not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = await getTags({
|
||||||
|
collectionId: collection.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(tags.status).json({ response: tags.response });
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,9 @@ export default async function tags(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
const tags = await getTags(user.id);
|
const tags = await getTags({
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
return res.status(tags.status).json({ response: tags.response });
|
return res.status(tags.status).json({ response: tags.response });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import getServerSideProps from "@/lib/client/getServerSideProps";
|
||||||
import LinkListOptions from "@/components/LinkListOptions";
|
import LinkListOptions from "@/components/LinkListOptions";
|
||||||
import { usePublicLinks } from "@/hooks/store/publicLinks";
|
import { usePublicLinks } from "@/hooks/store/publicLinks";
|
||||||
import Links from "@/components/LinkViews/Links";
|
import Links from "@/components/LinkViews/Links";
|
||||||
|
import { usePublicTags } from "@/hooks/store/publicTags";
|
||||||
|
|
||||||
export default function PublicCollections() {
|
export default function PublicCollections() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -34,6 +35,32 @@ export default function PublicCollections() {
|
||||||
Partial<AccountSettings>
|
Partial<AccountSettings>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
|
const handleTagSelection = (tag: string | undefined) => {
|
||||||
|
if (tag) {
|
||||||
|
Object.keys(searchFilter).forEach(
|
||||||
|
(v) =>
|
||||||
|
(searchFilter[
|
||||||
|
v as keyof {
|
||||||
|
name: boolean;
|
||||||
|
url: boolean;
|
||||||
|
description: boolean;
|
||||||
|
tags: boolean;
|
||||||
|
textContent: boolean;
|
||||||
|
}
|
||||||
|
] = false)
|
||||||
|
);
|
||||||
|
searchFilter.tags = true;
|
||||||
|
return router.push(
|
||||||
|
"/public/collections/" +
|
||||||
|
router.query.id +
|
||||||
|
"?q=" +
|
||||||
|
encodeURIComponent(tag || "")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return router.push("/public/collections/" + router.query.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [searchFilter, setSearchFilter] = useState({
|
const [searchFilter, setSearchFilter] = useState({
|
||||||
name: true,
|
name: true,
|
||||||
url: true,
|
url: true,
|
||||||
|
@ -46,6 +73,8 @@ export default function PublicCollections() {
|
||||||
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
|
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: tags } = usePublicTags();
|
||||||
|
|
||||||
const { links, data } = usePublicLinks({
|
const { links, data } = usePublicLinks({
|
||||||
sort: sortBy,
|
sort: sortBy,
|
||||||
searchQueryString: router.query.q
|
searchQueryString: router.query.q
|
||||||
|
@ -57,10 +86,8 @@ export default function PublicCollections() {
|
||||||
searchByTextContent: searchFilter.textContent,
|
searchByTextContent: searchFilter.textContent,
|
||||||
searchByTags: searchFilter.tags,
|
searchByTags: searchFilter.tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [collection, setCollection] =
|
const [collection, setCollection] =
|
||||||
useState<CollectionIncludingMembersAndLinkCount>();
|
useState<CollectionIncludingMembersAndLinkCount>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.query.id) {
|
if (router.query.id) {
|
||||||
getPublicCollectionData(Number(router.query.id)).then((res) => {
|
getPublicCollectionData(Number(router.query.id)).then((res) => {
|
||||||
|
@ -212,7 +239,56 @@ export default function PublicCollections() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</LinkListOptions>
|
</LinkListOptions>
|
||||||
|
{tags && tags[0] && (
|
||||||
|
<div className="flex gap-2 mt-2 mb-6 flex-wrap">
|
||||||
|
<button
|
||||||
|
className="max-w-full"
|
||||||
|
onClick={() => handleTagSelection(undefined)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
!router.query.q
|
||||||
|
? "bg-primary/20"
|
||||||
|
: "bg-neutral-content/20 hover:bg-neutral/20"
|
||||||
|
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 rounded-md h-8`}
|
||||||
|
>
|
||||||
|
<i className="text-primary bi-hash text-2xl drop-shadow"></i>
|
||||||
|
<p className="truncate pr-7">{t("all_links")}</p>
|
||||||
|
<div className="text-neutral drop-shadow text-xs">
|
||||||
|
{collection._count?.links}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{tags
|
||||||
|
.map((t) => t.name)
|
||||||
|
.filter((item, pos, self) => self.indexOf(item) === pos)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
.map((e, i) => {
|
||||||
|
const active = router.query.q === e;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="max-w-full"
|
||||||
|
key={i}
|
||||||
|
onClick={() => handleTagSelection(e)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
active
|
||||||
|
? "bg-primary/20"
|
||||||
|
: "bg-neutral-content/20 hover:bg-neutral/20"
|
||||||
|
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 rounded-md h-8`}
|
||||||
|
>
|
||||||
|
<i className="bi-hash text-2xl text-primary drop-shadow"></i>
|
||||||
|
<p className="truncate pr-7">{e}</p>
|
||||||
|
<div className="drop-shadow text-neutral text-xs">
|
||||||
|
{tags.filter((t) => t.name === e)[0]._count.links}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Links
|
<Links
|
||||||
links={
|
links={
|
||||||
links?.map((e, i) => {
|
links?.map((e, i) => {
|
||||||
|
|
Ŝarĝante…
Reference in New Issue