mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 02:47:02 +00:00
* build(deps): bump the npm_and_yarn group across 5 directories with 22 updates Bumps the npm_and_yarn group with 18 updates in the / directory: | Package | From | To | | --- | --- | --- | | [axios](https://github.com/axios/axios) | `1.5.1` | `1.12.0` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.0.6` | `3.2.4` | | [formidable](https://github.com/node-formidable/formidable) | `3.5.1` | `3.5.4` | | [next](https://github.com/vercel/next.js) | `13.4.12` | `14.2.35` | | [next-auth](https://github.com/nextauthjs/next-auth) | `4.22.1` | `4.24.12` | | [playwright](https://github.com/microsoft/playwright) | `1.55.0` | `1.55.1` | | [@mozilla/readability](https://github.com/mozilla/readability) | `0.4.4` | `0.6.0` | | [ai](https://github.com/vercel/ai) | `4.3.9` | `5.0.52` | | [nodemailer](https://github.com/nodemailer/nodemailer) | `6.9.3` | `7.0.11` | | [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.11` | `1.1.12` | | [braces](https://github.com/micromatch/braces) | `3.0.2` | `3.0.3` | | [form-data](https://github.com/form-data/form-data) | `3.0.3` | `3.0.4` | | [js-yaml](https://github.com/nodeca/js-yaml) | `3.14.1` | `3.14.2` | | [micromatch](https://github.com/micromatch/micromatch) | `4.0.5` | `4.0.8` | | [min-document](https://github.com/Raynos/min-document) | `2.19.0` | `2.19.2` | | [nanoid](https://github.com/ai/nanoid) | `3.3.6` | `3.3.8` | | [node-forge](https://github.com/digitalbazaar/forge) | `1.3.1` | `1.3.3` | | [tar](https://github.com/isaacs/node-tar) | `6.1.13` | `6.2.1` | Bumps the npm_and_yarn group with 1 update in the /apps/web directory: [next](https://github.com/vercel/next.js). Bumps the npm_and_yarn group with 2 updates in the /apps/worker directory: [@mozilla/readability](https://github.com/mozilla/readability) and [ai](https://github.com/vercel/ai). Bumps the npm_and_yarn group with 1 update in the /packages/lib directory: [nodemailer](https://github.com/nodemailer/nodemailer). Bumps the npm_and_yarn group with 1 update in the /packages/router directory: [next](https://github.com/vercel/next.js). Updates `axios` from 1.5.1 to 1.12.0 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.5.1...v1.12.0) Updates `dompurify` from 3.0.6 to 3.2.4 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.0.6...3.2.4) Updates `formidable` from 3.5.1 to 3.5.4 - [Release notes](https://github.com/node-formidable/formidable/releases) - [Changelog](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md) - [Commits](https://github.com/node-formidable/formidable/commits) Updates `next` from 13.4.12 to 14.2.35 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.12...v14.2.35) Updates `next-auth` from 4.22.1 to 4.24.12 - [Release notes](https://github.com/nextauthjs/next-auth/releases) - [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.22.1...next-auth@4.24.12) Updates `playwright` from 1.55.0 to 1.55.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.55.0...v1.55.1) Updates `postcss` from 8.4.26 to 8.5.3 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.26...8.5.3) Updates `@mozilla/readability` from 0.4.4 to 0.6.0 - [Changelog](https://github.com/mozilla/readability/blob/main/CHANGELOG.md) - [Commits](https://github.com/mozilla/readability/compare/0.4.4...0.6.0) Updates `ai` from 4.3.9 to 5.0.52 - [Release notes](https://github.com/vercel/ai/releases) - [Changelog](https://github.com/vercel/ai/blob/main/CHANGELOG.md) - [Commits](https://github.com/vercel/ai/compare/ai@4.3.9...ai@5.0.52) Updates `nodemailer` from 6.9.3 to 7.0.11 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.3...v7.0.11) Updates `@babel/runtime` from 7.21.5 to 7.27.0 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime) Updates `brace-expansion` from 1.1.11 to 1.1.12 - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12) Updates `braces` from 3.0.2 to 3.0.3 - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) Updates `follow-redirects` from 1.15.3 to 1.15.11 - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.11) Updates `form-data` from 3.0.3 to 3.0.4 - [Release notes](https://github.com/form-data/form-data/releases) - [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v3.0.3...v3.0.4) Updates `jose` from 4.14.4 to 4.15.9 - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/v4.15.9/CHANGELOG.md) - [Commits](https://github.com/panva/jose/compare/v4.14.4...v4.15.9) Updates `js-yaml` from 3.14.1 to 3.14.2 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2) Updates `micromatch` from 4.0.5 to 4.0.8 - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) Updates `min-document` from 2.19.0 to 2.19.2 - [Commits](https://github.com/Raynos/min-document/compare/v2.19.0...v2.19.2) Updates `nanoid` from 3.3.6 to 3.3.8 - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.6...3.3.8) Updates `node-forge` from 1.3.1 to 1.3.3 - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.3) Updates `tar` from 6.1.13 to 6.2.1 - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v6.1.13...v6.2.1) Updates `next` from 13.4.12 to 14.2.35 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.12...v14.2.35) Updates `@mozilla/readability` from 0.4.4 to 0.6.0 - [Changelog](https://github.com/mozilla/readability/blob/main/CHANGELOG.md) - [Commits](https://github.com/mozilla/readability/compare/0.4.4...0.6.0) Updates `ai` from 4.3.19 to 5.0.113 - [Release notes](https://github.com/vercel/ai/releases) - [Changelog](https://github.com/vercel/ai/blob/main/CHANGELOG.md) - [Commits](https://github.com/vercel/ai/compare/ai@4.3.9...ai@5.0.52) Updates `nodemailer` from 6.10.1 to 7.0.11 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.3...v7.0.11) Updates `next` from 13.4.12 to 14.2.35 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.12...v14.2.35) --- updated-dependencies: - dependency-name: axios dependency-version: 1.12.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: dompurify dependency-version: 3.2.4 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: formidable dependency-version: 3.5.4 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: next dependency-version: 14.2.35 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: next-auth dependency-version: 4.24.12 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: playwright dependency-version: 1.55.1 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: postcss dependency-version: 8.5.3 dependency-type: direct:development dependency-group: npm_and_yarn - dependency-name: "@mozilla/readability" dependency-version: 0.6.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: ai dependency-version: 5.0.52 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: nodemailer dependency-version: 7.0.11 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: "@babel/runtime" dependency-version: 7.27.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: brace-expansion dependency-version: 1.1.12 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: braces dependency-version: 3.0.3 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: follow-redirects dependency-version: 1.15.11 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: form-data dependency-version: 3.0.4 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: jose dependency-version: 4.15.9 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: micromatch dependency-version: 4.0.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: min-document dependency-version: 2.19.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: nanoid dependency-version: 3.3.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: node-forge dependency-version: 1.3.3 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: tar dependency-version: 6.2.1 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: next dependency-version: 14.2.35 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: "@mozilla/readability" dependency-version: 0.6.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: ai dependency-version: 5.0.113 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: nodemailer dependency-version: 7.0.11 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: next dependency-version: 14.2.35 dependency-type: direct:production dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> * bug fixes and improvements * always show navbar in reader view * bug fix and small performance improvement * minor fix * Refactor link selection management and bulk actions - Replaced the use of selectedLinks with selectedIds in the link store for better performance and clarity. - Updated LinkListOptions, BulkDeleteLinksModal, and BulkEditLinksModal components to utilize the new selection management. - Modified LinkCard, LinkMasonry, and LinkList components to handle selection state through props. - Enhanced updateLinks API to support bulk updates with improved tag management. - Cleaned up unused imports and code related to previous selection methods. * move refetching logic to Links component * move disableDraggable and user hook out of each card to improve efficiency * cleaner code * memoize components and increase performance * fix: update announcement links to use the correct domain * feat: add favicon field to Link model + update packages + bug fix * feat: implement favicon fetching API and update Link model for favicon support * feat: add priority attribute to Image components in Sidebar * Refactor pages to use consistent layout handling (yes, I forgot to do that until now :P) * bump version * Refactor setting pages to use consistent layout handling * upgrade yarn to 4.12.0 * fix DnD bug * Enhance announcement handling by adding support for announcement messages * slimmed down the docker image size * update Node and yarn versions in playwright tests workflow * small fix * fix attempt 2 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
663 lines
22 KiB
TypeScript
663 lines
22 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
LinkIncludingShortenedCollectionAndTags,
|
|
ArchivedFormat,
|
|
} from "@linkwarden/types";
|
|
import Link from "next/link";
|
|
import {
|
|
atLeastOneFormatAvailable,
|
|
formatAvailable,
|
|
} from "@linkwarden/lib/formatStats";
|
|
import PreservedFormatRow from "@/components/PreserverdFormatRow";
|
|
import getPublicUserData from "@/lib/client/getPublicUserData";
|
|
import { useTranslation } from "next-i18next";
|
|
import { BeatLoader } from "react-spinners";
|
|
import { useUser } from "@linkwarden/router/user";
|
|
import { useUpdateLink, useUpdateFile } from "@linkwarden/router/links";
|
|
import LinkIcon from "./LinkViews/LinkComponents/LinkIcon";
|
|
import CopyButton from "./CopyButton";
|
|
import { useRouter } from "next/router";
|
|
import Icon from "./Icon";
|
|
import { IconWeight } from "@phosphor-icons/react";
|
|
import Image from "next/image";
|
|
import clsx from "clsx";
|
|
import toast from "react-hot-toast";
|
|
import CollectionSelection from "./InputSelect/CollectionSelection";
|
|
import TagSelection from "./InputSelect/TagSelection";
|
|
import unescapeString from "@/lib/client/unescapeString";
|
|
import IconPopover from "./IconPopover";
|
|
import TextInput from "./TextInput";
|
|
import usePermissions from "@/hooks/usePermissions";
|
|
import oklchVariableToHex from "@/lib/client/oklchVariableToHex";
|
|
import { Button } from "./ui/button";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { Separator } from "./ui/separator";
|
|
|
|
type Props = {
|
|
className?: string;
|
|
activeLink: LinkIncludingShortenedCollectionAndTags;
|
|
standalone?: boolean;
|
|
mode?: "view" | "edit";
|
|
setMode?: Function;
|
|
onUpdateArchive?: () => void;
|
|
};
|
|
|
|
export default function LinkDetails({
|
|
className,
|
|
activeLink,
|
|
standalone,
|
|
mode = "view",
|
|
setMode,
|
|
onUpdateArchive,
|
|
}: Props) {
|
|
const [link, setLink] =
|
|
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
|
|
|
useEffect(() => {
|
|
setLink(activeLink);
|
|
}, [activeLink]);
|
|
|
|
const permissions = usePermissions(link.collection.id as number);
|
|
|
|
const { t } = useTranslation();
|
|
const { data: user } = useUser();
|
|
const router = useRouter();
|
|
|
|
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
|
|
|
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,
|
|
});
|
|
|
|
useEffect(() => {
|
|
const fetchOwner = async () => {
|
|
if (link.collection.ownerId !== user?.id) {
|
|
const owner = await getPublicUserData(
|
|
link.collection.ownerId as number
|
|
);
|
|
setCollectionOwner(owner);
|
|
} else if (link.collection.ownerId === user?.id) {
|
|
setCollectionOwner({
|
|
id: user?.id as number,
|
|
name: user?.name as string,
|
|
username: user?.username as string,
|
|
image: user?.image as string,
|
|
archiveAsScreenshot: user?.archiveAsScreenshot as boolean,
|
|
archiveAsMonolith: user?.archiveAsScreenshot as boolean,
|
|
archiveAsPDF: user?.archiveAsPDF as boolean,
|
|
});
|
|
}
|
|
};
|
|
|
|
fetchOwner();
|
|
}, [link.collection.ownerId]);
|
|
|
|
const isReady = () => {
|
|
return (
|
|
link &&
|
|
(collectionOwner.archiveAsScreenshot === true ? link.pdf : true) &&
|
|
(collectionOwner.archiveAsMonolith === true ? link.monolith : true) &&
|
|
(collectionOwner.archiveAsPDF === true ? link.pdf : true) &&
|
|
link.readable
|
|
);
|
|
};
|
|
|
|
const updateLink = useUpdateLink();
|
|
const updateFile = useUpdateFile();
|
|
|
|
const submit = async (e?: any) => {
|
|
e?.preventDefault();
|
|
|
|
const { updatedAt: b, ...oldLink } = activeLink;
|
|
const { updatedAt: a, ...newLink } = link;
|
|
|
|
if (JSON.stringify(oldLink) === JSON.stringify(newLink)) {
|
|
return;
|
|
}
|
|
|
|
const load = toast.loading(t("updating"));
|
|
|
|
await updateLink.mutateAsync(link, {
|
|
onSettled: (data, error) => {
|
|
toast.dismiss(load);
|
|
|
|
if (error) {
|
|
toast.error(error.message);
|
|
} else {
|
|
toast.success(t("updated"));
|
|
setMode && setMode("view");
|
|
setLink(data);
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const setCollection = (e: any) => {
|
|
if (e?.__isNew__) e.value = null;
|
|
setLink({
|
|
...link,
|
|
collection: { id: e?.value, name: e?.label, ownerId: e?.ownerId },
|
|
});
|
|
};
|
|
|
|
const setTags = (e: any) => {
|
|
const tagNames = e.map((e: any) => ({ name: e.label }));
|
|
setLink({ ...link, tags: tagNames });
|
|
};
|
|
|
|
const [iconPopover, setIconPopover] = useState(false);
|
|
|
|
return (
|
|
<div className={clsx(className)} data-vaul-no-drag>
|
|
<div
|
|
className={clsx(
|
|
standalone && "sm:border sm:border-neutral-content sm:rounded-xl p-5"
|
|
)}
|
|
>
|
|
<div
|
|
className={clsx(
|
|
"overflow-hidden select-none relative group h-40 opacity-80",
|
|
standalone
|
|
? "sm:max-w-xl -mx-5 -mt-5 sm:rounded-t-xl"
|
|
: "-mx-4 -mt-4"
|
|
)}
|
|
>
|
|
{formatAvailable(link, "preview") ? (
|
|
<Image
|
|
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true&updatedAt=${link.updatedAt}`}
|
|
width={1280}
|
|
height={720}
|
|
alt=""
|
|
className="object-cover scale-105 object-center h-full"
|
|
style={{
|
|
filter: "blur(1px)",
|
|
}}
|
|
onError={(e) => {
|
|
const target = e.target as HTMLElement;
|
|
target.style.display = "none";
|
|
}}
|
|
unoptimized
|
|
/>
|
|
) : link.preview === "unavailable" ? (
|
|
<div className="bg-gray-50 duration-100 h-40"></div>
|
|
) : (
|
|
<div className="h-40 skeleton rounded-none"></div>
|
|
)}
|
|
|
|
{!standalone &&
|
|
(permissions === true || permissions?.canUpdate) &&
|
|
!isPublicRoute && (
|
|
<div className="absolute top-0 bottom-0 left-0 right-0 opacity-0 group-hover:opacity-100 duration-100 flex justify-end items-end">
|
|
<Button
|
|
className="mb-2 mr-3 opacity-50 hover:opacity-100 p-0"
|
|
size="sm"
|
|
>
|
|
<label className="cursor-pointer py-1 px-2 w-full">
|
|
{t("upload_banner")}
|
|
<input
|
|
type="file"
|
|
accept="image/jpg, image/jpeg"
|
|
onChange={async (e) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
const load = toast.loading(t("updating"));
|
|
|
|
await updateFile.mutateAsync(
|
|
{
|
|
linkId: link.id as number,
|
|
file,
|
|
},
|
|
{
|
|
onSettled: (data, error) => {
|
|
toast.dismiss(load);
|
|
|
|
if (error) {
|
|
toast.error(error.message);
|
|
} else {
|
|
toast.success(t("updated"));
|
|
setLink({ updatedAt: data.updatedAt, ...link });
|
|
}
|
|
},
|
|
}
|
|
);
|
|
}}
|
|
className="hidden"
|
|
/>
|
|
</label>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{!standalone &&
|
|
(permissions === true || permissions?.canUpdate) &&
|
|
!isPublicRoute ? (
|
|
<div className="-mt-14 ml-8 relative w-fit pb-2">
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger>
|
|
<LinkIcon
|
|
link={link}
|
|
className="hover:bg-opacity-70 duration-100 cursor-pointer"
|
|
onClick={() => setIconPopover(true)}
|
|
/>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
<p>{t("change_icon")}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
|
|
{iconPopover && (
|
|
<IconPopover
|
|
color={link.color || oklchVariableToHex("--p")}
|
|
setColor={(color: string) => setLink({ ...link, color })}
|
|
weight={(link.iconWeight || "regular") as IconWeight}
|
|
setWeight={(iconWeight: string) =>
|
|
setLink({ ...link, iconWeight })
|
|
}
|
|
iconName={link.icon as string}
|
|
setIconName={(icon: string) => setLink({ ...link, icon })}
|
|
reset={() =>
|
|
setLink({
|
|
...link,
|
|
color: "",
|
|
icon: "",
|
|
iconWeight: "",
|
|
})
|
|
}
|
|
className="top-12"
|
|
onClose={() => {
|
|
setIconPopover(false);
|
|
submit();
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="-mt-14 ml-8 relative w-fit pb-2">
|
|
<LinkIcon link={link} onClick={() => setIconPopover(true)} />
|
|
</div>
|
|
)}
|
|
|
|
<div className="sm:px-8 p-5 pb-8 pt-2">
|
|
{mode === "view" && (
|
|
<div className="text-xl mt-2 pr-7">
|
|
<p
|
|
className={clsx("relative w-fit", !link.name && "text-neutral")}
|
|
>
|
|
{unescapeString(link.name) || t("untitled")}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{mode === "edit" && (
|
|
<>
|
|
<br />
|
|
|
|
<div>
|
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
|
{t("name")}
|
|
</p>
|
|
<TextInput
|
|
value={link.name}
|
|
onChange={(e) => setLink({ ...link, name: e.target.value })}
|
|
placeholder={t("placeholder_example_link")}
|
|
className="bg-base-200"
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{link.url && mode === "view" ? (
|
|
<>
|
|
<br />
|
|
|
|
<p className="text-sm mb-2 text-neutral">{t("link")}</p>
|
|
|
|
<div className="relative">
|
|
<div className="rounded-md p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14">
|
|
<Link href={link.url} title={link.url} target="_blank">
|
|
{link.url}
|
|
</Link>
|
|
<div className="absolute right-0 px-2 bg-base-200">
|
|
<CopyButton text={link.url} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : activeLink.url ? (
|
|
<>
|
|
<br />
|
|
|
|
<div>
|
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
|
{t("link")}
|
|
</p>
|
|
<TextInput
|
|
value={link.url || ""}
|
|
onChange={(e) => setLink({ ...link, url: e.target.value })}
|
|
placeholder={t("placeholder_example_link")}
|
|
className="bg-base-200"
|
|
/>
|
|
</div>
|
|
</>
|
|
) : undefined}
|
|
|
|
<br />
|
|
|
|
<div className="relative">
|
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
|
{t("collection")}
|
|
</p>
|
|
|
|
{mode === "view" ? (
|
|
<div className="relative">
|
|
<Link
|
|
href={
|
|
isPublicRoute
|
|
? `/public/collections/${link.collection.id}`
|
|
: `/collections/${link.collection.id}`
|
|
}
|
|
className="rounded-md p-2 bg-base-200 border border-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14"
|
|
>
|
|
<p>{link.collection.name}</p>
|
|
<div className="absolute right-0 px-2 bg-base-200">
|
|
{link.collection.icon ? (
|
|
<Icon
|
|
icon={link.collection.icon}
|
|
size={30}
|
|
weight={
|
|
(link.collection.iconWeight ||
|
|
"regular") as IconWeight
|
|
}
|
|
color={link.collection.color}
|
|
/>
|
|
) : (
|
|
<i
|
|
className="bi-folder-fill text-xl"
|
|
style={{ color: link.collection.color }}
|
|
></i>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<CollectionSelection
|
|
onChange={setCollection}
|
|
defaultValue={
|
|
link.collection.id
|
|
? { value: link.collection.id, label: link.collection.name }
|
|
: { value: null as unknown as number, label: "Unorganized" }
|
|
}
|
|
creatable={false}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<br />
|
|
|
|
<div className="relative">
|
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
|
{t("tags")}
|
|
</p>
|
|
|
|
{mode === "view" ? (
|
|
<div className="flex gap-2 flex-wrap rounded-md p-2 bg-base-200 border border-base-200 w-full text-xs">
|
|
{link.tags && link.tags[0] ? (
|
|
link.tags.map((tag) =>
|
|
isPublicRoute ? (
|
|
<div
|
|
key={tag.id}
|
|
className="bg-base-200 p-1 hover:bg-neutral-content rounded-md duration-100"
|
|
>
|
|
{tag.name}
|
|
</div>
|
|
) : (
|
|
<Link
|
|
href={"/tags/" + tag.id}
|
|
key={tag.id}
|
|
className="bg-base-200 py-1 px-2 hover:bg-neutral-content rounded-sm duration-150"
|
|
>
|
|
{tag.name}
|
|
</Link>
|
|
)
|
|
)
|
|
) : (
|
|
<div className="text-neutral text-base">{t("no_tags")}</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<TagSelection
|
|
onChange={setTags}
|
|
defaultValue={link.tags.map((e) => ({
|
|
label: e.name,
|
|
value: e.id,
|
|
}))}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<br />
|
|
|
|
<div className="relative">
|
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
|
{t("description")}
|
|
</p>
|
|
|
|
{mode === "view" ? (
|
|
<div className="rounded-md p-2 bg-base-200 hyphens-auto">
|
|
{link.description ? (
|
|
<p>{link.description}</p>
|
|
) : (
|
|
<p className="text-neutral">{t("no_description_provided")}</p>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<textarea
|
|
value={unescapeString(link.description) as string}
|
|
onChange={(e) =>
|
|
setLink({ ...link, description: e.target.value })
|
|
}
|
|
placeholder={t("link_description_placeholder")}
|
|
className="resize-none w-full rounded-md p-2 h-32 border-neutral-content bg-base-200 focus:border-primary border-solid border outline-none duration-100"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{mode === "view" && (
|
|
<div>
|
|
<br />
|
|
|
|
<div className="flex gap-1 items-center mb-2">
|
|
<p
|
|
className="text-sm text-neutral"
|
|
title={t("available_formats")}
|
|
>
|
|
{link.url ? t("preserved_formats") : t("content")}
|
|
</p>
|
|
|
|
{onUpdateArchive &&
|
|
(permissions === true || permissions?.canUpdate) &&
|
|
!isPublicRoute &&
|
|
link.url && (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-neutral"
|
|
onClick={onUpdateArchive}
|
|
>
|
|
<i className="bi-arrow-clockwise text-sm" />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
<p>{t("refresh_preserved_formats")}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
)}
|
|
</div>
|
|
|
|
<div className={`flex flex-col rounded-md p-3 bg-base-200`}>
|
|
{formatAvailable(link, "monolith") ? (
|
|
<>
|
|
<PreservedFormatRow
|
|
name={t("webpage")}
|
|
icon={"bi-filetype-html"}
|
|
format={ArchivedFormat.monolith}
|
|
link={link}
|
|
downloadable={true}
|
|
/>
|
|
<Separator className="my-3" />
|
|
</>
|
|
) : undefined}
|
|
|
|
{formatAvailable(link, "image") ? (
|
|
<>
|
|
<PreservedFormatRow
|
|
name={t("screenshot")}
|
|
icon={"bi-file-earmark-image"}
|
|
format={
|
|
link?.image?.endsWith("png")
|
|
? ArchivedFormat.png
|
|
: ArchivedFormat.jpeg
|
|
}
|
|
link={link}
|
|
downloadable={true}
|
|
/>
|
|
<Separator className="my-3" />
|
|
</>
|
|
) : undefined}
|
|
|
|
{formatAvailable(link, "pdf") ? (
|
|
<>
|
|
<PreservedFormatRow
|
|
name={t("pdf")}
|
|
icon={"bi-file-earmark-pdf"}
|
|
format={ArchivedFormat.pdf}
|
|
link={link}
|
|
downloadable={true}
|
|
/>
|
|
<Separator className="my-3" />
|
|
</>
|
|
) : undefined}
|
|
|
|
{formatAvailable(link, "readable") ? (
|
|
<>
|
|
<PreservedFormatRow
|
|
name={t("readable")}
|
|
icon={"bi-file-earmark-text"}
|
|
format={ArchivedFormat.readability}
|
|
link={link}
|
|
/>
|
|
<Separator className="my-3" />
|
|
</>
|
|
) : undefined}
|
|
|
|
{!isReady() && !atLeastOneFormatAvailable(link) ? (
|
|
<div
|
|
className={`w-full h-full flex flex-col justify-center p-10`}
|
|
>
|
|
<BeatLoader
|
|
color="oklch(var(--p))"
|
|
className="mx-auto mb-3"
|
|
size={30}
|
|
/>
|
|
|
|
<p className="text-center text-xl">
|
|
{t("preservation_in_queue")}
|
|
</p>
|
|
<p className="text-center text-lg">
|
|
{t("check_back_later")}
|
|
</p>
|
|
</div>
|
|
) : link.url &&
|
|
!isReady() &&
|
|
atLeastOneFormatAvailable(link) ? (
|
|
<div
|
|
className={`w-full h-full flex flex-col justify-center p-5`}
|
|
>
|
|
<BeatLoader
|
|
color="oklch(var(--p))"
|
|
className="mx-auto mb-3"
|
|
size={20}
|
|
/>
|
|
<p className="text-center">{t("there_are_more_formats")}</p>
|
|
<p className="text-center text-sm">
|
|
{t("check_back_later")}
|
|
</p>
|
|
</div>
|
|
) : undefined}
|
|
|
|
{link.url && (
|
|
<Link
|
|
href={`https://web.archive.org/web/${link?.url?.replace(
|
|
/(^\w+:|^)\/\//,
|
|
""
|
|
)}`}
|
|
target="_blank"
|
|
className="text-neutral mx-auto duration-100 hover:opacity-60 flex gap-2 w-1/2 justify-center items-center text-sm"
|
|
>
|
|
<p className="whitespace-nowrap">
|
|
{t("view_latest_snapshot")}
|
|
</p>
|
|
<i className="bi-box-arrow-up-right" />
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{mode === "view" ? (
|
|
<>
|
|
<br />
|
|
|
|
<p className="text-neutral text-xs text-center">
|
|
{t("saved")}{" "}
|
|
{new Date(link.createdAt || "").toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
})}{" "}
|
|
at{" "}
|
|
{new Date(link.createdAt || "").toLocaleTimeString("en-US", {
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
})}
|
|
</p>
|
|
</>
|
|
) : (
|
|
<>
|
|
<br />
|
|
<div className="flex justify-end items-center">
|
|
<Button
|
|
variant="accent"
|
|
disabled={JSON.stringify(activeLink) === JSON.stringify(link)}
|
|
onClick={submit}
|
|
>
|
|
{t("save_changes")}
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|