Fix merge conflicts

This commit is contained in:
Isaac Wise
2024-08-18 13:03:09 -05:00
95 changed files with 3462 additions and 1934 deletions
-39
View File
@@ -1,39 +0,0 @@
import LinkCard from "@/components/LinkViews/LinkCard";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import { link } from "fs";
import { GridLoader } from "react-spinners";
export default function CardView({
links,
editMode,
isLoading,
}: {
links: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
isLoading?: boolean;
}) {
return (
<div className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5">
{links.map((e, i) => {
return (
<LinkCard
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
})}
{isLoading && links.length > 0 && (
<GridLoader
color="oklch(var(--p))"
loading={true}
size={20}
className="fixed top-5 right-5 opacity-50 z-30"
/>
)}
</div>
);
}
-38
View File
@@ -1,38 +0,0 @@
import LinkList from "@/components/LinkViews/LinkList";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import { GridLoader } from "react-spinners";
export default function ListView({
links,
editMode,
isLoading,
}: {
links: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
isLoading?: boolean;
}) {
return (
<div className="flex gap-1 flex-col">
{links.map((e, i) => {
return (
<LinkList
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
})}
{isLoading && links.length > 0 && (
<GridLoader
color="oklch(var(--p))"
loading={true}
size={20}
className="fixed top-5 right-5 opacity-50 z-30"
/>
)}
</div>
);
}
@@ -1,58 +0,0 @@
import LinkMasonry from "@/components/LinkViews/LinkMasonry";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import { GridLoader } from "react-spinners";
import Masonry from "react-masonry-css";
import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "../../../tailwind.config.js";
import { useMemo } from "react";
export default function MasonryView({
links,
editMode,
isLoading,
}: {
links: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
isLoading?: boolean;
}) {
const fullConfig = resolveConfig(tailwindConfig as any);
const breakpointColumnsObj = useMemo(() => {
return {
default: 5,
1900: 4,
1500: 3,
880: 2,
550: 1,
};
}, []);
return (
<Masonry
breakpointCols={breakpointColumnsObj}
columnClassName="flex flex-col gap-5 !w-full"
className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5"
>
{links.map((e, i) => {
return (
<LinkMasonry
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
})}
{isLoading && links.length > 0 && (
<GridLoader
color="oklch(var(--p))"
loading={true}
size={20}
className="fixed top-5 right-5 opacity-50 z-30"
/>
)}
</Masonry>
);
}
@@ -7,11 +7,11 @@ import usePermissions from "@/hooks/usePermissions";
import EditLinkModal from "@/components/ModalContent/EditLinkModal";
import DeleteLinkModal from "@/components/ModalContent/DeleteLinkModal";
import PreservedFormatsModal from "@/components/ModalContent/PreservedFormatsModal";
import useLinkStore from "@/store/links";
import { toast } from "react-hot-toast";
import useAccountStore from "@/store/account";
import { dropdownTriggerer } from "@/lib/client/utils";
import { useTranslation } from "next-i18next";
import { useUser } from "@/hooks/store/user";
import { useDeleteLink, useUpdateLink } from "@/hooks/store/links";
import toast from "react-hot-toast";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
@@ -39,41 +39,35 @@ export default function LinkActions({
const [deleteLinkModal, setDeleteLinkModal] = useState(false);
const [preservedFormatsModal, setPreservedFormatsModal] = useState(false);
const { account } = useAccountStore();
const { data: user = {} } = useUser();
const { removeLink, updateLink } = useLinkStore();
const updateLink = useUpdateLink();
const deleteLink = useDeleteLink();
const pinLink = async () => {
const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0];
const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0] ? true : false;
const load = toast.loading(t("applying"));
const load = toast.loading(t("updating"));
const response = await updateLink({
...link,
pinnedBy: isAlreadyPinned ? undefined : [{ id: account.id }],
});
await updateLink.mutateAsync(
{
...link,
pinnedBy: isAlreadyPinned ? undefined : [{ id: user.id }],
},
{
onSettled: (data, error) => {
toast.dismiss(load);
toast.dismiss(load);
if (response.ok) {
toast.success(isAlreadyPinned ? t("link_unpinned") : t("link_unpinned"));
} else {
toast.error(response.data as string);
}
};
const deleteLink = async () => {
const load = toast.loading(t("deleting"));
const response = await removeLink(link.id as number);
toast.dismiss(load);
if (response.ok) {
toast.success(t("deleted"));
} else {
toast.error(response.data as string);
}
if (error) {
toast.error(error.message);
} else {
toast.success(
isAlreadyPinned ? t("link_unpinned") : t("link_pinned")
);
}
},
}
);
};
return (
@@ -92,7 +86,7 @@ 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 ${
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box mr-1 ${
alignToTop ? "" : "translate-y-10"
}`}
>
@@ -104,6 +98,7 @@ export default function LinkActions({
(document?.activeElement as HTMLElement)?.blur();
pinLink();
}}
className="whitespace-nowrap"
>
{link?.pinnedBy && link.pinnedBy[0]
? t("unpin")
@@ -119,6 +114,7 @@ export default function LinkActions({
(document?.activeElement as HTMLElement)?.blur();
toggleShowInfo();
}}
className="whitespace-nowrap"
>
{!linkInfo ? t("show_link_details") : t("hide_link_details")}
</div>
@@ -133,6 +129,7 @@ export default function LinkActions({
(document?.activeElement as HTMLElement)?.blur();
setEditLinkModal(true);
}}
className="whitespace-nowrap"
>
{t("edit_link")}
</div>
@@ -147,6 +144,7 @@ export default function LinkActions({
(document?.activeElement as HTMLElement)?.blur();
setPreservedFormatsModal(true);
}}
className="whitespace-nowrap"
>
{t("preserved_formats")}
</div>
@@ -157,10 +155,27 @@ export default function LinkActions({
<div
role="button"
tabIndex={0}
onClick={(e) => {
onClick={async (e) => {
(document?.activeElement as HTMLElement)?.blur();
e.shiftKey ? deleteLink() : setDeleteLinkModal(true);
e.shiftKey
? async () => {
const load = toast.loading(t("deleting"));
await deleteLink.mutateAsync(link.id as number, {
onSettled: (data, error) => {
toast.dismiss(load);
if (error) {
toast.error(error.message);
} else {
toast.success(t("deleted"));
}
},
});
}
: setDeleteLinkModal(true);
}}
className="whitespace-nowrap"
>
{t("delete")}
</div>
@@ -184,7 +199,7 @@ export default function LinkActions({
{preservedFormatsModal && (
<PreservedFormatsModal
onClose={() => setPreservedFormatsModal(false)}
activeLink={link}
link={link}
/>
)}
{/* {expandedLink ? (
@@ -5,7 +5,6 @@ import {
} from "@/types/global";
import { useEffect, useRef, useState } from "react";
import useLinkStore from "@/store/links";
import useCollectionStore from "@/store/collections";
import unescapeString from "@/lib/client/unescapeString";
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate";
@@ -13,14 +12,16 @@ import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection
import Image from "next/image";
import { previewAvailable } from "@/lib/shared/getArchiveValidity";
import Link from "next/link";
import LinkIcon from "./LinkComponents/LinkIcon";
import LinkIcon from "./LinkIcon";
import useOnScreen from "@/hooks/useOnScreen";
import { generateLinkHref } from "@/lib/client/generateLinkHref";
import useAccountStore from "@/store/account";
import usePermissions from "@/hooks/usePermissions";
import toast from "react-hot-toast";
import LinkTypeBadge from "./LinkComponents/LinkTypeBadge";
import LinkTypeBadge from "./LinkTypeBadge";
import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user";
import { useGetLink, useLinks } from "@/hooks/store/links";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
@@ -33,11 +34,16 @@ type Props = {
export default function LinkCard({ link, flipDropdown, editMode }: Props) {
const { t } = useTranslation();
const viewMode = localStorage.getItem("viewMode") || "card";
const { collections } = useCollectionStore();
const { account } = useAccountStore();
const { data: collections = [] } = useCollections();
const { links, getLink, setSelectedLinks, selectedLinks } = useLinkStore();
const { data: user = {} } = useUser();
const { setSelectedLinks, selectedLinks } = useLinkStore();
const {
data: { data: links = [] },
} = useLinks();
const getLink = useGetLink();
useEffect(() => {
if (!editMode) {
@@ -93,7 +99,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
link.preview !== "unavailable"
) {
interval = setInterval(async () => {
getLink(link.id as number);
getLink.mutateAsync(link.id as number);
}, 5000);
}
@@ -131,7 +137,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
<div
className="rounded-2xl cursor-pointer h-full flex flex-col justify-between"
onClick={() =>
!editMode && window.open(generateLinkHref(link, account), "_blank")
!editMode && window.open(generateLinkHref(link, user), "_blank")
}
>
<div>
@@ -3,7 +3,6 @@ import {
LinkIncludingShortenedCollectionAndTags,
} from "@/types/global";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
export default function LinkCollection({
@@ -13,22 +12,22 @@ export default function LinkCollection({
link: LinkIncludingShortenedCollectionAndTags;
collection: CollectionIncludingMembersAndLinkCount;
}) {
const router = useRouter();
return (
<Link
href={`/collections/${link.collection.id}`}
onClick={(e) => {
e.stopPropagation();
}}
className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100 select-none"
title={collection?.name}
>
<i
className="bi-folder-fill text-lg drop-shadow"
style={{ color: collection?.color }}
></i>
<p className="truncate capitalize">{collection?.name}</p>
</Link>
<>
<Link
href={`/collections/${link.collection.id}`}
onClick={(e) => {
e.stopPropagation();
}}
className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100 select-none"
title={collection?.name}
>
<i
className="bi-folder-fill text-lg drop-shadow"
style={{ color: collection?.color }}
></i>
<p className="truncate capitalize">{collection?.name}</p>
</Link>
</>
);
}
@@ -4,7 +4,6 @@ import {
} from "@/types/global";
import { useEffect, useState } from "react";
import useLinkStore from "@/store/links";
import useCollectionStore from "@/store/collections";
import unescapeString from "@/lib/client/unescapeString";
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate";
@@ -12,11 +11,13 @@ import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection
import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon";
import { isPWA } from "@/lib/client/utils";
import { generateLinkHref } from "@/lib/client/generateLinkHref";
import useAccountStore from "@/store/account";
import usePermissions from "@/hooks/usePermissions";
import toast from "react-hot-toast";
import LinkTypeBadge from "./LinkComponents/LinkTypeBadge";
import LinkTypeBadge from "./LinkTypeBadge";
import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user";
import { useLinks } from "@/hooks/store/links";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
@@ -33,9 +34,12 @@ export default function LinkCardCompact({
}: Props) {
const { t } = useTranslation();
const { collections } = useCollectionStore();
const { account } = useAccountStore();
const { links, setSelectedLinks, selectedLinks } = useLinkStore();
const { data: collections = [] } = useCollections();
const { data: user = {} } = useUser();
const { setSelectedLinks, selectedLinks } = useLinkStore();
const { links } = useLinks();
useEffect(() => {
if (!editMode) {
@@ -119,7 +123,7 @@ export default function LinkCardCompact({
<div
className="flex items-center cursor-pointer w-full"
onClick={() =>
!editMode && window.open(generateLinkHref(link, account), "_blank")
!editMode && window.open(generateLinkHref(link, user), "_blank")
}
>
<div className="shrink-0">
@@ -5,7 +5,6 @@ import {
} from "@/types/global";
import { useEffect, useRef, useState } from "react";
import useLinkStore from "@/store/links";
import useCollectionStore from "@/store/collections";
import unescapeString from "@/lib/client/unescapeString";
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate";
@@ -13,14 +12,16 @@ import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection
import Image from "next/image";
import { previewAvailable } from "@/lib/shared/getArchiveValidity";
import Link from "next/link";
import LinkIcon from "./LinkComponents/LinkIcon";
import LinkIcon from "./LinkIcon";
import useOnScreen from "@/hooks/useOnScreen";
import { generateLinkHref } from "@/lib/client/generateLinkHref";
import useAccountStore from "@/store/account";
import usePermissions from "@/hooks/usePermissions";
import toast from "react-hot-toast";
import LinkTypeBadge from "./LinkComponents/LinkTypeBadge";
import LinkTypeBadge from "./LinkTypeBadge";
import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user";
import { useGetLink, useLinks } from "@/hooks/store/links";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
@@ -33,10 +34,13 @@ type Props = {
export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
const { t } = useTranslation();
const { collections } = useCollectionStore();
const { account } = useAccountStore();
const { data: collections = [] } = useCollections();
const { data: user = {} } = useUser();
const { links, getLink, setSelectedLinks, selectedLinks } = useLinkStore();
const { setSelectedLinks, selectedLinks } = useLinkStore();
const { links } = useLinks();
const getLink = useGetLink();
useEffect(() => {
if (!editMode) {
@@ -92,7 +96,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
link.preview !== "unavailable"
) {
interval = setInterval(async () => {
getLink(link.id as number);
getLink.mutateAsync(link.id as number);
}, 5000);
}
@@ -130,7 +134,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
<div
className="rounded-2xl cursor-pointer"
onClick={() =>
!editMode && window.open(generateLinkHref(link, account), "_blank")
!editMode && window.open(generateLinkHref(link, user), "_blank")
}
>
<div className="relative rounded-t-2xl overflow-hidden">
+238
View File
@@ -0,0 +1,238 @@
import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard";
import {
LinkIncludingShortenedCollectionAndTags,
ViewMode,
} from "@/types/global";
import { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import LinkMasonry from "@/components/LinkViews/LinkComponents/LinkMasonry";
import Masonry from "react-masonry-css";
import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "../../tailwind.config.js";
import { useMemo } from "react";
import LinkList from "@/components/LinkViews/LinkComponents/LinkList";
export function CardView({
links,
editMode,
isLoading,
placeholders,
hasNextPage,
placeHolderRef,
}: {
links?: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
isLoading?: boolean;
placeholders?: number[];
hasNextPage?: boolean;
placeHolderRef?: any;
}) {
return (
<div className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5">
{links?.map((e, i) => {
return (
<LinkCard
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
})}
{(hasNextPage || isLoading) &&
placeholders?.map((e, i) => {
return (
<div
className="flex flex-col gap-4"
ref={e === 1 ? placeHolderRef : undefined}
key={i}
>
<div className="skeleton h-40 w-full"></div>
<div className="skeleton h-3 w-2/3"></div>
<div className="skeleton h-3 w-full"></div>
<div className="skeleton h-3 w-full"></div>
<div className="skeleton h-3 w-1/3"></div>
</div>
);
})}
</div>
);
}
export function MasonryView({
links,
editMode,
isLoading,
placeholders,
hasNextPage,
placeHolderRef,
}: {
links?: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
isLoading?: boolean;
placeholders?: number[];
hasNextPage?: boolean;
placeHolderRef?: any;
}) {
const fullConfig = resolveConfig(tailwindConfig as any);
const breakpointColumnsObj = useMemo(() => {
return {
default: 5,
1900: 4,
1500: 3,
880: 2,
550: 1,
};
}, []);
return (
<Masonry
breakpointCols={breakpointColumnsObj}
columnClassName="flex flex-col gap-5 !w-full"
className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5"
>
{links?.map((e, i) => {
return (
<LinkMasonry
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
})}
{(hasNextPage || isLoading) &&
placeholders?.map((e, i) => {
return (
<div
className="flex flex-col gap-4"
ref={e === 1 ? placeHolderRef : undefined}
key={i}
>
<div className="skeleton h-40 w-full"></div>
<div className="skeleton h-3 w-2/3"></div>
<div className="skeleton h-3 w-full"></div>
<div className="skeleton h-3 w-full"></div>
<div className="skeleton h-3 w-1/3"></div>
</div>
);
})}
</Masonry>
);
}
export function ListView({
links,
editMode,
isLoading,
placeholders,
hasNextPage,
placeHolderRef,
}: {
links?: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
isLoading?: boolean;
placeholders?: number[];
hasNextPage?: boolean;
placeHolderRef?: any;
}) {
return (
<div className="flex gap-1 flex-col">
{links?.map((e, i) => {
return (
<LinkList
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
})}
{(hasNextPage || isLoading) &&
placeholders?.map((e, i) => {
return (
<div
ref={e === 1 ? placeHolderRef : undefined}
key={i}
className="flex gap-4 p-4"
>
<div className="skeleton h-16 w-16"></div>
<div className="flex flex-col gap-4 w-full">
<div className="skeleton h-3 w-2/3"></div>
<div className="skeleton h-3 w-full"></div>
<div className="skeleton h-3 w-1/3"></div>
</div>
</div>
);
})}
</div>
);
}
export default function Links({
layout,
links,
editMode,
placeholderCount,
useData,
}: {
layout: ViewMode;
links?: LinkIncludingShortenedCollectionAndTags[];
editMode?: boolean;
placeholderCount?: number;
useData?: any;
}) {
const { ref, inView } = useInView();
useEffect(() => {
if (inView && useData?.fetchNextPage && useData?.hasNextPage) {
useData.fetchNextPage();
}
}, [useData, inView]);
if (layout === ViewMode.List) {
return (
<ListView
links={links}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
} else if (layout === ViewMode.Masonry) {
return (
<MasonryView
links={links}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
} else {
// Default to card view
return (
<CardView
links={links}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
}
}
const placeholderCountToArray = (num?: number) =>
num ? Array.from({ length: num }, (_, i) => i + 1) : [];