This commit is contained in:
Isaac Wise 2024-07-27 17:41:13 -05:00
parent 4faf389a2b
commit 94be3a7448
No known key found for this signature in database
GPG Key ID: A02A33A7E2427136
34 changed files with 308 additions and 256 deletions

View File

@ -1,5 +1,8 @@
import Link from "next/link";
import { AccountSettings, CollectionIncludingMembersAndLinkCount } from "@/types/global";
import {
AccountSettings,
CollectionIncludingMembersAndLinkCount,
} from "@/types/global";
import React, { useEffect, useState } from "react";
import ProfilePhoto from "./ProfilePhoto";
import usePermissions from "@/hooks/usePermissions";
@ -33,7 +36,9 @@ export default function CollectionCard({ collection, className }: Props) {
const permissions = usePermissions(collection.id as number);
const [collectionOwner, setCollectionOwner] = useState<Partial<AccountSettings>>({});
const [collectionOwner, setCollectionOwner] = useState<
Partial<AccountSettings>
>({});
useEffect(() => {
const fetchOwner = async () => {
@ -151,8 +156,10 @@ export default function CollectionCard({ collection, className }: Props) {
<Link
href={`/collections/${collection.id}`}
style={{
backgroundImage: `linear-gradient(45deg, ${collection.color}30 10%, ${settings.theme === "dark" ? "oklch(var(--b2))" : "oklch(var(--b2))"
} 50%, ${settings.theme === "dark" ? "oklch(var(--b2))" : "oklch(var(--b2))"
backgroundImage: `linear-gradient(45deg, ${collection.color}30 10%, ${
settings.theme === "dark" ? "oklch(var(--b2))" : "oklch(var(--b2))"
} 50%, ${
settings.theme === "dark" ? "oklch(var(--b2))" : "oklch(var(--b2))"
} 100%)`,
}}
className="card card-compact shadow-md hover:shadow-none duration-200 border border-neutral-content"

View File

@ -231,7 +231,8 @@ const renderItem = (
return (
<div ref={provided.innerRef} {...provided.draggableProps} className="mb-1">
<div
className={`${currentPath === `/collections/${collection.id}`
className={`${
currentPath === `/collections/${collection.id}`
? "bg-primary/20 is-active"
: "hover:bg-neutral/20"
} duration-100 flex gap-1 items-center pr-2 pl-1 rounded-md`}

View File

@ -60,7 +60,9 @@ export default function Dropdown({
}
}, [points, dropdownHeight]);
return !points || pos && (
return (
!points ||
(pos && (
<ClickAwayHandler
onMount={(e) => {
setDropdownHeight(e.height);
@ -76,7 +78,8 @@ export default function Dropdown({
: undefined
}
onClickOutside={onClickOutside}
className={`${className || ""
className={`${
className || ""
} py-1 shadow-md border border-neutral-content bg-base-200 rounded-md flex flex-col z-20`}
>
{items.map((e, i) => {
@ -101,5 +104,6 @@ export default function Dropdown({
);
})}
</ClickAwayHandler>
))
);
}

View File

@ -79,7 +79,8 @@ export default function LinkActions({
return (
<>
<div
className={`dropdown dropdown-left absolute ${position || "top-3 right-3"
className={`dropdown dropdown-left absolute ${
position || "top-3 right-3"
} ${alignToTop ? "" : "dropdown-end"} z-20`}
>
<div
@ -91,7 +92,8 @@ export default function LinkActions({
<i title="More" className="bi-three-dots text-xl" />
</div>
<ul
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box w-44 mr-1 ${alignToTop ? "" : "translate-y-10"
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box w-44 mr-1 ${
alignToTop ? "" : "translate-y-10"
}`}
>
<li>
@ -122,7 +124,8 @@ export default function LinkActions({
</div>
</li>
)}
{permissions === true || permissions?.canUpdate && (
{permissions === true ||
(permissions?.canUpdate && (
<li>
<div
role="button"
@ -135,7 +138,7 @@ export default function LinkActions({
{t("edit_link")}
</div>
</li>
)}
))}
{link.type === "url" && (
<li>
<div
@ -150,7 +153,8 @@ export default function LinkActions({
</div>
</li>
)}
{permissions === true || permissions?.canDelete && (
{permissions === true ||
(permissions?.canDelete && (
<li>
<div
role="button"
@ -163,7 +167,7 @@ export default function LinkActions({
{t("delete")}
</div>
</li>
)}
))}
</ul>
</div>

View File

@ -91,7 +91,8 @@ export default function LinkCardCompact({
return (
<>
<div
className={`${selectedStyle} border relative items-center flex ${!showInfo && !isPWA() ? "hover:bg-base-300 p-3" : "py-3"
className={`${selectedStyle} border relative items-center flex ${
!showInfo && !isPWA() ? "hover:bg-base-300 p-3" : "py-3"
} duration-200 rounded-lg w-full`}
onClick={() =>
selectable

View File

@ -21,7 +21,8 @@ export default function MobileNavigation({ }: Props) {
className={`fixed bottom-0 left-0 right-0 z-30 duration-200 sm:hidden`}
>
<div
className={`w-full flex bg-base-100 ${isIphone() && isPWA() ? "pb-5" : ""
className={`w-full flex bg-base-100 ${
isIphone() && isPWA() ? "pb-5" : ""
} border-solid border-t-neutral-content border-t`}
>
<MobileNavigationButton href={`/dashboard`} icon={"bi-house"} />
@ -83,9 +84,7 @@ export default function MobileNavigation({ }: Props) {
<MobileNavigationButton href={`/collections`} icon={"bi-folder"} />
</div>
</div>
{newLinkModal && (
<NewLinkModal onClose={() => setNewLinkModal(false)} />
)}
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
{newCollectionModal && (
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
)}

View File

@ -2,7 +2,11 @@ import React, { useEffect, useState } from "react";
import TextInput from "@/components/TextInput";
import useCollectionStore from "@/store/collections";
import toast from "react-hot-toast";
import { AccountSettings, CollectionIncludingMembersAndLinkCount, Member } from "@/types/global";
import {
AccountSettings,
CollectionIncludingMembersAndLinkCount,
Member,
} from "@/types/global";
import getPublicUserData from "@/lib/client/getPublicUserData";
import useAccountStore from "@/store/account";
import usePermissions from "@/hooks/usePermissions";
@ -62,7 +66,9 @@ export default function EditCollectionSharingModal({
const [memberUsername, setMemberUsername] = useState("");
const [collectionOwner, setCollectionOwner] = useState<Partial<AccountSettings>>({});
const [collectionOwner, setCollectionOwner] = useState<
Partial<AccountSettings>
>({});
useEffect(() => {
const fetchOwner = async () => {

View File

@ -38,7 +38,9 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
let isPublic = router.pathname.startsWith("/public") ? true : undefined;
const [collectionOwner, setCollectionOwner] = useState<Partial<AccountSettings>>({});
const [collectionOwner, setCollectionOwner] = useState<
Partial<AccountSettings>
>({});
useEffect(() => {
const fetchOwner = async () => {
@ -206,7 +208,9 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
<p className="text-center text-2xl">{t("preservation_in_queue")}</p>
<p className="text-center text-lg">{t("check_back_later")}</p>
</div>
) : !isReady() && atLeastOneFormatAvailable() && (
) : (
!isReady() &&
atLeastOneFormatAvailable() && (
<div className={`w-full h-full flex flex-col justify-center p-5`}>
<BeatLoader
color="oklch(var(--p))"
@ -216,10 +220,12 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
<p className="text-center">{t("there_are_more_formats")}</p>
<p className="text-center text-sm">{t("check_back_later")}</p>
</div>
)
)}
<div
className={`flex flex-col sm:flex-row gap-3 items-center justify-center ${isReady() ? "sm:mt " : ""
className={`flex flex-col sm:flex-row gap-3 items-center justify-center ${
isReady() ? "sm:mt " : ""
}`}
>
<Link

View File

@ -120,9 +120,7 @@ export default function Navbar() {
</ClickAwayHandler>
</div>
)}
{newLinkModal && (
<NewLinkModal onClose={() => setNewLinkModal(false)} />
)}
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
{newCollectionModal && (
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
)}

View File

@ -39,9 +39,7 @@ export default function NoLinksFound({ text }: Props) {
</span>
</div>
</div>
{newLinkModal && (
<NewLinkModal onClose={() => setNewLinkModal(false)} />
)}
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
</div>
);
}

View File

@ -90,17 +90,19 @@ export default function PreservedFormatRow({
</div>
<div className="flex gap-1">
{downloadable || false && (
{downloadable ||
(false && (
<div
onClick={() => handleDownload()}
className="btn btn-sm btn-square"
>
<i className="bi-cloud-arrow-down text-xl text-neutral" />
</div>
)}
))}
<Link
href={`${isPublic ? "/public" : ""
href={`${
isPublic ? "/public" : ""
}/preserved/${link?.id}?format=${format}`}
target="_blank"
className="btn btn-sm btn-square"

View File

@ -32,7 +32,8 @@ export default function ProfileDropdown() {
/>
</div>
<ul
className={`dropdown-content z-[1] menu shadow bg-base-200 border border-neutral-content rounded-box ${isAdmin ? "w-48" : "w-40"
className={`dropdown-content z-[1] menu shadow bg-base-200 border border-neutral-content rounded-box ${
isAdmin ? "w-48" : "w-40"
} mt-1`}
>
<li>

View File

@ -192,7 +192,8 @@ export default function ReadableView({ link }: Props) {
>
<i className="bi-link-45deg"></i>
{isValidUrl(link?.url || "") && new URL(link?.url as string).host}
{isValidUrl(link?.url || "") &&
new URL(link?.url as string).host}
</Link>
)}
</div>
@ -257,7 +258,8 @@ export default function ReadableView({ link }: Props) {
></div>
) : (
<div
className={`w-full h-full flex flex-col justify-center p-10 ${link?.readable === "pending" || !link?.readable ? "skeleton" : ""
className={`w-full h-full flex flex-col justify-center p-10 ${
link?.readable === "pending" || !link?.readable ? "skeleton" : ""
}`}
>
<svg

View File

@ -10,7 +10,9 @@ export default function ToggleDarkMode({ className }: Props) {
const { t } = useTranslation();
const { settings, updateSettings } = useLocalSettingsStore();
const [theme, setTheme] = useState<string | null>(localStorage.getItem("theme"));
const [theme, setTheme] = useState<string | null>(
localStorage.getItem("theme")
);
const handleToggle = (e: ChangeEvent<HTMLInputElement>) => {
setTheme(e.target.checked ? "dark" : "light");

View File

@ -25,7 +25,8 @@ export default function CenteredForm({
<div className="m-auto flex flex-col gap-2 w-full">
{settings.theme && (
<Image
src={`/linkwarden_${settings.theme === "dark" ? "dark" : "light"
src={`/linkwarden_${
settings.theme === "dark" ? "dark" : "light"
}.png`}
width={640}
height={136}

View File

@ -26,7 +26,7 @@ export default async function exportData(userId: number) {
if (Array.isArray(data)) {
data.forEach((item) => redactIds(item));
} else if (data !== null && typeof data === "object") {
const fieldsToRedact = ['id', 'parentId', 'collectionId', 'ownerId'];
const fieldsToRedact = ["id", "parentId", "collectionId", "ownerId"];
fieldsToRedact.forEach((field) => {
if (field in data) {
@ -37,7 +37,10 @@ export default async function exportData(userId: number) {
// Recursively call redactIds for each property that is an object or an array
Object.keys(data).forEach((key) => {
const value = (data as any)[key];
if (value !== null && (typeof value === "object" || Array.isArray(value))) {
if (
value !== null &&
(typeof value === "object" || Array.isArray(value))
) {
redactIds(value);
}
});

View File

@ -63,7 +63,8 @@ async function processBookmarks(
) as Element;
if (collectionName) {
const collectionNameContent = (collectionName.children[0] as TextNode)?.content;
const collectionNameContent = (collectionName.children[0] as TextNode)
?.content;
if (collectionNameContent) {
collectionId = await createCollection(
userId,
@ -274,4 +275,3 @@ function processNodes(nodes: Node[]) {
nodes.forEach(findAndProcessDL);
return nodes;
}

View File

@ -24,10 +24,7 @@ export default async function deleteUserById(
if (!isServerAdmin) {
if (user.password) {
const isPasswordValid = bcrypt.compareSync(
body.password,
user.password
);
const isPasswordValid = bcrypt.compareSync(body.password, user.password);
if (!isPasswordValid && !isServerAdmin) {
return {

View File

@ -7,10 +7,15 @@ export function isPWA() {
}
export function isIphone() {
return /iPhone/.test(navigator.userAgent) && !(window as unknown as { MSStream?: any }).MSStream;
return (
/iPhone/.test(navigator.userAgent) &&
!(window as unknown as { MSStream?: any }).MSStream
);
}
export function dropdownTriggerer(e: React.FocusEvent<HTMLElement> | React.MouseEvent<HTMLElement>) {
export function dropdownTriggerer(
e: React.FocusEvent<HTMLElement> | React.MouseEvent<HTMLElement>
) {
let targetEl = e.currentTarget;
if (targetEl && targetEl.matches(":focus")) {
setTimeout(function () {

View File

@ -39,7 +39,9 @@ export function monolithAvailable(
);
}
export function previewAvailable(link: LinkIncludingShortenedCollectionAndTags) {
export function previewAvailable(
link: LinkIncludingShortenedCollectionAndTags
) {
return (
link &&
link.preview &&

View File

@ -104,9 +104,7 @@ export default function Admin() {
<p>{t("no_users_found")}</p>
)}
{newUserModal && (
<NewUserModal onClose={() => setNewUserModal(false)} />
)}
{newUserModal && <NewUserModal onClose={() => setNewUserModal(false)} />}
</div>
);
}

View File

@ -189,8 +189,12 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
where: { id: linkId },
data: {
preview: isPDF ? "unavailable" : undefined,
image: isImage ? `archives/${collectionPermissions.id}/${linkId + suffix}` : null,
pdf: isPDF ? `archives/${collectionPermissions.id}/${linkId + suffix}` : null,
image: isImage
? `archives/${collectionPermissions.id}/${linkId + suffix}`
: null,
pdf: isPDF
? `archives/${collectionPermissions.id}/${linkId + suffix}`
: null,
lastPreserved: new Date().toISOString(),
},
});

View File

@ -54,7 +54,9 @@ export default function Index() {
const { account } = useAccountStore();
const [collectionOwner, setCollectionOwner] = useState<Partial<AccountSettings>>({});
const [collectionOwner, setCollectionOwner] = useState<
Partial<AccountSettings>
>({});
useEffect(() => {
const fetchOwner = async () => {
@ -108,7 +110,8 @@ export default function Index() {
<div
className="h-[60rem] p-5 flex gap-3 flex-col"
style={{
backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${settings.theme === "dark" ? "#262626" : "#f3f4f6"
backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}}
>
@ -210,7 +213,7 @@ export default function Index() {
/>
)}
{activeCollection.members
.sort((a, b) => (a.userId) - (b.userId))
.sort((a, b) => a.userId - b.userId)
.map((e, i) => {
return (
<ProfilePhoto

View File

@ -60,7 +60,10 @@ export default function Dashboard() {
handleNumberOfLinksToShow();
}, [width]);
const importBookmarks = async (e: React.ChangeEvent<HTMLInputElement>, format: MigrationFormat) => {
const importBookmarks = async (
e: React.ChangeEvent<HTMLInputElement>,
format: MigrationFormat
) => {
const file: File | null = e.target.files && e.target.files[0];
if (file) {
@ -324,9 +327,7 @@ export default function Dashboard() {
)}
</div>
</div>
{newLinkModal && (
<NewLinkModal onClose={() => setNewLinkModal(false)} />
)}
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
</MainLayout>
);
}

View File

@ -224,9 +224,9 @@ export default function Login({
loading={submitLoader}
>
{value.name.toLowerCase() === "google" ||
value.name.toLowerCase() === "apple" && (
(value.name.toLowerCase() === "apple" && (
<i className={"bi-" + value.name.toLowerCase()}></i>
)}
))}
{value.name}
</Button>
</React.Fragment>

View File

@ -37,7 +37,9 @@ export default function PublicCollections() {
const router = useRouter();
const [collectionOwner, setCollectionOwner] = useState<Partial<AccountSettings>>({});
const [collectionOwner, setCollectionOwner] = useState<
Partial<AccountSettings>
>({});
const [searchFilter, setSearchFilter] = useState({
name: true,
@ -103,7 +105,8 @@ export default function PublicCollections() {
<div
className="h-96"
style={{
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${settings.theme === "dark" ? "#262626" : "#f3f4f6"
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}}
>

View File

@ -133,9 +133,9 @@ export default function Register({
loading={submitLoader}
>
{value.name.toLowerCase() === "google" ||
value.name.toLowerCase() === "apple" && (
(value.name.toLowerCase() === "apple" && (
<i className={"bi-" + value.name.toLowerCase()}></i>
)}
))}
{value.name}
</Button>
</React.Fragment>

View File

@ -102,7 +102,10 @@ export default function Account() {
setSubmitLoader(false);
};
const importBookmarks = async (e: ChangeEvent<HTMLInputElement>, format: MigrationFormat) => {
const importBookmarks = async (
e: ChangeEvent<HTMLInputElement>,
format: MigrationFormat
) => {
setSubmitLoader(true);
const file = e.target.files?.[0];
@ -421,7 +424,8 @@ export default function Account() {
<p>
{t("delete_account_warning")}
{process.env.NEXT_PUBLIC_STRIPE && " " + t("cancel_subscription_notice")}
{process.env.NEXT_PUBLIC_STRIPE &&
" " + t("cancel_subscription_notice")}
</p>
</div>

View File

@ -81,7 +81,7 @@ export enum Sort {
DescriptionZA,
}
export type Order = { [key: string]: 'asc' | 'desc' };
export type Order = { [key: string]: "asc" | "desc" };
export type LinkRequestQuery = {
sort: Sort;