minor changes

This commit is contained in:
daniel31x13 2024-11-02 20:43:53 -04:00
parent 2e1e94112f
commit 4febe1ace5
21 changed files with 4603 additions and 21657 deletions

View File

@ -32,32 +32,18 @@ const usePublicLinks = (params: LinkRequestQuery = {}) => {
searchByTags: params.searchByTags, searchByTags: params.searchByTags,
} as LinkRequestQuery; } 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 queryString = buildQueryString(queryParamsObject);
const queryStringForAllLinkObject = buildQueryString(queryParamsForAllLinksObject);
const { data, ...rest } = useFetchLinks(queryString); const { data, ...rest } = useFetchLinks(queryString);
const allLinks = useFetchLinks(queryStringForAllLinkObject);
const links = useMemo(() => { const links = useMemo(() => {
return data?.pages.reduce((acc, page) => { return data?.pages.reduce((acc, page) => {
return [...acc, ...page]; return [...acc, ...page];
}, []); }, []);
}, [data]); }, [data]);
const linksForWholeCollection = useMemo(() => {
return allLinks.data?.pages.reduce((acc, page) => {
return [...acc, ...page];
}, []);
}, [allLinks.data])
return { return {
links, links,
linksForWholeCollection,
data: { ...data, ...rest }, data: { ...data, ...rest },
} as { } as {
links: LinkIncludingShortenedCollectionAndTags[]; links: LinkIncludingShortenedCollectionAndTags[];
linksForWholeCollection: LinkIncludingShortenedCollectionAndTags[];
data: UseInfiniteQueryResult<InfiniteData<any, unknown>, Error>; data: UseInfiniteQueryResult<InfiniteData<any, unknown>, Error>;
}; };
}; };

View File

@ -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 };

View File

@ -2,7 +2,7 @@ import { prisma } from "@/lib/api/db";
import { LinkRequestQuery, Order, Sort } from "@/types/global"; import { LinkRequestQuery, Order, Sort } from "@/types/global";
export default async function getLink( export default async function getLink(
query: Omit<LinkRequestQuery, "tagId" | "pinnedOnly">, takeAll = false query: Omit<LinkRequestQuery, "tagId" | "pinnedOnly">
) { ) {
const POSTGRES_IS_ENABLED = const POSTGRES_IS_ENABLED =
process.env.DATABASE_URL?.startsWith("postgresql"); process.env.DATABASE_URL?.startsWith("postgresql");

View File

@ -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: {

17218
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import getPublicLinksUnderCollection from "@/lib/api/controllers/public/links/getPublicLinksUnderCollection";
import getTags from "@/lib/api/controllers/tags/getTags"; import getTags from "@/lib/api/controllers/tags/getTags";
import { prisma } from "@/lib/api/db";
import { LinkRequestQuery } from "@/types/global"; import { LinkRequestQuery } from "@/types/global";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
@ -22,14 +22,21 @@ export default async function collections(
.json({ response: "Please choose a valid collection." }); .json({ response: "Please choose a valid collection." });
} }
const links = await getPublicLinksUnderCollection(convertedData, true); const collection = await prisma.collection.findFirst({
const tags = await getTags(); where: {
const tagsInLinks = links.response.map(l => l.tags).flat().filter((value, index, self) => id: convertedData.collectionId,
index === self.findIndex((t) => ( isPublic: true,
t.name === value.name },
))).map(t => t.id); });
const tagsWithCount = tags.response.filter(tag => tagsInLinks.includes(tag.id));
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 });
} }
} }

View File

@ -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 });
} }
} }

View File

