Accepted incoming changes

This commit is contained in:
daniel31x13
2024-11-12 10:09:02 -05:00
55 changed files with 6714 additions and 1281 deletions
+212 -146
View File
@@ -1,6 +1,7 @@
"use client";
import getPublicCollectionData from "@/lib/client/getPublicCollectionData";
import {
AccountSettings,
CollectionIncludingMembersAndLinkCount,
Sort,
ViewMode,
@@ -21,6 +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 { usePublicTags } from "@/hooks/store/publicTags";
export default function PublicCollections() {
const { t } = useTranslation();
@@ -29,15 +31,35 @@ export default function PublicCollections() {
const router = useRouter();
const [collectionOwner, setCollectionOwner] = useState({
id: null as unknown as number,
name: "",
username: "",
image: "",
archiveAsScreenshot: undefined as unknown as boolean,
archiveAsMonolith: undefined as unknown as boolean,
archiveAsPDF: undefined as unknown as boolean,
});
const [collectionOwner, setCollectionOwner] = useState<
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({
name: true,
@@ -51,6 +73,8 @@ export default function PublicCollections() {
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const { data: tags } = usePublicTags();
const { links, data } = usePublicLinks({
sort: sortBy,
searchQueryString: router.query.q
@@ -62,10 +86,8 @@ export default function PublicCollections() {
searchByTextContent: searchFilter.textContent,
searchByTags: searchFilter.tags,
});
const [collection, setCollection] =
useState<CollectionIncludingMembersAndLinkCount>();
useEffect(() => {
if (router.query.id) {
getPublicCollectionData(Number(router.query.id)).then((res) => {
@@ -93,160 +115,204 @@ export default function PublicCollections() {
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
return collection ? (
<div
className="h-96"
style={{
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}}
>
{collection ? (
<Head>
<title>{collection.name} | Linkwarden</title>
<meta
property="og:title"
content={`${collection.name} | Linkwarden`}
key="title"
/>
</Head>
) : undefined}
<div className="lg:w-3/4 w-full mx-auto p-5 bg">
<div className="flex items-center justify-between">
<p className="text-4xl font-thin mb-2 capitalize mt-10">
{collection.name}
</p>
<div className="flex gap-2 items-center mt-8 min-w-fit">
<ToggleDarkMode />
if (!collection) return <></>;
else
return (
<div
className="h-96"
style={{
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}}
>
{collection && (
<Head>
<title>{collection.name} | Linkwarden</title>
<meta
property="og:title"
content={`${collection.name} | Linkwarden`}
key="title"
/>
</Head>
)}
<div className="lg:w-3/4 w-full mx-auto p-5 bg">
<div className="flex items-center justify-between">
<p className="text-4xl font-thin mb-2 capitalize mt-10">
{collection.name}
</p>
<div className="flex gap-2 items-center mt-8 min-w-fit">
<ToggleDarkMode />
<Link href="https://linkwarden.app/" target="_blank">
<Image
src={`/icon.png`}
width={551}
height={551}
alt="Linkwarden"
title={t("list_created_with_linkwarden")}
className="h-8 w-fit mx-auto rounded"
/>
</Link>
<Link href="https://linkwarden.app/" target="_blank">
<Image
src={`/icon.png`}
width={551}
height={551}
alt="Linkwarden"
title={t("list_created_with_linkwarden")}
className="h-8 w-fit mx-auto rounded"
/>
</Link>
</div>
</div>
</div>
<div className="mt-3">
<div className={`min-w-[15rem]`}>
<div className="flex gap-1 justify-center sm:justify-end items-center w-fit">
<div
className="flex items-center btn px-2 btn-ghost rounded-full"
onClick={() => setEditCollectionSharingModal(true)}
>
{collectionOwner.id ? (
<ProfilePhoto
src={collectionOwner.image || undefined}
name={collectionOwner.name}
/>
) : undefined}
{collection.members
.sort((a, b) => (a.userId as number) - (b.userId as number))
.map((e, i) => {
return (
<ProfilePhoto
key={i}
src={e.user.image ? e.user.image : undefined}
className="-ml-3"
name={e.user.name}
/>
);
})
.slice(0, 3)}
{collection.members.length - 3 > 0 ? (
<div className={`avatar drop-shadow-md placeholder -ml-3`}>
<div className="bg-base-100 text-neutral rounded-full w-8 h-8 ring-2 ring-neutral-content">
<span>+{collection.members.length - 3}</span>
</div>
</div>
) : null}
</div>
<p className="text-neutral text-sm">
{collection.members.length > 0 &&
collection.members.length === 1
? t("by_author_and_other", {
author: collectionOwner.name,
count: collection.members.length,
<div className="mt-3">
<div className={`min-w-[15rem]`}>
<div className="flex gap-1 justify-center sm:justify-end items-center w-fit">
<div
className="flex items-center btn px-2 btn-ghost rounded-full"
onClick={() => setEditCollectionSharingModal(true)}
>
{collectionOwner.id && (
<ProfilePhoto
src={collectionOwner.image || undefined}
name={collectionOwner.name}
/>
)}
{collection.members
.sort((a, b) => (a.userId as number) - (b.userId as number))
.map((e, i) => {
return (
<ProfilePhoto
key={i}
src={e.user.image ? e.user.image : undefined}
className="-ml-3"
name={e.user.name}
/>
);
})
: collection.members.length > 0 &&
collection.members.length !== 1
? t("by_author_and_others", {
.slice(0, 3)}
{collection.members.length - 3 > 0 && (
<div className={`avatar drop-shadow-md placeholder -ml-3`}>
<div className="bg-base-100 text-neutral rounded-full w-8 h-8 ring-2 ring-neutral-content">
<span>+{collection.members.length - 3}</span>
</div>
</div>
)}
</div>
<p className="text-neutral text-sm">
{collection.members.length > 0 &&
collection.members.length === 1
? t("by_author_and_other", {
author: collectionOwner.name,
count: collection.members.length,
})
: t("by_author", {
author: collectionOwner.name,
})}
</p>
: collection.members.length > 0 &&
collection.members.length !== 1
? t("by_author_and_others", {
author: collectionOwner.name,
count: collection.members.length,
})
: t("by_author", {
author: collectionOwner.name,
})}
</p>
</div>
</div>
</div>
</div>
<p className="mt-5">{collection.description}</p>
<p className="mt-5">{collection.description}</p>
<div className="divider mt-5 mb-0"></div>
<div className="divider mt-5 mb-0"></div>
<div className="flex mb-5 mt-10 flex-col gap-5">
<LinkListOptions
t={t}
viewMode={viewMode}
setViewMode={setViewMode}
sortBy={sortBy}
setSortBy={setSortBy}
searchFilter={searchFilter}
setSearchFilter={setSearchFilter}
>
<SearchBar
placeholder={
collection._count?.links === 1
? t("search_count_link", {
count: collection._count?.links,
})
: t("search_count_links", {
count: collection._count?.links,
})
<div className="flex mb-5 mt-10 flex-col gap-5">
<LinkListOptions
t={t}
viewMode={viewMode}
setViewMode={setViewMode}
sortBy={sortBy}
setSortBy={setSortBy}
searchFilter={searchFilter}
setSearchFilter={setSearchFilter}
>
<SearchBar
placeholder={
collection._count?.links === 1
? t("search_count_link", {
count: collection._count?.links,
})
: t("search_count_links", {
count: collection._count?.links,
})
}
/>
</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`}
>
<p className="truncate px-3">{t("all_links")}</p>
</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-3">{e}</p>
</div>
</button>
);
})}
</div>
)}
<Links
links={
links?.map((e, i) => {
const linkWithCollectionData = {
...e,
collection: collection, // Append collection data
};
return linkWithCollectionData;
}) as any
}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
</LinkListOptions>
{!data.isLoading && links && !links[0] && (
<p>{t("nothing_found")}</p>
)}
<Links
links={
links?.map((e, i) => {
const linkWithCollectionData = {
...e,
collection: collection, // Append collection data
};
return linkWithCollectionData;
}) as any
}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
{!data.isLoading && links && !links[0] && <p>{t("nothing_found")}</p>}
{/* <p className="text-center text-neutral">
{/* <p className="text-center text-neutral">
List created with <span className="text-black">Linkwarden.</span>
</p> */}
</div>
</div>
{editCollectionSharingModal && (
<EditCollectionSharingModal
onClose={() => setEditCollectionSharingModal(false)}
activeCollection={collection}
/>
)}
</div>
{editCollectionSharingModal ? (
<EditCollectionSharingModal
onClose={() => setEditCollectionSharingModal(false)}
activeCollection={collection}
/>
) : undefined}
</div>
) : (
<></>
);
);
}
export { getServerSideProps };
+40 -33
View File
@@ -1,63 +1,70 @@
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import {
ArchivedFormat,
LinkIncludingShortenedCollectionAndTags,
} from "@/types/global";
import { ArchivedFormat } from "@/types/global";
import ReadableView from "@/components/ReadableView";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useGetLink, useLinks } from "@/hooks/store/links";
import { useGetLink } from "@/hooks/store/links";
import clsx from "clsx";
export default function Index() {
const { links } = useLinks();
const getLink = useGetLink();
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
const router = useRouter();
useEffect(() => {
const fetchLink = async () => {
if (router.query.id) {
await getLink.mutateAsync(Number(router.query.id));
}
};
fetchLink();
if (router.query.id) {
getLink.mutateAsync({ id: Number(router.query.id), isPublicRoute: true });
}
}, []);
useEffect(() => {
if (links && links[0])
setLink(links.find((e) => e.id === Number(router.query.id)));
}, [links]);
return (
<div className="relative">
<div className={clsx(getLink.isPending ? "flex h-screen" : "relative")}>
{/* <div className="fixed left-1/2 transform -translate-x-1/2 w-fit py-1 px-3 bg-base-200 border border-neutral-content rounded-md">
Readable
</div> */}
{link && Number(router.query.format) === ArchivedFormat.readability && (
<ReadableView link={link} />
)}
{link && Number(router.query.format) === ArchivedFormat.pdf && (
{getLink.data?.id &&
Number(router.query.format) === ArchivedFormat.readability ? (
<ReadableView link={getLink.data} />
) : getLink.data?.id &&
Number(router.query.format) === ArchivedFormat.monolith ? (
<iframe
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.pdf}`}
src={`/api/v1/archives/${getLink.data.id}?format=${ArchivedFormat.monolith}`}
className="w-full h-screen border-none"
></iframe>
)}
{link && Number(router.query.format) === ArchivedFormat.png && (
) : getLink.data?.id &&
Number(router.query.format) === ArchivedFormat.pdf ? (
<iframe
src={`/api/v1/archives/${getLink.data.id}?format=${ArchivedFormat.pdf}`}
className="w-full h-screen border-none"
></iframe>
) : getLink.data?.id &&
Number(router.query.format) === ArchivedFormat.png ? (
<img
alt=""
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.png}`}
src={`/api/v1/archives/${getLink.data.id}?format=${ArchivedFormat.png}`}
className="w-fit mx-auto"
/>
)}
{link && Number(router.query.format) === ArchivedFormat.jpeg && (
) : getLink.data?.id &&
Number(router.query.format) === ArchivedFormat.jpeg ? (
<img
alt=""
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}`}
src={`/api/v1/archives/${getLink.data.id}?format=${ArchivedFormat.jpeg}`}
className="w-fit mx-auto"
/>
) : getLink.error ? (
<p>404 - Not found</p>
) : (
<div className="max-w-3xl p-5 m-auto w-full flex flex-col items-center gap-5">
<div className="w-full mr-auto h-4 skeleton rounded-md"></div>
<div className="w-full mr-auto h-4 skeleton rounded-md"></div>
<div className="w-full mr-auto h-4 skeleton rounded-md"></div>
<div className="w-3/4 mr-auto h-4 skeleton rounded-md"></div>
<div className="w-5/6 mr-auto h-4 skeleton rounded-md"></div>
<div className="w-3/4 mr-auto h-4 skeleton rounded-md"></div>
<div className="w-full mr-auto h-4 skeleton rounded-md"></div>
<div className="w-full mr-auto h-4 skeleton rounded-md"></div>
<div className="w-5/6 mr-auto h-4 skeleton rounded-md"></div>
</div>
)}
</div>
);