Files
linkwarden/apps/web/pages/settings/preference.tsx
Daniel 389e5df117 Chore/tech debts (#1536)
* 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>
2025-12-21 18:09:05 -05:00

628 lines
21 KiB
TypeScript

import SettingsLayout from "@/layouts/SettingsLayout";
import { useState, useEffect, ReactElement } from "react";
import { toast } from "react-hot-toast";
import Checkbox from "@/components/Checkbox";
import useLocalSettingsStore from "@/store/localSettings";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { AiTaggingMethod, LinksRouteTo } from "@linkwarden/prisma/client";
import {
useUpdateUser,
useUpdateUserPreference,
useUser,
} from "@linkwarden/router/user";
import { useConfig } from "@linkwarden/router/config";
import { useTags, useUpsertTags } from "@linkwarden/router/tags";
import TagSelection from "@/components/InputSelect/TagSelection";
import { useArchivalTags } from "@/hooks/useArchivalTags";
import { isArchivalTag } from "@linkwarden/lib";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Separator } from "@/components/ui/separator";
import { NextPageWithLayout } from "../_app";
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { settings, updateSettings } = useLocalSettingsStore();
const updateUserPreference = useUpdateUserPreference();
const [submitLoader, setSubmitLoader] = useState(false);
const { data: account } = useUser() as any;
const { data: tags } = useTags();
const upsertTags = useUpsertTags();
const {
ARCHIVAL_OPTIONS,
archivalTags,
options,
addTags,
toggleOption,
removeTag,
} = useArchivalTags(tags ? tags : []);
const updateUser = useUpdateUser();
const [user, setUser] = useState(account);
const [preventDuplicateLinks, setPreventDuplicateLinks] = useState<boolean>(
account.preventDuplicateLinks || false
);
const [archiveAsScreenshot, setArchiveAsScreenshot] = useState<boolean>(
account.archiveAsScreenshot || false
);
const [archiveAsMonolith, setArchiveAsMonolith] = useState<boolean>(
account.archiveAsMonolith || false
);
const [archiveAsPDF, setArchiveAsPDF] = useState<boolean>(
account.archiveAsPDF || false
);
const [archiveAsReadable, setArchiveAsReadable] = useState<boolean>(false);
const [archiveAsWaybackMachine, setArchiveAsWaybackMachine] =
useState<boolean>(account.archiveAsWaybackMachine || false);
const [dashboardPinnedLinks, setDashboardPinnedLinks] = useState<boolean>(
account.dashboardPinnedLinks || false
);
const [linksRouteTo, setLinksRouteTo] = useState(account.linksRouteTo);
const [aiTaggingMethod, setAiTaggingMethod] = useState<AiTaggingMethod>(
account.aiTaggingMethod
);
const [aiPredefinedTags, setAiPredefinedTags] = useState<string[]>();
const [aiTagExistingLinks, setAiTagExistingLinks] = useState<boolean>(
account.aiTagExistingLinks ?? false
);
const [hasAccountChanges, setHasAccountChanges] = useState(false);
const [hasTagChanges, setHasTagChanges] = useState(false);
const { data: config } = useConfig();
useEffect(() => {
setUser({
...account,
archiveAsScreenshot,
archiveAsMonolith,
archiveAsPDF,
archiveAsReadable,
archiveAsWaybackMachine,
linksRouteTo,
preventDuplicateLinks,
aiTaggingMethod,
aiPredefinedTags,
aiTagExistingLinks,
dashboardPinnedLinks,
});
}, [
account,
archiveAsScreenshot,
archiveAsMonolith,
archiveAsPDF,
archiveAsReadable,
archiveAsWaybackMachine,
linksRouteTo,
preventDuplicateLinks,
aiTaggingMethod,
aiPredefinedTags,
aiTagExistingLinks,
]);
function objectIsEmpty(obj: object) {
return Object.keys(obj).length === 0;
}
useEffect(() => {
if (!objectIsEmpty(account)) {
setArchiveAsScreenshot(account.archiveAsScreenshot);
setArchiveAsMonolith(account.archiveAsMonolith);
setArchiveAsPDF(account.archiveAsPDF);
setArchiveAsReadable(account.archiveAsReadable);
setArchiveAsWaybackMachine(account.archiveAsWaybackMachine);
setLinksRouteTo(account.linksRouteTo);
setPreventDuplicateLinks(account.preventDuplicateLinks);
setAiTaggingMethod(account.aiTaggingMethod);
setAiPredefinedTags(account.aiPredefinedTags);
setAiTagExistingLinks(account.aiTagExistingLinks);
}
}, [account]);
useEffect(() => {
const relevantKeys = [
"archiveAsScreenshot",
"archiveAsMonolith",
"archiveAsPDF",
"archiveAsReadable",
"archiveAsWaybackMachine",
"linksRouteTo",
"preventDuplicateLinks",
"aiTaggingMethod",
"aiPredefinedTags",
"aiTagExistingLinks",
];
const hasChanges = relevantKeys.some((key) => account[key] !== user[key]);
setHasAccountChanges(hasChanges);
}, [account, user]);
useEffect(() => {
if (!tags || !archivalTags) return;
const hasChanges = archivalTags.some((newTag) => {
const originalTag = tags.find((t) => t.name === newTag.label);
if (!originalTag) return true;
return (
newTag.archiveAsScreenshot !== originalTag.archiveAsScreenshot ||
newTag.archiveAsMonolith !== originalTag.archiveAsMonolith ||
newTag.archiveAsPDF !== originalTag.archiveAsPDF ||
newTag.archiveAsReadable !== originalTag.archiveAsReadable ||
newTag.archiveAsWaybackMachine !==
originalTag.archiveAsWaybackMachine ||
newTag.aiTag !== originalTag.aiTag
);
});
setHasTagChanges(hasChanges);
}, [archivalTags, tags]);
const submit = async () => {
setSubmitLoader(true);
const load = toast.loading(t("applying_settings"));
try {
const promises = [];
if (hasAccountChanges) promises.push(updateUser.mutateAsync({ ...user }));
if (hasTagChanges) promises.push(upsertTags.mutateAsync(archivalTags));
if (promises.length > 0) {
await Promise.all(promises);
toast.success(t("settings_applied"));
}
} catch (error: any) {
toast.error(error.message);
} finally {
setSubmitLoader(false);
toast.dismiss(load);
}
};
return (
<>
<p className="capitalize text-3xl font-thin inline">{t("preference")}</p>
<Separator className="my-3" />
<div className="flex flex-col gap-5">
<div>
<div className="flex gap-3 w-full">
{[
{
theme: "dark",
icon: "bi-moon-fill",
bgColor: "bg-black",
textColor: "text-white",
activeColor: "text-primary",
},
{
theme: "light",
icon: "bi-sun-fill",
bgColor: "bg-white",
textColor: "text-black",
activeColor: "text-primary",
},
].map(({ theme, icon, bgColor, textColor, activeColor }) => (
<div
key={theme}
className={`w-full text-center outline-solid outline-neutral-content outline h-20 duration-100 rounded-xl flex items-center justify-center cursor-pointer select-none ${bgColor} ${
account.theme === theme
? `outline-primary ${activeColor}`
: textColor
}`}
onClick={() => {
updateUserPreference.mutate({ theme: theme as any });
document.documentElement.setAttribute("data-theme", theme);
}}
>
<i className={`${icon} text-3xl`}></i>
<p className="ml-2 text-xl">{t(theme)}</p>
</div>
))}
</div>
<div className="mt-3">
<div className="flex gap-3 w-3/4 mx-auto">
{[
"--default",
"--red",
"--rose",
"--yellow",
"--green",
"--orange",
"--zinc",
].map((color) => (
<div
key={color}
className="relative rounded-full w-full aspect-square cursor-pointer"
style={{ backgroundColor: `oklch(var(${color}))` }}
onClick={() => updateSettings({ color })}
>
{settings.color === color && (
<i className="bi-check2 text-xl text-base-100 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"></i>
)}
</div>
))}
</div>
</div>
</div>
{config?.AI_ENABLED && (
<div>
<p className="capitalize text-3xl font-thin inline">
{t("ai_settings")}
</p>
<Separator className="my-3" />
<p>{t("ai_tagging_method")}</p>
<div className="p-3">
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="ai-tagging-method-radio"
className="radio checked:bg-primary"
value="DISABLED"
checked={aiTaggingMethod === AiTaggingMethod.DISABLED}
onChange={() => setAiTaggingMethod(AiTaggingMethod.DISABLED)}
/>
<span className="label-text">{t("disabled")}</span>
</label>
<p className="text-neutral text-sm pl-5">
{t("ai_tagging_disabled_desc")}
</p>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="ai-tagging-method-radio"
className="radio checked:bg-primary"
value="GENERATE"
checked={aiTaggingMethod === AiTaggingMethod.GENERATE}
onChange={() => setAiTaggingMethod(AiTaggingMethod.GENERATE)}
/>
<span className="label-text">{t("auto_generate_tags")}</span>
</label>
<p className="text-neutral text-sm pl-5">
{t("auto_generate_tags_desc")}
</p>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="ai-tagging-method-radio"
className="radio checked:bg-primary"
value="EXISTING"
checked={aiTaggingMethod === AiTaggingMethod.EXISTING}
onChange={() => setAiTaggingMethod(AiTaggingMethod.EXISTING)}
/>
<span className="label-text">
{t("based_on_existing_tags")}
</span>
</label>
<p className="text-neutral text-sm pl-5">
{t("based_on_existing_tags_desc")}
</p>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="ai-tagging-method-radio"
className="radio checked:bg-primary"
value="PREDEFINED"
checked={aiTaggingMethod === AiTaggingMethod.PREDEFINED}
onChange={() =>
setAiTaggingMethod(AiTaggingMethod.PREDEFINED)
}
/>
<span className="label-text">
{t("based_on_predefined_tags")}
</span>
</label>
<div className="pl-5">
<p className="text-neutral text-sm mb-2">
{t("based_on_predefined_tags_desc")}
</p>
{aiPredefinedTags && (
<TagSelection
onChange={(e: any) => {
setAiPredefinedTags(e.map((e: any) => e.label));
}}
defaultValue={aiPredefinedTags
.map((e) => ({ label: e }))
.filter((e) => e.label !== "")}
/>
)}
</div>
</div>
<div
className={`mb-3 ${
aiTaggingMethod === AiTaggingMethod.DISABLED ? "opacity-50" : ""
}`}
>
<Checkbox
label={t("generate_tags_for_existing_links")}
state={aiTagExistingLinks}
onClick={() =>
aiTaggingMethod !== AiTaggingMethod.DISABLED &&
setAiTagExistingLinks(!aiTagExistingLinks)
}
disabled={aiTaggingMethod === AiTaggingMethod.DISABLED}
/>
</div>
</div>
)}
<div>
<p className="capitalize text-3xl font-thin inline">
{t("archive_settings")}
</p>
<Separator className="my-3" />
<p>{t("formats_to_archive")}</p>
<div className="p-3">
<Checkbox
label={t("screenshot")}
state={archiveAsScreenshot}
onClick={() => setArchiveAsScreenshot(!archiveAsScreenshot)}
/>
<Checkbox
label={t("webpage")}
state={archiveAsMonolith}
onClick={() => setArchiveAsMonolith(!archiveAsMonolith)}
/>
<Checkbox
label={t("pdf")}
state={archiveAsPDF}
onClick={() => setArchiveAsPDF(!archiveAsPDF)}
/>
<Checkbox
label={t("readable")}
state={archiveAsReadable}
onClick={() => setArchiveAsReadable(!archiveAsReadable)}
/>
<Checkbox
label={t("archive_org_snapshot")}
state={archiveAsWaybackMachine}
onClick={() =>
setArchiveAsWaybackMachine(!archiveAsWaybackMachine)
}
/>
</div>
<div className="max-w-full">
<p>{t("tag_preservation_rule_label")}</p>
</div>
<div className="p-3">
<TagSelection
isArchivalSelection
onChange={addTags}
options={options}
/>
<div className="flex flex-col gap-2">
{archivalTags &&
archivalTags.filter(isArchivalTag).map((tag) => (
<div
key={tag.label}
className="w-full bg-base-200 py-2 px-4 rounded-md first-of-type:mt-4 max-w-full shadow"
>
<div className="flex justify-between gap-1">
<span className="block sm:text-lg truncate max-w-sm">
{tag.label}
</span>
<Button
variant="ghost"
size="icon"
onClick={() => removeTag(tag)}
className="hover:text-error"
>
<i className="bi-x text-lg leading-none"></i>
</Button>
</div>
<div className="flex flex-wrap items-center justify-between gap-1 mt-1">
<p className="text-sm">{t("preservation_rules")}</p>
<div className="flex gap-1">
{ARCHIVAL_OPTIONS.map(({ type, icon, label }) => (
<TooltipProvider key={type}>
<Tooltip>
<TooltipTrigger>
<Button
variant="ghost"
size="icon"
onClick={() => toggleOption(tag, type)}
className={
tag[type]
? "bg-primary hover:bg-primary text-primary-foreground hover:text-primary-foreground"
: ""
}
>
<i
className={`${icon} text-lg leading-none`}
></i>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{label}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
))}
</div>
</div>
</div>
))}
</div>
</div>
</div>
<div>
<p className="capitalize text-3xl font-thin inline">
{t("link_settings")}
</p>
<Separator className="my-3" />
<div className="mb-3">
<Checkbox
label={t("prevent_duplicate_links")}
state={preventDuplicateLinks}
onClick={() => setPreventDuplicateLinks(!preventDuplicateLinks)}
/>
</div>
<p>{t("clicking_on_links_should")}</p>
<div className="p-3">
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="link-preference-radio"
className="radio checked:bg-primary"
value="Original"
checked={linksRouteTo === LinksRouteTo.ORIGINAL}
onChange={() => setLinksRouteTo(LinksRouteTo.ORIGINAL)}
/>
<span className="label-text">{t("open_original_content")}</span>
</label>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="link-preference-radio"
className="radio checked:bg-primary"
value="Details"
checked={linksRouteTo === LinksRouteTo.DETAILS}
onChange={() => setLinksRouteTo(LinksRouteTo.DETAILS)}
/>
<span className="label-text">{t("show_link_details")}</span>
</label>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="link-preference-radio"
className="radio checked:bg-primary"
value="PDF"
checked={linksRouteTo === LinksRouteTo.PDF}
onChange={() => setLinksRouteTo(LinksRouteTo.PDF)}
/>
<span className="label-text">{t("open_pdf_if_available")}</span>
</label>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="link-preference-radio"
className="radio checked:bg-primary"
value="Readable"
checked={linksRouteTo === LinksRouteTo.READABLE}
onChange={() => setLinksRouteTo(LinksRouteTo.READABLE)}
/>
<span className="label-text">
{t("open_readable_if_available")}
</span>
</label>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="link-preference-radio"
className="radio checked:bg-primary"
value="Monolith"
checked={linksRouteTo === LinksRouteTo.MONOLITH}
onChange={() => setLinksRouteTo(LinksRouteTo.MONOLITH)}
/>
<span className="label-text">
{t("open_webpage_if_available")}
</span>
</label>
<label
className="label cursor-pointer flex gap-2 justify-start w-fit"
tabIndex={0}
role="button"
>
<input
type="radio"
name="link-preference-radio"
className="radio checked:bg-primary"
value="Screenshot"
checked={linksRouteTo === LinksRouteTo.SCREENSHOT}
onChange={() => setLinksRouteTo(LinksRouteTo.SCREENSHOT)}
/>
<span className="label-text">
{t("open_screenshot_if_available")}
</span>
</label>
</div>
</div>
<Button
onClick={submit}
disabled={submitLoader}
className="mt-2 w-full sm:w-fit"
variant="accent"
>
{t("save_changes")}
</Button>
</div>
</>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <SettingsLayout>{page}</SettingsLayout>;
};
export default Page;
export { getServerSideProps };