@ -4,7 +4,6 @@ import {
AccountSettings, AccountSettings,
CollectionIncludingMembersAndLinkCount, CollectionIncludingMembersAndLinkCount,
Sort, Sort,
TagIncludingLinkCount,
ViewMode, ViewMode,
} from "@/types/global"; } from "@/types/global";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -23,7 +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 { Disclosure, Transition } from "@headlessui/react"; import { usePublicTags } from "@/hooks/store/publicTags";
export default function PublicCollections() { export default function PublicCollections() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -74,7 +73,9 @@ export default function PublicCollections() {
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
); );
const { links, linksForWholeCollection, data } = usePublicLinks({ const { data: tags } = usePublicTags();
const { links, data } = usePublicLinks({
sort: sortBy, sort: sortBy,
searchQueryString: router.query.q searchQueryString: router.query.q
? decodeURIComponent(router.query.q as string) ? decodeURIComponent(router.query.q as string)
@ -114,19 +115,6 @@ export default function PublicCollections() {
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card (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 <></>; if (!collection) return <></>;
else else
return ( return (
@ -251,48 +239,27 @@ export default function PublicCollections() {
} }
/> />
</LinkListOptions> </LinkListOptions>
{linksForWholeCollection?.flatMap((l) => l.tags)[0] && ( {tags && 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"
>
<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="flex gap-2 mt-2 mb-6 flex-wrap">
<button <button
className="max-w-full" className="max-w-full"
onClick={() => handleTagSelection(undefined)} onClick={() => handleTagSelection(undefined)}
> >
<div <div
className=" 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" !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 text-primary drop-shadow"></i> <i className="text-primary bi-hash text-2xl drop-shadow"></i>
<p className="truncate pr-7">{t("all_links")}</p> <p className="truncate pr-7">{t("all_links")}</p>
<div className="text-neutral drop-shadow text-neutral text-xs"> <div className="text-neutral drop-shadow text-xs">
{collection._count?.links} {collection._count?.links}
</div> </div>
</div> </div>
</button> </button>
{linksForWholeCollection {tags
.flatMap((l) => l.tags)
.map((t) => t.name) .map((t) => t.name)
.filter((item, pos, self) => self.indexOf(item) === pos) .filter((item, pos, self) => self.indexOf(item) === pos)
.sort((a, b) => a.localeCompare(b)) .sort((a, b) => a.localeCompare(b))
@ -305,8 +272,7 @@ export default function PublicCollections() {
onClick={() => handleTagSelection(e)} onClick={() => handleTagSelection(e)}
> >
<div <div
className={` className={`${
${
active active
? "bg-primary/20" ? "bg-primary/20"
: "bg-neutral-content/20 hover:bg-neutral/20" : "bg-neutral-content/20 hover:bg-neutral/20"
@ -315,20 +281,13 @@ export default function PublicCollections() {
<i className="bi-hash text-2xl text-primary drop-shadow"></i> <i className="bi-hash text-2xl text-primary drop-shadow"></i>
<p className="truncate pr-7">{e}</p> <p className="truncate pr-7">{e}</p>
<div className="drop-shadow text-neutral text-xs"> <div className="drop-shadow text-neutral text-xs">
{ {tags.filter((t) => t.name === e)[0]._count.links}
linksForWholeCollection.filter((l) =>
l.tags.map((t) => t.name).includes(e)
).length
}
</div> </div>
</div> </div>
</button> </button>
); );
})} })}
</div> </div>
</Disclosure.Panel>
</Transition>
</Disclosure>
)} )}
<Links <Links
links={ links={

View File

@ -371,6 +371,5 @@
"demo_title": "Nur Demo", "demo_title": "Nur Demo",
"demo_desc": "Dies ist nur eine Demo-Instanz von Linkwarden und Uploads sind deaktiviert.", "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_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", "demo_button": "Login als Demo-Benutzer"
"browse_by_topic": "Nach Thema suchen"
} }

View File

@ -397,7 +397,6 @@
"invalid_url_guide":"Please enter a valid Address for the Link. (It should start with http/https)", "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.", "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.", "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", "team_management": "Team Management",
"invite_user": "Invite User", "invite_user": "Invite User",
"invite_users": "Invite Users", "invite_users": "Invite Users",

View File

@ -371,6 +371,5 @@
"demo_title": "Solo para demostración", "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": "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_desc_2": "Si deseas probar la versión completa, puedes registrarte para una prueba gratuita en:",
"demo_button": "Iniciar sesión como usuario demo", "demo_button": "Iniciar sesión como usuario demo"
"browse_by_topic": "Navegar por tema"
} }

View File

@ -370,6 +370,5 @@
"demo_title": "Démonstration uniquement", "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": "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_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", "demo_button": "Se connecter en tant qu'utilisateur de démonstration"
"browse_by_topic": "Naviguer par sujet"
} }

View File

@ -367,6 +367,5 @@
"webpage": "Pagina web", "webpage": "Pagina web",
"server_administration": "Amministrazione Server", "server_administration": "Amministrazione Server",
"all_collections": "Tutte le Collezioni", "all_collections": "Tutte le Collezioni",
"dashboard": "Dashboard", "dashboard": "Dashboard"
"browse_by_topic": "Sfoglia per argomento"
} }

