diff --git a/components/LinkCard.tsx b/components/LinkCard.tsx index 62a6cfb..fc23253 100644 --- a/components/LinkCard.tsx +++ b/components/LinkCard.tsx @@ -26,7 +26,6 @@ import unescapeString from "@/lib/client/unescapeString"; import { useRouter } from "next/router"; import EditLinkModal from "./ModalContent/EditLinkModal"; import DeleteLinkModal from "./ModalContent/DeleteLinkModal"; -import ExpandedLink from "./ModalContent/ExpandedLink"; import PreservedFormatsModal from "./ModalContent/PreservedFormatsModal"; type Props = { @@ -112,11 +111,10 @@ export default function LinkCard({ link, count, className }: Props) { const [editLinkModal, setEditLinkModal] = useState(false); const [deleteLinkModal, setDeleteLinkModal] = useState(false); const [preservedFormatsModal, setPreservedFormatsModal] = useState(false); - const [expandedLink, setExpandedLink] = useState(false); return (
@@ -200,12 +198,8 @@ export default function LinkCard({ link, count, className }: Props) {
) : undefined} - router.push("/links/" + link.id) - // // setExpandedLink(true) - // } +
link.url && window.open(link.url || "", "_blank")} className="flex flex-col justify-between cursor-pointer h-full w-full gap-1 p-3" > {link.url && url ? ( @@ -241,13 +235,7 @@ export default function LinkCard({ link, count, className }: Props) {
{link.url ? ( -
{ - e.preventDefault(); - window.open(link.url || "", "_blank"); - }} - className="flex items-center gap-1 max-w-full w-fit text-neutral hover:opacity-60 duration-100" - > +

{shortendURL}

@@ -257,7 +245,7 @@ export default function LinkCard({ link, count, className }: Props) {
{ - e.preventDefault(); + e.stopPropagation(); router.push(`/collections/${link.collection.id}`); }} className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" @@ -295,7 +283,7 @@ export default function LinkCard({ link, count, className }: Props) { ) : (

No Tags

)} */} - +
{editLinkModal ? ( setEditLinkModal(false)} @@ -314,9 +302,6 @@ export default function LinkCard({ link, count, className }: Props) { activeLink={link} /> ) : undefined} - {/* {expandedLink ? ( - setExpandedLink(false)} link={link} /> - ) : undefined} */}
); } diff --git a/components/Modal.tsx b/components/Modal.tsx index c7f4da5..2ddf656 100644 --- a/components/Modal.tsx +++ b/components/Modal.tsx @@ -21,7 +21,7 @@ export default function Modal({ toggleModal, className, children }: Props) {
} - className="absolute top-3 right-3 btn btn-sm outline-none btn-circle btn-ghost" + className="absolute top-3 right-3 btn btn-sm outline-none btn-circle btn-ghost z-10" >
diff --git a/components/ModalContent/ExpandedLink.tsx b/components/ModalContent/ExpandedLink.tsx deleted file mode 100644 index 464d568..0000000 --- a/components/ModalContent/ExpandedLink.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { - CollectionIncludingMembersAndLinkCount, - LinkIncludingShortenedCollectionAndTags, -} from "@/types/global"; -import Image from "next/image"; -import ColorThief, { RGBColor } from "colorthief"; -import { useEffect, useState } from "react"; -import Link from "next/link"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowUpRightFromSquare, - faBoxArchive, - faCloudArrowDown, - faFolder, -} from "@fortawesome/free-solid-svg-icons"; -import useCollectionStore from "@/store/collections"; -import { - faCalendarDays, - faFileImage, - faFilePdf, -} from "@fortawesome/free-regular-svg-icons"; -import isValidUrl from "@/lib/shared/isValidUrl"; -import unescapeString from "@/lib/client/unescapeString"; -import useLocalSettingsStore from "@/store/localSettings"; -import Modal from "../Modal"; - -type Props = { - link: LinkIncludingShortenedCollectionAndTags; - onClose: Function; -}; - -export default function LinkDetails({ link, onClose }: Props) { - const { - settings: { theme }, - } = useLocalSettingsStore(); - - const [imageError, setImageError] = useState(false); - const formattedDate = new Date(link.createdAt as string).toLocaleString( - "en-US", - { - year: "numeric", - month: "short", - day: "numeric", - } - ); - - const { collections } = useCollectionStore(); - - const [collection, setCollection] = - useState( - collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount - ); - - useEffect(() => { - setCollection( - collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount - ); - }, [collections]); - - const [colorPalette, setColorPalette] = useState(); - - const colorThief = new ColorThief(); - - const url = link.url && isValidUrl(link.url) ? new URL(link.url) : undefined; - - const handleDownload = (format: "png" | "pdf") => { - const path = `/api/v1/archives/${link.collection.id}/${link.id}.${format}`; - fetch(path) - .then((response) => { - if (response.ok) { - // Create a temporary link and click it to trigger the download - const link = document.createElement("a"); - link.href = path; - link.download = format === "pdf" ? "PDF" : "Screenshot"; - link.click(); - } else { - console.error("Failed to download file"); - } - }) - .catch((error) => { - console.error("Error:", error); - }); - }; - - return ( - -
- {!imageError && url && ( - { - try { - const color = colorThief.getPalette( - e.target as HTMLImageElement, - 4 - ); - - setColorPalette(color); - } catch (err) { - console.log(err); - } - }} - onError={(e) => { - setImageError(true); - }} - /> - )} -
-

- {unescapeString(link.name)} -

- - {url ? url.host : link.url} - -
-
-
- - -

- {collection?.name} -

- - {link.tags.map((e, i) => ( - -

- {e.name} -

- - ))} -
- {link.description && ( - <> -
- {unescapeString(link.description)} -
- - )} - -
-
- -

Archived Formats:

-
-
- -

{formattedDate}

-
-
-
-
-
-
- -
- -

Screenshot

-
- -
- - - - -
handleDownload("png")} - className="cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-2 rounded-md" - > - -
-
-
- -
-
-
- -
- -

PDF

-
- -
- - - - -
handleDownload("pdf")} - className="cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-2 rounded-md" - > - -
-
-
-
-
- ); -} diff --git a/components/ModalContent/PreservedFormatsModal.tsx b/components/ModalContent/PreservedFormatsModal.tsx index ff0556b..964dab5 100644 --- a/components/ModalContent/PreservedFormatsModal.tsx +++ b/components/ModalContent/PreservedFormatsModal.tsx @@ -15,11 +15,16 @@ import { faUpRightFromSquare, } from "@fortawesome/free-solid-svg-icons"; import Modal from "../Modal"; -import { faFileImage, faFilePdf } from "@fortawesome/free-regular-svg-icons"; +import { + faFileImage, + faFileLines, + faFilePdf, +} from "@fortawesome/free-regular-svg-icons"; import { useRouter } from "next/router"; import { useSession } from "next-auth/react"; import { pdfAvailable, + readabilityAvailable, screenshotAvailable, } from "@/lib/shared/getArchiveValidity"; @@ -30,7 +35,7 @@ type Props = { export default function PreservedFormatsModal({ onClose, activeLink }: Props) { const session = useSession(); - const { links, getLink } = useLinkStore(); + const { getLink } = useLinkStore(); const [link, setLink] = useState(activeLink); @@ -110,9 +115,54 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {

Preserved Formats

-
+
+ + {screenshotAvailable(link) || + pdfAvailable(link) || + readabilityAvailable(link) ? ( +

+ The following formats are available for this link: +

+ ) : ( +

No preserved formats available.

+ )}
+ {readabilityAvailable(link) ? ( +
+
+
+ +
+ +

Readable

+
+ +
+ {/*
handleDownload(ArchivedFormat.pdf)} + className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md" + > + +
*/} + + + + +
+
+ ) : undefined} + {screenshotAvailable(link) ? (
@@ -152,7 +202,7 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
) : undefined} - {link?.pdfPath && link.pdfPath !== "pending" ? ( + {pdfAvailable(link) ? (
@@ -191,7 +241,11 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) { {link?.collection.ownerId === session.data?.user.id ? (
updateArchive()} > @@ -208,11 +262,15 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) { )}`} target="_blank" className={`text-neutral duration-100 hover:opacity-60 flex gap-2 w-1/2 justify-center items-center text-sm ${ - screenshotAvailable(link) && pdfAvailable(link) ? "sm:mt-3" : "" + screenshotAvailable(link) && + pdfAvailable(link) && + readabilityAvailable(link) + ? "sm:mt-3" + : "" }`} >

- View Latest Snapshot on archive.org + View latest snapshot on archive.org

diff --git a/lib/api/archiveHandler.ts b/lib/api/archiveHandler.ts index 9ae230f..b34613f 100644 --- a/lib/api/archiveHandler.ts +++ b/lib/api/archiveHandler.ts @@ -7,11 +7,6 @@ import { JSDOM } from "jsdom"; import DOMPurify from "dompurify"; import { Collection, Link, User } from "@prisma/client"; import validateUrlSize from "./validateUrlSize"; -import { - pdfAvailable, - readabilityAvailable, - screenshotAvailable, -} from "../shared/getArchiveValidity"; type LinksAndCollectionAndOwner = Link & { collection: Collection & { @@ -174,23 +169,15 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { await prisma.link.update({ where: { id: link.id }, data: { - readabilityPath: - !finalLink.textContent || - finalLink.textContent === "" || - !readabilityAvailable(finalLink) || - finalLink.type !== "url" - ? "unavailable" - : undefined, - screenshotPath: - !screenshotAvailable(finalLink) || - (finalLink.type !== "url" && finalLink.type !== "pdf") - ? "unavailable" - : undefined, - pdfPath: - !pdfAvailable(finalLink) || - (finalLink.type !== "url" && finalLink.type !== "image") - ? "unavailable" - : undefined, + readabilityPath: !finalLink.readabilityPath?.startsWith("archives") + ? "unavailable" + : undefined, + screenshotPath: !finalLink.screenshotPath?.startsWith("archives") + ? "unavailable" + : undefined, + pdfPath: !finalLink.pdfPath?.startsWith("archives") + ? "unavailable" + : undefined, }, }); diff --git a/lib/api/imageHandler.ts b/lib/api/imageHandler.ts deleted file mode 100644 index bff89c3..0000000 --- a/lib/api/imageHandler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { prisma } from "@/lib/api/db"; -import createFile from "@/lib/api/storage/createFile"; -import fs from "fs"; -import path from "path"; - -export default async function imageHandler( - linkId: number, - url: string | null, - extension: string, - file?: string -) { - const image = await fetch(url as string).then((res) => res.blob()); - - const buffer = Buffer.from(await image.arrayBuffer()); - - const linkExists = await prisma.link.findUnique({ - where: { id: linkId }, - }); - - linkExists - ? await createFile({ - data: buffer, - filePath: `archives/${linkExists.collectionId}/${linkId}.${extension}`, - }) - : undefined; - - await prisma.link.update({ - where: { id: linkId }, - data: { - screenshotPath: linkExists - ? `archives/${linkExists.collectionId}/${linkId}.${extension}` - : null, - pdfPath: null, - readabilityPath: null, - }, - }); -} diff --git a/lib/api/pdfHandler.ts b/lib/api/pdfHandler.ts deleted file mode 100644 index 37dbef7..0000000 --- a/lib/api/pdfHandler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { prisma } from "@/lib/api/db"; -import createFile from "@/lib/api/storage/createFile"; -import fs from "fs"; -import path from "path"; - -export default async function pdfHandler( - linkId: number, - url: string | null, - file?: string -) { - const targetLink = await prisma.link.update({ - where: { id: linkId }, - data: { - pdfPath: "pending", - lastPreserved: new Date().toISOString(), - }, - }); - - const pdf = await fetch(url as string).then((res) => res.blob()); - - const buffer = Buffer.from(await pdf.arrayBuffer()); - - const linkExists = await prisma.link.findUnique({ - where: { id: linkId }, - }); - - linkExists - ? await createFile({ - data: buffer, - filePath: `archives/${linkExists.collectionId}/${linkId}.pdf`, - }) - : undefined; - - await prisma.link.update({ - where: { id: linkId }, - data: { - pdfPath: linkExists - ? `archives/${linkExists.collectionId}/${linkId}.pdf` - : null, - readabilityPath: null, - screenshotPath: null, - }, - }); -} diff --git a/pages/links/[id].tsx b/pages/links/[id].tsx deleted file mode 100644 index 694932b..0000000 --- a/pages/links/[id].tsx +++ /dev/null @@ -1,303 +0,0 @@ -import LinkLayout from "@/layouts/LinkLayout"; -import React, { useEffect, useState } from "react"; -import Link from "next/link"; -import useLinkStore from "@/store/links"; -import { useRouter } from "next/router"; -import { - ArchivedFormat, - LinkIncludingShortenedCollectionAndTags, -} from "@/types/global"; -import Image from "next/image"; -import ColorThief, { RGBColor } from "colorthief"; -import unescapeString from "@/lib/client/unescapeString"; -import isValidUrl from "@/lib/shared/isValidUrl"; -import DOMPurify from "dompurify"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faBoxesStacked, - faFolder, - faLink, -} from "@fortawesome/free-solid-svg-icons"; -import useModalStore from "@/store/modals"; -import { useSession } from "next-auth/react"; -import useLocalSettingsStore from "@/store/localSettings"; -import { readabilityAvailable } from "@/lib/shared/getArchiveValidity"; - -type LinkContent = { - title: string; - content: string; - textContent: string; - length: number; - excerpt: string; - byline: string; - dir: string; - siteName: string; - lang: string; -}; - -export default function Index() { - const { links, getLink } = useLinkStore(); - const { setModal } = useModalStore(); - - const { settings } = useLocalSettingsStore(); - - const session = useSession(); - const userId = session.data?.user.id; - - const [link, setLink] = useState(); - const [linkContent, setLinkContent] = useState(); - const [imageError, setImageError] = useState(false); - const [colorPalette, setColorPalette] = useState(); - - const router = useRouter(); - - useEffect(() => { - const fetchLink = async () => { - if (router.query.id) { - await getLink(Number(router.query.id)); - } - }; - - fetchLink(); - }, []); - - useEffect(() => { - if (links[0]) setLink(links.find((e) => e.id === Number(router.query.id))); - }, [links]); - - useEffect(() => { - const fetchLinkContent = async () => { - if (router.query.id && readabilityAvailable(link)) { - const response = await fetch( - `/api/v1/archives/${link?.id}?format=${ArchivedFormat.readability}` - ); - - const data = await response?.json(); - - setLinkContent(data); - } - }; - - fetchLinkContent(); - }, [link]); - - useEffect(() => { - let interval: any; - if (link?.readabilityPath === "pending") { - interval = setInterval(() => getLink(link.id as number), 5000); - } else { - if (interval) { - clearInterval(interval); - } - } - - return () => { - if (interval) { - clearInterval(interval); - } - }; - }, [link?.screenshotPath, link?.pdfPath, link?.readabilityPath]); - - const colorThief = new ColorThief(); - - const rgbToHex = (r: number, g: number, b: number): string => - "#" + - [r, g, b] - .map((x) => { - const hex = x.toString(16); - return hex.length === 1 ? "0" + hex : hex; - }) - .join(""); - - useEffect(() => { - const banner = document.getElementById("link-banner"); - const bannerInner = document.getElementById("link-banner-inner"); - - if (colorPalette && banner && bannerInner) { - if (colorPalette[0] && colorPalette[1]) { - banner.style.background = `linear-gradient(to bottom, ${rgbToHex( - colorPalette[0][0], - colorPalette[0][1], - colorPalette[0][2] - )}20, ${rgbToHex( - colorPalette[1][0], - colorPalette[1][1], - colorPalette[1][2] - )}20)`; - } - - if (colorPalette[2] && colorPalette[3]) { - bannerInner.style.background = `linear-gradient(to bottom, ${rgbToHex( - colorPalette[2][0], - colorPalette[2][1], - colorPalette[2][2] - )}30, ${rgbToHex( - colorPalette[3][0], - colorPalette[3][1], - colorPalette[3][2] - )})30`; - } - } - }, [colorPalette]); - - return ( - -
- - -
- -
- {link?.readabilityPath?.startsWith("archives") ? ( -
- ) : ( -
- {link?.readabilityPath === "pending" ? ( -

- Generating readable format, please wait... -

- ) : ( - <> -

- There is no reader view for this webpage -

-

- {link?.collection.ownerId === userId - ? "You can update (refetch) the preserved formats by managing them below" - : "The collections owners can refetch the preserved formats"} -

- {link?.collection.ownerId === userId ? ( -
- link - ? setModal({ - modal: "LINK", - state: true, - active: link, - method: "FORMATS", - }) - : undefined - } - className="mt-4 flex gap-2 w-fit mx-auto relative items-center font-semibold select-none cursor-pointer p-2 px-3 rounded-md dark:hover:bg-sky-600 text-white bg-sky-700 hover:bg-sky-600 duration-100" - > - -

Manage preserved formats

-
- ) : undefined} - - )} -
- )} -
-
- - ); -} diff --git a/pages/preserved/[id].tsx b/pages/preserved/[id].tsx new file mode 100644 index 0000000..6612950 --- /dev/null +++ b/pages/preserved/[id].tsx @@ -0,0 +1,298 @@ +import LinkLayout from "@/layouts/LinkLayout"; +import React, { useEffect, useState } from "react"; +import Link from "next/link"; +import useLinkStore from "@/store/links"; +import { useRouter } from "next/router"; +import { + ArchivedFormat, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import Image from "next/image"; +import ColorThief, { RGBColor } from "colorthief"; +import unescapeString from "@/lib/client/unescapeString"; +import isValidUrl from "@/lib/shared/isValidUrl"; +import DOMPurify from "dompurify"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFolder, faLink } from "@fortawesome/free-solid-svg-icons"; +import useModalStore from "@/store/modals"; +import { useSession } from "next-auth/react"; +import useLocalSettingsStore from "@/store/localSettings"; +import { readabilityAvailable } from "@/lib/shared/getArchiveValidity"; + +type LinkContent = { + title: string; + content: string; + textContent: string; + length: number; + excerpt: string; + byline: string; + dir: string; + siteName: string; + lang: string; +}; + +export default function Index() { + const { links, getLink } = useLinkStore(); + const { setModal } = useModalStore(); + + const { settings } = useLocalSettingsStore(); + + const session = useSession(); + const userId = session.data?.user.id; + + const [link, setLink] = useState(); + const [linkContent, setLinkContent] = useState(); + const [imageError, setImageError] = useState(false); + const [colorPalette, setColorPalette] = useState(); + + const router = useRouter(); + + useEffect(() => { + const fetchLink = async () => { + if (router.query.id) { + await getLink(Number(router.query.id)); + } + }; + + fetchLink(); + }, []); + + useEffect(() => { + if (links[0]) setLink(links.find((e) => e.id === Number(router.query.id))); + }, [links]); + + useEffect(() => { + const fetchLinkContent = async () => { + if (router.query.id && readabilityAvailable(link)) { + const response = await fetch( + `/api/v1/archives/${link?.id}?format=${ArchivedFormat.readability}` + ); + + const data = await response?.json(); + + setLinkContent(data); + } + }; + + fetchLinkContent(); + }, [link]); + + useEffect(() => { + if (link) getLink(link?.id as number); + + let interval: any; + if ( + link && + (link?.screenshotPath === "pending" || + link?.pdfPath === "pending" || + link?.readabilityPath === "pending" || + !link?.screenshotPath || + !link?.pdfPath || + !link?.readabilityPath) + ) { + interval = setInterval(() => getLink(link.id as number), 5000); + } else { + if (interval) { + clearInterval(interval); + } + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [link?.screenshotPath, link?.pdfPath, link?.readabilityPath]); + + const colorThief = new ColorThief(); + + const rgbToHex = (r: number, g: number, b: number): string => + "#" + + [r, g, b] + .map((x) => { + const hex = x.toString(16); + return hex.length === 1 ? "0" + hex : hex; + }) + .join(""); + + useEffect(() => { + const banner = document.getElementById("link-banner"); + const bannerInner = document.getElementById("link-banner-inner"); + + if (colorPalette && banner && bannerInner) { + if (colorPalette[0] && colorPalette[1]) { + banner.style.background = `linear-gradient(to bottom, ${rgbToHex( + colorPalette[0][0], + colorPalette[0][1], + colorPalette[0][2] + )}20, ${rgbToHex( + colorPalette[1][0], + colorPalette[1][1], + colorPalette[1][2] + )}20)`; + } + + if (colorPalette[2] && colorPalette[3]) { + bannerInner.style.background = `linear-gradient(to bottom, ${rgbToHex( + colorPalette[2][0], + colorPalette[2][1], + colorPalette[2][2] + )}30, ${rgbToHex( + colorPalette[3][0], + colorPalette[3][1], + colorPalette[3][2] + )})30`; + } + } + }, [colorPalette]); + + return ( +
+ + +
+ {link && link.type === "url" + ? DisplayReadable(link, linkContent) + : undefined} +
+
+ ); +} + +const DisplayReadable = ( + link: LinkIncludingShortenedCollectionAndTags, + linkContent?: LinkContent +) => { + return link.readabilityPath?.startsWith("archives") ? ( +
+ ) : ( +
+ + + + +

+ The Link preservation is currently in the queue +

+

+ Please check back later to see the result +

+
+ ); +}; diff --git a/pages/public/links/[id].tsx b/pages/public/links/[id].tsx deleted file mode 100644 index d9641cc..0000000 --- a/pages/public/links/[id].tsx +++ /dev/null @@ -1,299 +0,0 @@ -import LinkLayout from "@/layouts/LinkLayout"; -import React, { useEffect, useState } from "react"; -import Link from "next/link"; -import useLinkStore from "@/store/links"; -import { useRouter } from "next/router"; -import { - ArchivedFormat, - LinkIncludingShortenedCollectionAndTags, -} from "@/types/global"; -import Image from "next/image"; -import ColorThief, { RGBColor } from "colorthief"; -import unescapeString from "@/lib/client/unescapeString"; -import isValidUrl from "@/lib/shared/isValidUrl"; -import DOMPurify from "dompurify"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faBoxesStacked, faFolder } from "@fortawesome/free-solid-svg-icons"; -import useModalStore from "@/store/modals"; -import { useSession } from "next-auth/react"; -import useLocalSettingsStore from "@/store/localSettings"; -import { readabilityAvailable } from "@/lib/shared/getArchiveValidity"; - -type LinkContent = { - title: string; - content: string; - textContent: string; - length: number; - excerpt: string; - byline: string; - dir: string; - siteName: string; - lang: string; -}; - -export default function Index() { - const { links, getLink } = useLinkStore(); - const { setModal } = useModalStore(); - - const { settings } = useLocalSettingsStore(); - - const session = useSession(); - const userId = session.data?.user.id; - - const [link, setLink] = useState(); - const [linkContent, setLinkContent] = useState(); - const [imageError, setImageError] = useState(false); - const [colorPalette, setColorPalette] = useState(); - - const router = useRouter(); - - useEffect(() => { - const fetchLink = async () => { - if (router.query.id) { - await getLink(Number(router.query.id), true); - } - }; - - fetchLink(); - }, []); - - useEffect(() => { - if (links[0]) setLink(links.find((e) => e.id === Number(router.query.id))); - }, [links]); - - useEffect(() => { - const fetchLinkContent = async () => { - if (router.query.id && readabilityAvailable(link)) { - const response = await fetch( - `/api/v1/archives/${link?.id}?format=${ArchivedFormat.readability}` - ); - - const data = await response?.json(); - - setLinkContent(data); - } - }; - - fetchLinkContent(); - }, [link]); - - useEffect(() => { - let interval: any; - if (link?.readabilityPath === "pending") { - interval = setInterval(() => getLink(link.id as number, true), 5000); - } else { - if (interval) { - clearInterval(interval); - } - } - - return () => { - if (interval) { - clearInterval(interval); - } - }; - }, [link?.screenshotPath, link?.pdfPath, link?.readabilityPath]); - - const colorThief = new ColorThief(); - - const rgbToHex = (r: number, g: number, b: number): string => - "#" + - [r, g, b] - .map((x) => { - const hex = x.toString(16); - return hex.length === 1 ? "0" + hex : hex; - }) - .join(""); - - useEffect(() => { - const banner = document.getElementById("link-banner"); - const bannerInner = document.getElementById("link-banner-inner"); - - if (colorPalette && banner && bannerInner) { - if (colorPalette[0] && colorPalette[1]) { - banner.style.background = `linear-gradient(to right, ${rgbToHex( - colorPalette[0][0], - colorPalette[0][1], - colorPalette[0][2] - )}30, ${rgbToHex( - colorPalette[1][0], - colorPalette[1][1], - colorPalette[1][2] - )}30)`; - } - - if (colorPalette[2] && colorPalette[3]) { - bannerInner.style.background = `linear-gradient(to left, ${rgbToHex( - colorPalette[2][0], - colorPalette[2][1], - colorPalette[2][2] - )}30, ${rgbToHex( - colorPalette[3][0], - colorPalette[3][1], - colorPalette[3][2] - )})30`; - } - } - }, [colorPalette]); - - return ( - -
- - -
- {link?.readabilityPath?.startsWith("archives") ? ( -
- ) : ( -
- {link?.readabilityPath === "pending" ? ( -

- Generating readable format, please wait... -

- ) : ( - <> -

- There is no reader view for this webpage -

-

- {link?.collection?.ownerId === userId - ? "You can update (refetch) the preserved formats by managing them below" - : "The collections owners can refetch the preserved formats"} -

- {link?.collection?.ownerId === userId ? ( -
- link - ? setModal({ - modal: "LINK", - state: true, - active: link, - method: "FORMATS", - }) - : undefined - } - className="mt-4 flex gap-2 w-fit mx-auto relative items-center font-semibold select-none cursor-pointer p-2 px-3 rounded-md dark:hover:bg-sky-600 text-white bg-sky-700 hover:bg-sky-600 duration-100" - > - -

Manage preserved formats

-
- ) : undefined} - - )} -
- )} -
-
-
- ); -}