minor changes
This commit is contained in:
parent
2e1e94112f
commit
4febe1ace5
|
@ -32,32 +32,18 @@ const usePublicLinks = (params: LinkRequestQuery = {}) => {
|
|||
searchByTags: params.searchByTags,
|
||||
} as LinkRequestQuery;
|
||||
|
||||
const queryParamsForAllLinksObject = {
|
||||
sort: params.sort ?? Number(window.localStorage.getItem("sortBy")) ?? 0,
|
||||
collectionId: params.collectionId ?? router.query.id,
|
||||
} as LinkRequestQuery;
|
||||
|
||||
const queryString = buildQueryString(queryParamsObject);
|
||||
const queryStringForAllLinkObject = buildQueryString(queryParamsForAllLinksObject);
|
||||
const { data, ...rest } = useFetchLinks(queryString);
|
||||
const allLinks = useFetchLinks(queryStringForAllLinkObject);
|
||||
const links = useMemo(() => {
|
||||
return data?.pages.reduce((acc, page) => {
|
||||
return [...acc, ...page];
|
||||
}, []);
|
||||
}, [data]);
|
||||
const linksForWholeCollection = useMemo(() => {
|
||||
return allLinks.data?.pages.reduce((acc, page) => {
|
||||
return [...acc, ...page];
|
||||
}, []);
|
||||
}, [allLinks.data])
|
||||
return {
|
||||
links,
|
||||
linksForWholeCollection,
|
||||
data: { ...data, ...rest },
|
||||
} as {
|
||||
links: LinkIncludingShortenedCollectionAndTags[];
|
||||
linksForWholeCollection: LinkIncludingShortenedCollectionAndTags[];
|
||||
data: UseInfiniteQueryResult<InfiniteData<any, unknown>, Error>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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 };
|
|
@ -2,7 +2,7 @@ import { prisma } from "@/lib/api/db";
|
|||
import { LinkRequestQuery, Order, Sort } from "@/types/global";
|
||||
|
||||
export default async function getLink(
|
||||
query: Omit<LinkRequestQuery, "tagId" | "pinnedOnly">, takeAll = false
|
||||
query: Omit<LinkRequestQuery, "tagId" | "pinnedOnly">
|
||||
) {
|
||||
const POSTGRES_IS_ENABLED =
|
||||
process.env.DATABASE_URL?.startsWith("postgresql");
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
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
|
||||
await prisma.tag.deleteMany({
|
||||
where: {
|
||||
ownerId: userId,
|
||||
links: {
|
||||
none: {},
|
||||
if (userId)
|
||||
await prisma.tag.deleteMany({
|
||||
where: {
|
||||
ownerId: userId,
|
||||
links: {
|
||||
none: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const tags = await prisma.tag.findMany({
|
||||
where: {
|
||||
|
@ -28,6 +35,13 @@ export default async function getTags(userId?: number) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
links: {
|
||||
some: {
|
||||
collectionId,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
import getPublicLinksUnderCollection from "@/lib/api/controllers/public/links/getPublicLinksUnderCollection";
|
||||
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";
|
||||
|
||||
|
@ -22,14 +22,21 @@ export default async function collections(
|
|||
.json({ response: "Please choose a valid collection." });
|
||||
}
|
||||
|
||||
const links = await getPublicLinksUnderCollection(convertedData, true);
|
||||
const tags = await getTags();
|
||||
const tagsInLinks = links.response.map(l => l.tags).flat().filter((value, index, self) =>
|
||||
index === self.findIndex((t) => (
|
||||
t.name === value.name
|
||||
))).map(t => t.id);
|
||||
const tagsWithCount = tags.response.filter(tag => tagsInLinks.includes(tag.id));
|
||||
const collection = await prisma.collection.findFirst({
|
||||
where: {
|
||||
id: convertedData.collectionId,
|
||||
isPublic: true,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(links.status).json({ response: tagsWithCount });
|
||||
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 (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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
AccountSettings,
|
||||
CollectionIncludingMembersAndLinkCount,
|
||||
Sort,
|
||||
TagIncludingLinkCount,
|
||||
ViewMode,
|
||||
} from "@/types/global";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -23,7 +22,7 @@ import getServerSideProps from "@/lib/client/getServerSideProps";
|
|||
import LinkListOptions from "@/components/LinkListOptions";
|
||||
import { usePublicLinks } from "@/hooks/store/publicLinks";
|
||||
import Links from "@/components/LinkViews/Links";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { usePublicTags } from "@/hooks/store/publicTags";
|
||||
|
||||
export default function PublicCollections() {
|
||||
const { t } = useTranslation();
|
||||
|
@ -74,7 +73,9 @@ export default function PublicCollections() {
|
|||
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
|
||||
);
|
||||
|
||||
const { links, linksForWholeCollection, data } = usePublicLinks({
|
||||
const { data: tags } = usePublicTags();
|
||||
|
||||
const { links, data } = usePublicLinks({
|
||||
sort: sortBy,
|
||||
searchQueryString: router.query.q
|
||||
? decodeURIComponent(router.query.q as string)
|
||||
|
@ -114,19 +115,6 @@ export default function PublicCollections() {
|
|||
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
|
||||
);
|
||||
|
||||
const [tagDisclosure, setTagDisclosure] = useState<boolean>(() => {
|
||||
const storedValue = localStorage.getItem(
|
||||
"tagDisclosureForPublicCollection" + collection?.id
|
||||
);
|
||||
return storedValue ? storedValue === "true" : true;
|
||||
});
|
||||
useEffect(() => {
|
||||
localStorage.setItem(
|
||||
"tagDisclosureForPublicCollection" + collection?.id,
|
||||
tagDisclosure ? "true" : "false"
|
||||
);
|
||||
}, [tagDisclosure]);
|
||||
|
||||
if (!collection) return <></>;
|
||||
else
|
||||
return (
|
||||
|
@ -251,84 +239,55 @@ export default function PublicCollections() {
|
|||
}
|
||||
/>
|
||||
</LinkListOptions>
|
||||
{linksForWholeCollection?.flatMap((l) => l.tags)[0] && (
|
||||
<Disclosure defaultOpen={tagDisclosure}>
|
||||
<Disclosure.Button
|
||||
onClick={() => {
|
||||
setTagDisclosure(!tagDisclosure);
|
||||
}}
|
||||
className="flex items-center justify-between w-full text-left mb-2 pl-2 font-bold text-neutral mt-5"
|
||||
{tags && tags[0] && (
|
||||
<div className="flex gap-2 mt-2 mb-6 flex-wrap">
|
||||
<button
|
||||
className="max-w-full"
|
||||
onClick={() => handleTagSelection(undefined)}
|
||||
>
|
||||
<p className="text-sm">{t("browse_by_topic")}</p>
|
||||
<i
|
||||
className={`bi-chevron-down ${
|
||||
tagDisclosure ? "rotate-reverse" : "rotate"
|
||||
}`}
|
||||
></i>
|
||||
</Disclosure.Button>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="transform opacity-0 -translate-y-3"
|
||||
enterTo="transform opacity-100 translate-y-0"
|
||||
leave="transition duration-100 ease-out"
|
||||
leaveFrom="transform opacity-100 translate-y-0"
|
||||
leaveTo="transform opacity-0 -translate-y-3"
|
||||
>
|
||||
<Disclosure.Panel>
|
||||
<div className="flex gap-2 mt-2 mb-6 flex-wrap">
|
||||
<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"
|
||||
onClick={() => handleTagSelection(undefined)}
|
||||
key={i}
|
||||
onClick={() => handleTagSelection(e)}
|
||||
>
|
||||
<div
|
||||
className="
|
||||
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"
|
||||
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="text-primary bi-hash text-2xl text-primary drop-shadow"></i>
|
||||
<p className="truncate pr-7">{t("all_links")}</p>
|
||||
<div className="text-neutral drop-shadow text-neutral text-xs">
|
||||
{collection._count?.links}
|
||||
<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>
|
||||
{linksForWholeCollection
|
||||
.flatMap((l) => l.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">
|
||||
{
|
||||
linksForWholeCollection.filter((l) =>
|
||||
l.tags.map((t) => t.name).includes(e)
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</Disclosure>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Links
|
||||
links={
|
||||
|
|
|
@ -371,6 +371,5 @@
|
|||
"demo_title": "Nur Demo",
|
||||
"demo_desc": "Dies ist nur eine Demo-Instanz von Linkwarden und Uploads sind deaktiviert.",
|
||||
"demo_desc_2": "Wenn Du die Vollversion ausprobieren möchtest, kannst Du Dich für eine kostenlose Testversion anmelden unter:",
|
||||
"demo_button": "Login als Demo-Benutzer",
|
||||
"browse_by_topic": "Nach Thema suchen"
|
||||
"demo_button": "Login als Demo-Benutzer"
|
||||
}
|
|
@ -397,7 +397,6 @@
|
|||
"invalid_url_guide":"Please enter a valid Address for the Link. (It should start with http/https)",
|
||||
"email_invalid": "Please enter a valid email address.",
|
||||
"username_invalid_guide": "Username has to be at least 3 characters, no spaces and special characters are allowed.",
|
||||
"browse_by_topic": "Browse by topic",
|
||||
"team_management": "Team Management",
|
||||
"invite_user": "Invite User",
|
||||
"invite_users": "Invite Users",
|
||||
|
|
|
@ -371,6 +371,5 @@
|
|||
"demo_title": "Solo para demostración",
|
||||
"demo_desc": "Esta es solo una instancia de demostración de Linkwarden y las cargas están deshabilitadas.",
|
||||
"demo_desc_2": "Si deseas probar la versión completa, puedes registrarte para una prueba gratuita en:",
|
||||
"demo_button": "Iniciar sesión como usuario demo",
|
||||
"browse_by_topic": "Navegar por tema"
|
||||
"demo_button": "Iniciar sesión como usuario demo"
|
||||
}
|
|
@ -370,6 +370,5 @@
|
|||
"demo_title": "Démonstration uniquement",
|
||||
"demo_desc": "Il s'agit d'une instance de démonstration de Linkwarden et les téléchargements sont désactivés.",
|
||||
"demo_desc_2": "Si vous souhaitez tester la version complète, vous pouvez vous inscrire pour un essai gratuit à l'adresse suivante:",
|
||||
"demo_button": "Se connecter en tant qu'utilisateur de démonstration",
|
||||
"browse_by_topic": "Naviguer par sujet"
|
||||
"demo_button": "Se connecter en tant qu'utilisateur de démonstration"
|
||||
}
|
||||
|
|
|
@ -367,6 +367,5 @@
|
|||
"webpage": "Pagina web",
|
||||
"server_administration": "Amministrazione Server",
|
||||
"all_collections": "Tutte le Collezioni",
|
||||
"dashboard": "Dashboard",
|
||||
"browse_by_topic": "Sfoglia per argomento"
|
||||
"dashboard": "Dashboard"
|
||||
}
|
|
@ -393,6 +393,5 @@
|
|||
"change_icon": "アイコンを変更",
|
||||
"upload_preview_image": "プレビュー画像をアップロード",
|
||||
"columns": "列",
|
||||
"default": "デフォルト",
|
||||
"browse_by_topic": "トピックごとに閲覧する"
|
||||
"default": "デフォルト"
|
||||
}
|
|
@ -371,6 +371,5 @@
|
|||
"demo_title": "Demo Alleen",
|
||||
"demo_desc": "Dit is slechts een demo-instantie van Linkwarden en uploads zijn uitgeschakeld.",
|
||||
"demo_desc_2": "Als u de volledige versie wilt proberen, kunt u zich aanmelden voor een gratis proefperiode op:",
|
||||
"demo_button": "Inloggen als demo gebruiker",
|
||||
"browse_by_topic": "Blader op onderwerp"
|
||||
"demo_button": "Inloggen als demo gebruiker"
|
||||
}
|
|
@ -396,6 +396,5 @@
|
|||
"demo_desc": "Esta é apenas uma instância de demonstração do Linkwarden e os uploads estão desativados.",
|
||||
"demo_desc_2": "Se você quiser experimentar a versão completa, você pode se inscrever para um teste gratuito em:",
|
||||
"demo_button": "Entrar como usuário de demonstração",
|
||||
"notes": "Notas",
|
||||
"browse_by_topic": "Navegue por tópico"
|
||||
"notes": "Notas"
|
||||
}
|
|
@ -373,6 +373,5 @@
|
|||
"demo_title": "Sadece Demo",
|
||||
"demo_desc": "Bu sadece bir Linkwarden demo örneğidir ve yüklemeler devre dışı bırakılmıştır.",
|
||||
"demo_desc_2": "Tam sürümü denemek istiyorsanız, ücretsiz deneme için kaydolabilirsiniz:",
|
||||
"demo_button": "Demo kullanıcı olarak giriş yap",
|
||||
"browse_by_topic": "Konuya göre göz atı"
|
||||
"demo_button": "Demo kullanıcı olarak giriş yap"
|
||||
}
|
|
@ -393,6 +393,5 @@
|
|||
"change_icon": "Змінити піктограму",
|
||||
"upload_preview_image": "Завантажте зображення для попереднього перегляду",
|
||||
"columns": "Стовпці",
|
||||
"default": "За замовчуванням",
|
||||
"browse_by_topic": "Перегляньте за темою"
|
||||
"default": "За замовчуванням"
|
||||
}
|
|
@ -370,6 +370,5 @@
|
|||
"demo_title": "僅供展示",
|
||||
"demo_desc": "這只是 Linkwarden 的展示實例,禁止上傳檔案。",
|
||||
"demo_desc_2": "如果您想嘗試完整版,您可以註冊免費試用:",
|
||||
"demo_button": "以展示用戶登入",
|
||||
"browse_by_topic": "按主題瀏覽"
|
||||
"demo_button": "以展示用戶登入"
|
||||
}
|
|
@ -370,6 +370,5 @@
|
|||
"demo_title": "仅限演示",
|
||||
"demo_desc": "这只是 Linkwarden 的演示实例,禁止上传文件。",
|
||||
"demo_desc_2": "如果你想尝试完整版,你可以注册免费试用:",
|
||||
"demo_button": "以演示用户登录",
|
||||
"browse_by_topic": "按主题浏览"
|
||||
"demo_button": "以演示用户登录"
|
||||
}
|
Ŝarĝante…
Reference in New Issue