better edit view
This commit is contained in:
parent
820d686c37
commit
aee10fa406
|
@ -273,13 +273,13 @@ const renderItem = (
|
||||||
icon={collection.icon}
|
icon={collection.icon}
|
||||||
size={30}
|
size={30}
|
||||||
weight={(collection.iconWeight || "regular") as IconWeight}
|
weight={(collection.iconWeight || "regular") as IconWeight}
|
||||||
color={collection.color || "#0ea5e9"}
|
color={collection.color}
|
||||||
className="-mr-[0.15rem]"
|
className="-mr-[0.15rem]"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bi-folder-fill text-2xl"
|
className="bi-folder-fill text-2xl"
|
||||||
style={{ color: collection.color || "#0ea5e9" }}
|
style={{ color: collection.color }}
|
||||||
></i>
|
></i>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClick: Function;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function EditButton({ onClick, className }: Props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
onClick={() => onClick()}
|
||||||
|
className={clsx(
|
||||||
|
"group-hover:opacity-100 opacity-0 duration-100 btn-square btn-xs btn btn-ghost absolute bi-pencil-fill text-neutral cursor-pointer -right-7 text-xs",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
title={t("edit")}
|
||||||
|
></span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditButton;
|
|
@ -6,6 +6,8 @@ import { useTranslation } from "next-i18next";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { IconWeight } from "@phosphor-icons/react";
|
import { IconWeight } from "@phosphor-icons/react";
|
||||||
import IconGrid from "./IconGrid";
|
import IconGrid from "./IconGrid";
|
||||||
|
import IconPopover from "./IconPopover";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
alignment?: string;
|
alignment?: string;
|
||||||
|
@ -33,7 +35,6 @@ const IconPicker = ({
|
||||||
reset,
|
reset,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
const [iconPicker, setIconPicker] = useState(false);
|
const [iconPicker, setIconPicker] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -59,57 +60,21 @@ const IconPicker = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{iconPicker && (
|
{iconPicker && (
|
||||||
<Popover
|
<IconPopover
|
||||||
onClose={() => setIconPicker(false)}
|
alignment={alignment}
|
||||||
className={
|
|
||||||
className +
|
|
||||||
" fade-in bg-base-200 border border-neutral-content p-2 h-44 w-[22.5rem] rounded-lg backdrop-blur-sm bg-opacity-90 " +
|
|
||||||
(alignment || " lg:-translate-x-1/3 top-20 left-0 ")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex gap-2 h-full w-full">
|
|
||||||
<div className="flex flex-col gap-2 h-full w-fit color-picker">
|
|
||||||
<div
|
|
||||||
className="btn btn-ghost btn-xs"
|
|
||||||
onClick={reset as React.MouseEventHandler<HTMLDivElement>}
|
|
||||||
>
|
|
||||||
{t("reset")}
|
|
||||||
</div>
|
|
||||||
<select
|
|
||||||
className="border border-neutral-content bg-base-100 focus:outline-none focus:border-primary duration-100 w-full rounded-md h-7"
|
|
||||||
value={weight}
|
|
||||||
onChange={(e) => setWeight(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="regular">{t("regular")}</option>
|
|
||||||
<option value="thin">{t("thin")}</option>
|
|
||||||
<option value="light">{t("light_icon")}</option>
|
|
||||||
<option value="bold">{t("bold")}</option>
|
|
||||||
<option value="fill">{t("fill")}</option>
|
|
||||||
<option value="duotone">{t("duotone")}</option>
|
|
||||||
</select>
|
|
||||||
<HexColorPicker color={color} onChange={(e) => setColor(e)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 w-full">
|
|
||||||
<TextInput
|
|
||||||
className="p-2 rounded w-full h-7 text-sm"
|
|
||||||
placeholder={t("search")}
|
|
||||||
value={query}
|
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-4 gap-1 w-full overflow-y-auto h-32 border border-neutral-content bg-base-100 rounded-md p-2">
|
|
||||||
<IconGrid
|
|
||||||
query={query}
|
|
||||||
color={color}
|
color={color}
|
||||||
weight={weight}
|
setColor={setColor}
|
||||||
iconName={iconName}
|
iconName={iconName}
|
||||||
setIconName={setIconName}
|
setIconName={setIconName}
|
||||||
|
weight={weight}
|
||||||
|
setWeight={setWeight}
|
||||||
|
reset={reset}
|
||||||
|
onClose={() => setIconPicker(false)}
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
alignment || "lg:-translate-x-1/3 top-20 left-0"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import TextInput from "./TextInput";
|
||||||
|
import Popover from "./Popover";
|
||||||
|
import { HexColorPicker } from "react-colorful";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import IconGrid from "./IconGrid";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
alignment?: string;
|
||||||
|
color: string;
|
||||||
|
setColor: Function;
|
||||||
|
iconName?: string;
|
||||||
|
setIconName: Function;
|
||||||
|
weight: "light" | "regular" | "bold" | "fill" | "duotone" | "thin";
|
||||||
|
setWeight: Function;
|
||||||
|
reset: Function;
|
||||||
|
className?: string;
|
||||||
|
onClose: Function;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IconPopover = ({
|
||||||
|
alignment,
|
||||||
|
color,
|
||||||
|
setColor,
|
||||||
|
iconName,
|
||||||
|
setIconName,
|
||||||
|
weight,
|
||||||
|
setWeight,
|
||||||
|
reset,
|
||||||
|
className,
|
||||||
|
onClose,
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
onClose={() => onClose()}
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
"fade-in bg-base-200 border border-neutral-content p-2 h-44 w-[22.5rem] rounded-lg"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 h-full w-full">
|
||||||
|
<div className="flex flex-col gap-2 h-full w-fit color-picker">
|
||||||
|
<div
|
||||||
|
className="btn btn-ghost btn-xs"
|
||||||
|
onClick={reset as React.MouseEventHandler<HTMLDivElement>}
|
||||||
|
>
|
||||||
|
{t("reset")}
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className="border border-neutral-content bg-base-100 focus:outline-none focus:border-primary duration-100 w-full rounded-md h-7"
|
||||||
|
value={weight}
|
||||||
|
onChange={(e) => setWeight(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="regular">{t("regular")}</option>
|
||||||
|
<option value="thin">{t("thin")}</option>
|
||||||
|
<option value="light">{t("light_icon")}</option>
|
||||||
|
<option value="bold">{t("bold")}</option>
|
||||||
|
<option value="fill">{t("fill")}</option>
|
||||||
|
<option value="duotone">{t("duotone")}</option>
|
||||||
|
</select>
|
||||||
|
<HexColorPicker color={color} onChange={(e) => setColor(e)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 w-full">
|
||||||
|
<TextInput
|
||||||
|
className="p-2 rounded w-full h-7 text-sm"
|
||||||
|
placeholder={t("search")}
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-4 gap-1 w-full overflow-y-auto h-32 border border-neutral-content bg-base-100 rounded-md p-2">
|
||||||
|
<IconGrid
|
||||||
|
query={query}
|
||||||
|
color={color}
|
||||||
|
weight={weight}
|
||||||
|
iconName={iconName}
|
||||||
|
setIconName={setIconName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IconPopover;
|
|
@ -16,6 +16,8 @@ type Props = {
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
creatable?: boolean;
|
creatable?: boolean;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
onBlur?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CollectionSelection({
|
export default function CollectionSelection({
|
||||||
|
@ -23,6 +25,8 @@ export default function CollectionSelection({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
showDefaultValue = true,
|
showDefaultValue = true,
|
||||||
creatable = true,
|
creatable = true,
|
||||||
|
autoFocus,
|
||||||
|
onBlur,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { data: collections = [] } = useCollections();
|
const { data: collections = [] } = useCollections();
|
||||||
|
|
||||||
|
@ -76,7 +80,7 @@ export default function CollectionSelection({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...innerProps}
|
{...innerProps}
|
||||||
className="px-2 py-2 last:border-0 border-b border-neutral-content hover:bg-neutral-content cursor-pointer"
|
className="px-2 py-2 last:border-0 border-b border-neutral-content hover:bg-neutral-content duration-100 cursor-pointer"
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-between items-center">
|
<div className="flex w-full justify-between items-center">
|
||||||
<span>{data.label}</span>
|
<span>{data.label}</span>
|
||||||
|
@ -104,6 +108,8 @@ export default function CollectionSelection({
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={styles}
|
styles={styles}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
defaultValue={showDefaultValue ? defaultValue : null}
|
defaultValue={showDefaultValue ? defaultValue : null}
|
||||||
components={{
|
components={{
|
||||||
Option: customOption,
|
Option: customOption,
|
||||||
|
@ -120,7 +126,9 @@ export default function CollectionSelection({
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={styles}
|
styles={styles}
|
||||||
|
autoFocus={autoFocus}
|
||||||
defaultValue={showDefaultValue ? defaultValue : null}
|
defaultValue={showDefaultValue ? defaultValue : null}
|
||||||
|
onBlur={onBlur}
|
||||||
components={{
|
components={{
|
||||||
Option: customOption,
|
Option: customOption,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -10,9 +10,16 @@ type Props = {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
}[];
|
}[];
|
||||||
|
autoFocus?: boolean;
|
||||||
|
onBlur?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelection({ onChange, defaultValue }: Props) {
|
export default function TagSelection({
|
||||||
|
onChange,
|
||||||
|
defaultValue,
|
||||||
|
autoFocus,
|
||||||
|
onBlur,
|
||||||
|
}: Props) {
|
||||||
const { data: tags = [] } = useTags();
|
const { data: tags = [] } = useTags();
|
||||||
|
|
||||||
const [options, setOptions] = useState<Options[]>([]);
|
const [options, setOptions] = useState<Options[]>([]);
|
||||||
|
@ -35,6 +42,8 @@ export default function TagSelection({ onChange, defaultValue }: Props) {
|
||||||
styles={styles}
|
styles={styles}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
isMulti
|
isMulti
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,19 +50,28 @@ export const styles: StylesConfig = {
|
||||||
multiValue: (styles) => {
|
multiValue: (styles) => {
|
||||||
return {
|
return {
|
||||||
...styles,
|
...styles,
|
||||||
backgroundColor: "#0ea5e9",
|
backgroundColor: "oklch(var(--b2))",
|
||||||
color: "white",
|
color: "oklch(var(--bc))",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.1rem",
|
||||||
|
marginRight: "0.4rem",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
multiValueLabel: (styles) => ({
|
multiValueLabel: (styles) => ({
|
||||||
...styles,
|
...styles,
|
||||||
color: "white",
|
color: "oklch(var(--bc))",
|
||||||
}),
|
}),
|
||||||
multiValueRemove: (styles) => ({
|
multiValueRemove: (styles) => ({
|
||||||
...styles,
|
...styles,
|
||||||
|
height: "1.2rem",
|
||||||
|
width: "1.2rem",
|
||||||
|
borderRadius: "100px",
|
||||||
|
transition: "all 100ms",
|
||||||
|
color: "oklch(var(--w))",
|
||||||
":hover": {
|
":hover": {
|
||||||
color: "white",
|
color: "red",
|
||||||
backgroundColor: "#38bdf8",
|
backgroundColor: "oklch(var(--nc))",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||||
|
|
|
@ -17,7 +17,7 @@ import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { BeatLoader } from "react-spinners";
|
import { BeatLoader } from "react-spinners";
|
||||||
import { useUser } from "@/hooks/store/user";
|
import { useUser } from "@/hooks/store/user";
|
||||||
import { useGetLink } from "@/hooks/store/links";
|
import { useGetLink, useUpdateLink } from "@/hooks/store/links";
|
||||||
import LinkIcon from "./LinkViews/LinkComponents/LinkIcon";
|
import LinkIcon from "./LinkViews/LinkComponents/LinkIcon";
|
||||||
import CopyButton from "./CopyButton";
|
import CopyButton from "./CopyButton";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -25,14 +25,27 @@ import Icon from "./Icon";
|
||||||
import { IconWeight } from "@phosphor-icons/react";
|
import { IconWeight } from "@phosphor-icons/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import EditButton from "./EditButton";
|
||||||
|
import CollectionSelection from "./InputSelect/CollectionSelection";
|
||||||
|
import TagSelection from "./InputSelect/TagSelection";
|
||||||
|
import unescapeString from "@/lib/client/unescapeString";
|
||||||
|
import IconPopover from "./IconPopover";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
activeLink: LinkIncludingShortenedCollectionAndTags;
|
||||||
standalone?: boolean;
|
standalone?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LinkDetails({ className, link, standalone }: Props) {
|
export default function LinkDetails({
|
||||||
|
className,
|
||||||
|
activeLink,
|
||||||
|
standalone,
|
||||||
|
}: Props) {
|
||||||
|
const [link, setLink] =
|
||||||
|
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const getLink = useGetLink();
|
const getLink = useGetLink();
|
||||||
|
@ -129,6 +142,59 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
|
|
||||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||||
|
|
||||||
|
const updateLink = useUpdateLink();
|
||||||
|
|
||||||
|
const [fieldToEdit, setFieldToEdit] = useState<
|
||||||
|
"name" | "collection" | "tags" | "description" | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const submit = async (e?: any) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
|
||||||
|
const { updatedAt: b, ...oldLink } = activeLink;
|
||||||
|
const { updatedAt: a, ...newLink } = link;
|
||||||
|
|
||||||
|
console.log(oldLink);
|
||||||
|
console.log(newLink);
|
||||||
|
|
||||||
|
if (JSON.stringify(oldLink) === JSON.stringify(newLink)) {
|
||||||
|
setFieldToEdit(null);
|
||||||
|
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"));
|
||||||
|
setFieldToEdit(null);
|
||||||
|
console.log(data);
|
||||||
|
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 (
|
return (
|
||||||
<div className={clsx(className)} data-vaul-no-drag>
|
<div className={clsx(className)} data-vaul-no-drag>
|
||||||
<div
|
<div
|
||||||
|
@ -138,7 +204,7 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"overflow-hidden select-none relative h-32 opacity-80 bg-opacity-80",
|
"overflow-hidden select-none relative h-32 opacity-80 group",
|
||||||
standalone
|
standalone
|
||||||
? "sm:max-w-xl -mx-5 -mt-5 sm:rounded-t-2xl"
|
? "sm:max-w-xl -mx-5 -mt-5 sm:rounded-t-2xl"
|
||||||
: "-mx-4 -mt-4"
|
: "-mx-4 -mt-4"
|
||||||
|
@ -164,13 +230,116 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
) : (
|
) : (
|
||||||
<div className="duration-100 h-32 skeleton rounded-b-none"></div>
|
<div className="duration-100 h-32 skeleton rounded-b-none"></div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center rounded-md">
|
|
||||||
<LinkIcon link={link} />
|
<div className="absolute top-0 bottom-0 left-0 right-0 opacity-0 group-hover:opacity-100 duration-100 flex justify-end items-end">
|
||||||
|
<label className="btn btn-xs mb-2 mr-12">
|
||||||
|
{t("upload_preview_image")}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/jpg, image/jpeg, image/png"
|
||||||
|
onChange={async (e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
const load = toast.loading(t("uploading"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/v1/archives/${link.id}/preview`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(await res.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
setLink({
|
||||||
|
...link,
|
||||||
|
preview: data.preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(t("uploaded"));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
toast.dismiss(load);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="-mt-14 ml-8 relative w-fit pb-2">
|
||||||
|
<div className="tooltip tooltip-bottom" data-tip={t("change_icon")}>
|
||||||
|
<LinkIcon
|
||||||
|
link={link}
|
||||||
|
className="hover:bg-opacity-70 duration-100 cursor-pointer"
|
||||||
|
onClick={() => setIconPopover(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* #006796 */}
|
||||||
|
{iconPopover && (
|
||||||
|
<IconPopover
|
||||||
|
color={link.color || "#006796"}
|
||||||
|
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="max-w-xl sm:px-8 p-5 pb-8 pt-2">
|
<div className="max-w-xl sm:px-8 p-5 pb-8 pt-2">
|
||||||
{link.name && <p className="text-xl text-center mt-2">{link.name}</p>}
|
{fieldToEdit !== "name" ? (
|
||||||
|
<div className="text-xl mt-2 group pr-7">
|
||||||
|
<p
|
||||||
|
className={clsx("relative w-fit", !link.name && "text-neutral")}
|
||||||
|
>
|
||||||
|
{link.name || t("untitled")}
|
||||||
|
<EditButton
|
||||||
|
onClick={() => setFieldToEdit("name")}
|
||||||
|
className="top-0"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : fieldToEdit === "name" ? (
|
||||||
|
<form onSubmit={submit} className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autoFocus
|
||||||
|
onBlur={submit}
|
||||||
|
className="text-xl bg-transparent h-9 w-full outline-none border-b border-b-neutral-content"
|
||||||
|
value={link.name}
|
||||||
|
onChange={(e) => setLink({ ...link, name: e.target.value })}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
{link.url && (
|
{link.url && (
|
||||||
<>
|
<>
|
||||||
|
@ -179,7 +348,7 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
<p className="text-sm mb-2 text-neutral">{t("link")}</p>
|
<p className="text-sm mb-2 text-neutral">{t("link")}</p>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14">
|
<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 href={link.url} title={link.url} target="_blank">
|
||||||
{link.url}
|
{link.url}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -193,8 +362,18 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<p className="text-sm mb-2 text-neutral">{t("collection")}</p>
|
<div className="group relative">
|
||||||
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
||||||
|
{t("collection")}
|
||||||
|
{fieldToEdit !== "collection" && (
|
||||||
|
<EditButton
|
||||||
|
onClick={() => setFieldToEdit("collection")}
|
||||||
|
className="bottom-0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{fieldToEdit !== "collection" ? (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={
|
||||||
|
@ -202,7 +381,7 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
? `/public/collections/${link.collection.id}`
|
? `/public/collections/${link.collection.id}`
|
||||||
: `/collections/${link.collection.id}`
|
: `/collections/${link.collection.id}`
|
||||||
}
|
}
|
||||||
className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14"
|
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>
|
<p>{link.collection.name}</p>
|
||||||
<div className="absolute right-0 px-2 bg-base-200">
|
<div className="absolute right-0 px-2 bg-base-200">
|
||||||
|
@ -211,34 +390,56 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
icon={link.collection.icon}
|
icon={link.collection.icon}
|
||||||
size={30}
|
size={30}
|
||||||
weight={
|
weight={
|
||||||
(link.collection.iconWeight || "regular") as IconWeight
|
(link.collection.iconWeight ||
|
||||||
|
"regular") as IconWeight
|
||||||
}
|
}
|
||||||
color={link.collection.color || "#0ea5e9"}
|
color={link.collection.color}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bi-folder-fill text-2xl"
|
className="bi-folder-fill text-2xl"
|
||||||
style={{ color: link.collection.color || "#0ea5e9" }}
|
style={{ color: link.collection.color }}
|
||||||
></i>
|
></i>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
) : fieldToEdit === "collection" ? (
|
||||||
{link.tags[0] && (
|
<CollectionSelection
|
||||||
<>
|
onChange={setCollection}
|
||||||
<br />
|
defaultValue={
|
||||||
|
link.collection.id
|
||||||
<div>
|
? { value: link.collection.id, label: link.collection.name }
|
||||||
<p className="text-sm mb-2 text-neutral">{t("tags")}</p>
|
: { value: null as unknown as number, label: "Unorganized" }
|
||||||
|
}
|
||||||
|
creatable={false}
|
||||||
|
autoFocus
|
||||||
|
onBlur={submit}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
<br />
|
||||||
{link.tags.map((tag) =>
|
|
||||||
|
<div className="group relative">
|
||||||
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
||||||
|
{t("tags")}
|
||||||
|
{fieldToEdit !== "tags" && (
|
||||||
|
<EditButton
|
||||||
|
onClick={() => setFieldToEdit("tags")}
|
||||||
|
className="bottom-0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{fieldToEdit !== "tags" ? (
|
||||||
|
<div className="flex gap-2 flex-wrap rounded-md p-2 bg-base-200 border border-base-200 w-full text-xs">
|
||||||
|
{link.tags[0] ? (
|
||||||
|
link.tags.map((tag) =>
|
||||||
isPublicRoute ? (
|
isPublicRoute ? (
|
||||||
<div
|
<div
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
className="rounded-lg px-3 py-1 bg-base-200"
|
className="bg-base-200 p-1 hover:bg-neutral-content rounded-md duration-100"
|
||||||
>
|
>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -246,29 +447,63 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
<Link
|
<Link
|
||||||
href={"/tags/" + tag.id}
|
href={"/tags/" + tag.id}
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
className="rounded-lg px-3 py-1 bg-base-200"
|
className="bg-base-200 p-1 hover:bg-neutral-content btn btn-xs btn-ghost rounded-md"
|
||||||
>
|
>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="text-neutral text-base">{t("no_tags")}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
) : (
|
||||||
|
<TagSelection
|
||||||
|
onChange={setTags}
|
||||||
|
defaultValue={link.tags.map((e) => ({
|
||||||
|
label: e.name,
|
||||||
|
value: e.id,
|
||||||
|
}))}
|
||||||
|
autoFocus
|
||||||
|
onBlur={submit}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{link.description && (
|
|
||||||
<>
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div>
|
<div className="relative group">
|
||||||
<p className="text-sm mb-2 text-neutral">{t("notes")}</p>
|
<p className="text-sm mb-2 text-neutral relative w-fit flex justify-between">
|
||||||
|
{t("description")}
|
||||||
<div className="rounded-lg p-2 bg-base-200 hyphens-auto">
|
{fieldToEdit !== "description" && (
|
||||||
<p>{link.description}</p>
|
<EditButton
|
||||||
</div>
|
onClick={() => setFieldToEdit("description")}
|
||||||
</div>
|
className="bottom-0"
|
||||||
</>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{fieldToEdit !== "description" ? (
|
||||||
|
<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"
|
||||||
|
autoFocus
|
||||||
|
onBlur={submit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
@ -279,8 +514,9 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
{link.url ? t("preserved_formats") : t("file")}
|
{link.url ? t("preserved_formats") : t("file")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className={`flex flex-col gap-3`}>
|
<div className={`flex flex-col rounded-md p-3 bg-base-200`}>
|
||||||
{monolithAvailable(link) ? (
|
{monolithAvailable(link) ? (
|
||||||
|
<>
|
||||||
<PreservedFormatRow
|
<PreservedFormatRow
|
||||||
name={t("webpage")}
|
name={t("webpage")}
|
||||||
icon={"bi-filetype-html"}
|
icon={"bi-filetype-html"}
|
||||||
|
@ -288,9 +524,12 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
link={link}
|
link={link}
|
||||||
downloadable={true}
|
downloadable={true}
|
||||||
/>
|
/>
|
||||||
|
<hr className="m-3 border-t border-neutral-content" />
|
||||||
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
||||||
{screenshotAvailable(link) ? (
|
{screenshotAvailable(link) ? (
|
||||||
|
<>
|
||||||
<PreservedFormatRow
|
<PreservedFormatRow
|
||||||
name={t("screenshot")}
|
name={t("screenshot")}
|
||||||
icon={"bi-file-earmark-image"}
|
icon={"bi-file-earmark-image"}
|
||||||
|
@ -302,9 +541,12 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
link={link}
|
link={link}
|
||||||
downloadable={true}
|
downloadable={true}
|
||||||
/>
|
/>
|
||||||
|
<hr className="m-3 border-t border-neutral-content" />
|
||||||
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
||||||
{pdfAvailable(link) ? (
|
{pdfAvailable(link) ? (
|
||||||
|
<>
|
||||||
<PreservedFormatRow
|
<PreservedFormatRow
|
||||||
name={t("pdf")}
|
name={t("pdf")}
|
||||||
icon={"bi-file-earmark-pdf"}
|
icon={"bi-file-earmark-pdf"}
|
||||||
|
@ -312,15 +554,20 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
link={link}
|
link={link}
|
||||||
downloadable={true}
|
downloadable={true}
|
||||||
/>
|
/>
|
||||||
|
<hr className="m-3 border-t border-neutral-content" />
|
||||||
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
||||||
{readabilityAvailable(link) ? (
|
{readabilityAvailable(link) ? (
|
||||||
|
<>
|
||||||
<PreservedFormatRow
|
<PreservedFormatRow
|
||||||
name={t("readable")}
|
name={t("readable")}
|
||||||
icon={"bi-file-earmark-text"}
|
icon={"bi-file-earmark-text"}
|
||||||
format={ArchivedFormat.readability}
|
format={ArchivedFormat.readability}
|
||||||
link={link}
|
link={link}
|
||||||
/>
|
/>
|
||||||
|
<hr className="m-3 border-t border-neutral-content" />
|
||||||
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
||||||
{!isReady() && !atLeastOneFormatAvailable() ? (
|
{!isReady() && !atLeastOneFormatAvailable() ? (
|
||||||
|
@ -364,6 +611,22 @@ export default function LinkDetails({ className, link, standalone }: Props) {
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,12 +29,12 @@ export default function LinkCollection({
|
||||||
icon={link.collection.icon}
|
icon={link.collection.icon}
|
||||||
size={20}
|
size={20}
|
||||||
weight={(link.collection.iconWeight || "regular") as IconWeight}
|
weight={(link.collection.iconWeight || "regular") as IconWeight}
|
||||||
color={link.collection.color || "#0ea5e9"}
|
color={link.collection.color}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bi-folder-fill text-lg"
|
className="bi-folder-fill text-lg"
|
||||||
style={{ color: link.collection.color || "#0ea5e9" }}
|
style={{ color: link.collection.color }}
|
||||||
></i>
|
></i>
|
||||||
)}
|
)}
|
||||||
<p className="truncate capitalize">{collection?.name}</p>
|
<p className="truncate capitalize">{collection?.name}</p>
|
||||||
|
|
|
@ -10,10 +10,12 @@ export default function LinkIcon({
|
||||||
link,
|
link,
|
||||||
className,
|
className,
|
||||||
hideBackground,
|
hideBackground,
|
||||||
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
link: LinkIncludingShortenedCollectionAndTags;
|
||||||
className?: string;
|
className?: string;
|
||||||
hideBackground?: boolean;
|
hideBackground?: boolean;
|
||||||
|
onClick?: Function;
|
||||||
}) {
|
}) {
|
||||||
let iconClasses: string = clsx(
|
let iconClasses: string = clsx(
|
||||||
"rounded flex item-center justify-center shadow select-none z-10 w-12 h-12",
|
"rounded flex item-center justify-center shadow select-none z-10 w-12 h-12",
|
||||||
|
@ -27,14 +29,14 @@ export default function LinkIcon({
|
||||||
const [showFavicon, setShowFavicon] = React.useState<boolean>(true);
|
const [showFavicon, setShowFavicon] = React.useState<boolean>(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div onClick={() => onClick && onClick()}>
|
||||||
{link.icon ? (
|
{link.icon ? (
|
||||||
<div className={iconClasses}>
|
<div className={iconClasses}>
|
||||||
<Icon
|
<Icon
|
||||||
icon={link.icon}
|
icon={link.icon}
|
||||||
size={30}
|
size={30}
|
||||||
weight={(link.iconWeight || "regular") as IconWeight}
|
weight={(link.iconWeight || "regular") as IconWeight}
|
||||||
color={link.color || "#0ea5e9"}
|
color={link.color || "#006796"}
|
||||||
className="m-auto"
|
className="m-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +74,7 @@ export default function LinkIcon({
|
||||||
// />
|
// />
|
||||||
// )
|
// )
|
||||||
undefined}
|
undefined}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +86,7 @@ const LinkPlaceholderIcon = ({
|
||||||
icon: string;
|
icon: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(iconClasses, "aspect-square text-4xl text-[#000000]")}>
|
<div className={clsx(iconClasses, "aspect-square text-4xl text-[#006796]")}>
|
||||||
<i className={`${icon} m-auto`}></i>
|
<i className={`${icon} m-auto`}></i>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default function EditCollectionModal({
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="flex gap-3 items-end">
|
<div className="flex gap-3 items-end">
|
||||||
<IconPicker
|
<IconPicker
|
||||||
color={collection.color || "#0ea5e9"}
|
color={collection.color}
|
||||||
setColor={(color: string) =>
|
setColor={(color: string) =>
|
||||||
setCollection({ ...collection, color })
|
setCollection({ ...collection, color })
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,54 +145,6 @@ export default function EditLinkModal({ onClose, activeLink }: Props) {
|
||||||
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"
|
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>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="mb-2">{t("icon_and_preview")}</p>
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<IconPicker
|
|
||||||
hideDefaultIcon
|
|
||||||
color={link.color || "#0ea5e9"}
|
|
||||||
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: "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
alignment="-top-10 translate-x-20"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewAvailable(link) ? (
|
|
||||||
<Image
|
|
||||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
|
||||||
width={1280}
|
|
||||||
height={720}
|
|
||||||
alt=""
|
|
||||||
className="object-cover h-20 w-32 rounded-lg opacity-80"
|
|
||||||
onError={(e) => {
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
target.style.display = "none";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : link.preview === "unavailable" ? (
|
|
||||||
<div className="bg-gray-50 duration-100 h-20 w-32 bg-opacity-80 rounded-lg flex flex-col justify-center">
|
|
||||||
<p className="text-black text-sm text-center">
|
|
||||||
{t("preview_unavailable")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="duration-100 h-20 w-32 bg-opacity-80 skeleton rounded-lg"></div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ export default function LinkDetailModal({
|
||||||
></Link>
|
></Link>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<LinkDetails link={link} className="sm:mt-0 -mt-11" />
|
<LinkDetails activeLink={link} className="sm:mt-0 -mt-11" />
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default function PreservedFormatRow({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center rounded-lg p-2 bg-base-200">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<i className={`${icon} text-2xl text-primary`} />
|
<i className={`${icon} text-2xl text-primary`} />
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
|
|
|
@ -212,12 +212,12 @@ export default function ReadableView({ link }: Props) {
|
||||||
weight={
|
weight={
|
||||||
(link.collection.iconWeight || "regular") as IconWeight
|
(link.collection.iconWeight || "regular") as IconWeight
|
||||||
}
|
}
|
||||||
color={link.collection.color || "#0ea5e9"}
|
color={link.collection.color}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bi-folder-fill text-2xl"
|
className="bi-folder-fill text-2xl"
|
||||||
style={{ color: link.collection.color || "#0ea5e9" }}
|
style={{ color: link.collection.color }}
|
||||||
></i>
|
></i>
|
||||||
)}
|
)}
|
||||||
<p
|
<p
|
||||||
|
|
|
@ -119,12 +119,12 @@ export default function Index() {
|
||||||
weight={
|
weight={
|
||||||
(activeCollection.iconWeight || "regular") as IconWeight
|
(activeCollection.iconWeight || "regular") as IconWeight
|
||||||
}
|
}
|
||||||
color={activeCollection.color || "#0ea5e9"}
|
color={activeCollection.color}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bi-folder-fill text-3xl"
|
className="bi-folder-fill text-3xl"
|
||||||
style={{ color: activeCollection.color || "#0ea5e9" }}
|
style={{ color: activeCollection.color }}
|
||||||
></i>
|
></i>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ const Index = () => {
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
{getLink.data ? (
|
{getLink.data ? (
|
||||||
<LinkDetails
|
<LinkDetails
|
||||||
link={getLink.data}
|
activeLink={getLink.data}
|
||||||
className="sm:max-w-xl sm:m-auto sm:p-5 w-full"
|
className="sm:max-w-xl sm:m-auto sm:p-5 w-full"
|
||||||
standalone
|
standalone
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Index = () => {
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
{getLink.data ? (
|
{getLink.data ? (
|
||||||
<LinkDetails
|
<LinkDetails
|
||||||
link={getLink.data}
|
activeLink={getLink.data}
|
||||||
className="sm:max-w-xl sm:m-auto sm:p-5 w-full"
|
className="sm:max-w-xl sm:m-auto sm:p-5 w-full"
|
||||||
standalone
|
standalone
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -146,7 +146,6 @@ export default function Index() {
|
||||||
<i className={"bi-hash text-primary text-3xl"} />
|
<i className={"bi-hash text-primary text-3xl"} />
|
||||||
|
|
||||||
{renameTag ? (
|
{renameTag ? (
|
||||||
<>
|
|
||||||
<form onSubmit={submit} className="flex items-center gap-2">
|
<form onSubmit={submit} className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -160,7 +159,7 @@ export default function Index() {
|
||||||
id="expand-dropdown"
|
id="expand-dropdown"
|
||||||
className="btn btn-ghost btn-square btn-sm"
|
className="btn btn-ghost btn-square btn-sm"
|
||||||
>
|
>
|
||||||
<i className={"bi-check text-neutral text-2xl"}></i>
|
<i className={"bi-check2 text-neutral text-2xl"}></i>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
onClick={() => cancelUpdateTag()}
|
onClick={() => cancelUpdateTag()}
|
||||||
|
@ -170,7 +169,6 @@ export default function Index() {
|
||||||
<i className={"bi-x text-neutral text-2xl"}></i>
|
<i className={"bi-x text-neutral text-2xl"}></i>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="sm:text-3xl text-2xl capitalize">
|
<p className="sm:text-3xl text-2xl capitalize">
|
||||||
|
|
|
@ -372,7 +372,6 @@
|
||||||
"demo_desc": "This is only a demo instance of Linkwarden and uploads are disabled.",
|
"demo_desc": "This is only a demo instance of Linkwarden and uploads are disabled.",
|
||||||
"demo_desc_2": "If you want to try out the full version, you can sign up for a free trial at:",
|
"demo_desc_2": "If you want to try out the full version, you can sign up for a free trial at:",
|
||||||
"demo_button": "Login as demo user",
|
"demo_button": "Login as demo user",
|
||||||
"notes": "Notes",
|
|
||||||
"regular": "Regular",
|
"regular": "Regular",
|
||||||
"thin": "Thin",
|
"thin": "Thin",
|
||||||
"bold": "Bold",
|
"bold": "Bold",
|
||||||
|
@ -387,5 +386,10 @@
|
||||||
"icon": "Icon",
|
"icon": "Icon",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"preview_unavailable": "Preview Unavailable",
|
"preview_unavailable": "Preview Unavailable",
|
||||||
"icon_and_preview": "Icon & Preview"
|
"saved": "Saved",
|
||||||
|
"untitled": "Untitled",
|
||||||
|
"no_tags": "No tags.",
|
||||||
|
"no_description_provided": "No description provided.",
|
||||||
|
"change_icon": "Change Icon",
|
||||||
|
"upload_preview_image": "Upload Preview Image"
|
||||||
}
|
}
|
|
@ -162,6 +162,19 @@
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-select__indicator-separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-select__control--is-focused .react-select__dropdown-indicator,
|
||||||
|
.react-select__control .react-select__dropdown-indicator,
|
||||||
|
.react-select__control .react-select__dropdown-indicator:hover,
|
||||||
|
.react-select__control .react-select__dropdown-indicator:focus,
|
||||||
|
.react-select__control--is-focused .react-select__dropdown-indicator:hover,
|
||||||
|
.react-select__control--is-focused .react-select__dropdown-indicator:focus {
|
||||||
|
color: oklch(var(--n));
|
||||||
|
}
|
||||||
|
|
||||||
/* Theme */
|
/* Theme */
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
@ -169,13 +182,13 @@
|
||||||
@apply bg-base-200 hover:border-neutral-content;
|
@apply bg-base-200 hover:border-neutral-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-select-container .react-select__control--is-focused {
|
||||||
|
@apply border-primary hover:border-primary;
|
||||||
|
}
|
||||||
|
|
||||||
.react-select-container .react-select__menu {
|
.react-select-container .react-select__menu {
|
||||||
@apply bg-base-100 border-neutral-content border rounded-md;
|
@apply bg-base-100 border-neutral-content border rounded-md;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
.react-select-container .react-select__menu-list {
|
|
||||||
@apply h-20;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.react-select-container .react-select__input-container,
|
.react-select-container .react-select__input-container,
|
||||||
.react-select-container .react-select__single-value {
|
.react-select-container .react-select__single-value {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export interface LinkIncludingShortenedCollectionAndTags
|
||||||
pinnedBy?: {
|
pinnedBy?: {
|
||||||
id: number;
|
id: number;
|
||||||
}[];
|
}[];
|
||||||
|
updatedAt?: string;
|
||||||
collection: OptionalExcluding<Collection, "name" | "ownerId">;
|
collection: OptionalExcluding<Collection, "name" | "ownerId">;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Ŝarĝante…
Reference in New Issue