View File

@ -393,6 +393,5 @@
"change_icon": "アイコンを変更", "change_icon": "アイコンを変更",
"upload_preview_image": "プレビュー画像をアップロード", "upload_preview_image": "プレビュー画像をアップロード",
"columns": "列", "columns": "列",
"default": "デフォルト", "default": "デフォルト"
"browse_by_topic": "トピックごとに閲覧する"
} }

View File

@ -371,6 +371,5 @@
"demo_title": "Demo Alleen", "demo_title": "Demo Alleen",
"demo_desc": "Dit is slechts een demo-instantie van Linkwarden en uploads zijn uitgeschakeld.", "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_desc_2": "Als u de volledige versie wilt proberen, kunt u zich aanmelden voor een gratis proefperiode op:",
"demo_button": "Inloggen als demo gebruiker", "demo_button": "Inloggen als demo gebruiker"
"browse_by_topic": "Blader op onderwerp"
} }

View File

@ -396,6 +396,5 @@
"demo_desc": "Esta é apenas uma instância de demonstração do Linkwarden e os uploads estão desativados.", "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_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", "demo_button": "Entrar como usuário de demonstração",
"notes": "Notas", "notes": "Notas"
"browse_by_topic": "Navegue por tópico"
} }

View File

@ -373,6 +373,5 @@
"demo_title": "Sadece Demo", "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": "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_desc_2": "Tam sürümü denemek istiyorsanız, ücretsiz deneme için kaydolabilirsiniz:",
"demo_button": "Demo kullanıcı olarak giriş yap", "demo_button": "Demo kullanıcı olarak giriş yap"
"browse_by_topic": "Konuya göre göz atı"
} }

View File

@ -393,6 +393,5 @@
"change_icon": "Змінити піктограму", "change_icon": "Змінити піктограму",
"upload_preview_image": "Завантажте зображення для попереднього перегляду", "upload_preview_image": "Завантажте зображення для попереднього перегляду",
"columns": "Стовпці", "columns": "Стовпці",
"default": "За замовчуванням", "default": "За замовчуванням"
"browse_by_topic": "Перегляньте за темою"
} }

View File

@ -370,6 +370,5 @@
"demo_title": "僅供展示", "demo_title": "僅供展示",
"demo_desc": "這只是 Linkwarden 的展示實例,禁止上傳檔案。", "demo_desc": "這只是 Linkwarden 的展示實例,禁止上傳檔案。",
"demo_desc_2": "如果您想嘗試完整版,您可以註冊免費試用:", "demo_desc_2": "如果您想嘗試完整版,您可以註冊免費試用:",
"demo_button": "以展示用戶登入", "demo_button": "以展示用戶登入"
"browse_by_topic": "按主題瀏覽"
} }

View File

@ -370,6 +370,5 @@
"demo_title": "仅限演示", "demo_title": "仅限演示",
"demo_desc": "这只是 Linkwarden 的演示实例,禁止上传文件。", "demo_desc": "这只是 Linkwarden 的演示实例,禁止上传文件。",
"demo_desc_2": "如果你想尝试完整版,你可以注册免费试用:", "demo_desc_2": "如果你想尝试完整版,你可以注册免费试用:",
"demo_button": "以演示用户登录", "demo_button": "以演示用户登录"
"browse_by_topic": "按主题浏览"
} }

8772
yarn.lock

File diff suppressed because it is too large Load Diff