mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-06 04:07:01 +00:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9e389d09b | ||
|
|
00bfa73eec | ||
|
|
43a69970c4 | ||
|
|
9e7ba90a44 | ||
|
|
bf54ef0ff0 | ||
|
|
777129a622 | ||
|
|
f1cc320f3a | ||
|
|
a7a7b795ab | ||
|
|
1fac208267 | ||
|
|
a1507dd28a | ||
|
|
002626f352 | ||
|
|
e708b1e431 | ||
|
|
9a24e36cb1 | ||
|
|
fae0fc6690 | ||
|
|
28204ab351 | ||
|
|
5514272b64 | ||
|
|
c1b4a8af1f | ||
|
|
156ecc715c | ||
|
|
3741e1eab4 | ||
|
|
032459b08b | ||
|
|
9c70814fb8 | ||
|
|
1b38ffe24c | ||
|
|
15385de8c5 | ||
|
|
27b676cb6f | ||
|
|
3457dafed2 | ||
|
|
a462bedaaa | ||
|
|
b5072c0a8a | ||
|
|
c1d089b97c | ||
|
|
2bd7a7a253 | ||
|
|
dd201f843a | ||
|
|
f81b92aba2 | ||
|
|
b641e6da37 | ||
|
|
6ace6583d7 | ||
|
|
3e988e1bf9 | ||
|
|
5b51b9e7bb | ||
|
|
ff5f56311e | ||
|
|
3330bdca8f | ||
|
|
dc27c08666 | ||
|
|
ab9d9eb69e | ||
|
|
299306ec61 | ||
|
|
f7c7c63827 | ||
|
|
185cd5b5ba | ||
|
|
b2325319ba | ||
|
|
26b6fc6060 | ||
|
|
269301f916 | ||
|
|
864e58e186 | ||
|
|
598e434ca8 | ||
|
|
6958851d4f | ||
|
|
d09cdf814e | ||
|
|
4980e17d95 | ||
|
|
622d498e2d | ||
|
|
3be519d5dd | ||
|
|
f115eb4649 | ||
|
|
9489b99a61 | ||
|
|
44f7821f01 | ||
|
|
76aaa92a8a | ||
|
|
af5fbcaa4c | ||
|
|
b9d39e595b | ||
|
|
cc01986186 | ||
|
|
9801c4b6d3 | ||
|
|
53b31616cd | ||
|
|
41b54c92c4 | ||
|
|
1e6c405e02 | ||
|
|
8e841bcd46 | ||
|
|
a10fcfbb59 | ||
|
|
cdef808a8f | ||
|
|
201891ccdc | ||
|
|
aee129a893 | ||
|
|
dfb15f45bf | ||
|
|
55c100cb74 | ||
|
|
18c0d53f18 | ||
|
|
285958c0b7 | ||
|
|
981360dc6f | ||
|
|
45adcd128f | ||
|
|
d39d0da642 | ||
|
|
deeabb335f | ||
|
|
ae6479c838 | ||
|
|
fd2eaabe2c | ||
|
|
6852e0d7d4 | ||
|
|
f28856ceb4 | ||
|
|
1435a65694 | ||
|
|
adebd10963 | ||
|
|
e1d00bb7b5 | ||
|
|
4d7780ae61 | ||
|
|
ecc37bbdbe | ||
|
|
075985eb74 | ||
|
|
1f3e4738f9 | ||
|
|
abaf84702e | ||
|
|
4d450a9934 | ||
|
|
f22cafd171 | ||
|
|
10253f2cc3 | ||
|
|
419a7cdfaf | ||
|
|
71a58f4394 |
@@ -120,8 +120,8 @@ const RootComponent = ({
|
||||
auth: MobileAuth;
|
||||
}) => {
|
||||
const { colorScheme } = useColorScheme();
|
||||
const updateLink = useUpdateLink({ auth, Alert });
|
||||
const deleteLink = useDeleteLink({ auth, Alert });
|
||||
const updateLink = useUpdateLink(auth);
|
||||
const deleteLink = useDeleteLink(auth);
|
||||
|
||||
const { tmp } = useTmpStore();
|
||||
|
||||
@@ -229,12 +229,12 @@ const RootComponent = ({
|
||||
{tmp.link && tmp.user && (
|
||||
<DropdownMenu.Item
|
||||
key="pin-link"
|
||||
onSelect={() => {
|
||||
onSelect={async () => {
|
||||
const isAlreadyPinned =
|
||||
tmp.link?.pinnedBy && tmp.link.pinnedBy[0]
|
||||
? true
|
||||
: false;
|
||||
updateLink.mutateAsync({
|
||||
await updateLink.mutateAsync({
|
||||
...(tmp.link as LinkIncludingShortenedCollectionAndTags),
|
||||
pinnedBy: (isAlreadyPinned
|
||||
? [{ id: undefined }]
|
||||
@@ -282,15 +282,18 @@ const RootComponent = ({
|
||||
{
|
||||
text: "Delete",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
onPress: () => {
|
||||
deleteLink.mutate(
|
||||
tmp.link?.id as number
|
||||
tmp.link?.id as number,
|
||||
{
|
||||
onSuccess: async () => {
|
||||
await deleteLinkCache(
|
||||
tmp.link?.id as number
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await deleteLinkCache(
|
||||
tmp.link?.id as number
|
||||
);
|
||||
|
||||
// go back
|
||||
router.back();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function IncomingScreen() {
|
||||
const { auth } = useAuthStore();
|
||||
const router = useRouter();
|
||||
const { data, updateData } = useDataStore();
|
||||
const addLink = useAddLink({ auth });
|
||||
const addLink = useAddLink(auth);
|
||||
const { colorScheme } = useColorScheme();
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
export default function AddLinkSheet() {
|
||||
const actionSheetRef = useRef<ActionSheetRef>(null);
|
||||
const { auth } = useAuthStore();
|
||||
const addLink = useAddLink({ auth, Alert });
|
||||
const addLink = useAddLink(auth);
|
||||
const [link, setLink] = useState("");
|
||||
const { colorScheme } = useColorScheme();
|
||||
|
||||
@@ -43,12 +43,21 @@ export default function AddLinkSheet() {
|
||||
/>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
addLink.mutate({ url: link });
|
||||
|
||||
actionSheetRef.current?.hide();
|
||||
setLink("");
|
||||
}}
|
||||
onPress={() =>
|
||||
addLink.mutate(
|
||||
{ url: link },
|
||||
{
|
||||
onSuccess: () => {
|
||||
actionSheetRef.current?.hide();
|
||||
setLink("");
|
||||
},
|
||||
onError: (error) => {
|
||||
Alert.alert("Error", "There was an error adding the link.");
|
||||
console.error("Error adding link:", error);
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
isLoading={addLink.isPending}
|
||||
variant="accent"
|
||||
className="mb-2"
|
||||
|
||||
@@ -33,7 +33,7 @@ const Main = (props: SheetProps<"edit-link-sheet">) => {
|
||||
const [link, setLink] = useState<
|
||||
LinkIncludingShortenedCollectionAndTags | undefined
|
||||
>(props.payload?.link);
|
||||
const updateLink = useUpdateLink({ auth, Alert });
|
||||
const editLink = useUpdateLink(auth);
|
||||
const router = useSheetRouter("edit-link-sheet");
|
||||
const { colorScheme } = useColorScheme();
|
||||
|
||||
@@ -124,15 +124,23 @@ const Main = (props: SheetProps<"edit-link-sheet">) => {
|
||||
/>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
updateLink.mutate(link as LinkIncludingShortenedCollectionAndTags);
|
||||
if (link && tmp.link)
|
||||
updateTmp({
|
||||
link,
|
||||
});
|
||||
SheetManager.hide("edit-link-sheet");
|
||||
}}
|
||||
isLoading={updateLink.isPending}
|
||||
onPress={() =>
|
||||
editLink.mutate(link as LinkIncludingShortenedCollectionAndTags, {
|
||||
onSuccess: () => {
|
||||
if (link && tmp.link)
|
||||
updateTmp({
|
||||
link,
|
||||
});
|
||||
|
||||
SheetManager.hide("edit-link-sheet");
|
||||
},
|
||||
onError: (error) => {
|
||||
Alert.alert("Error", "There was an error editing the link.");
|
||||
console.error("Error editing link:", error);
|
||||
},
|
||||
})
|
||||
}
|
||||
isLoading={editLink.isPending}
|
||||
variant="accent"
|
||||
className="mb-2"
|
||||
>
|
||||
@@ -154,7 +162,7 @@ const Main = (props: SheetProps<"edit-link-sheet">) => {
|
||||
|
||||
const Collections = () => {
|
||||
const { auth } = useAuthStore();
|
||||
const addLink = useAddLink({ auth });
|
||||
const addLink = useAddLink(auth);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const router = useSheetRouter("edit-link-sheet");
|
||||
const { link: currentLink } = useSheetRouteParams<
|
||||
@@ -262,7 +270,7 @@ const Collections = () => {
|
||||
|
||||
const Tags = () => {
|
||||
const { auth } = useAuthStore();
|
||||
const addLink = useAddLink({ auth });
|
||||
const addLink = useAddLink(auth);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const router = useSheetRouter("edit-link-sheet");
|
||||
const params = useSheetRouteParams("edit-link-sheet", "tags");
|
||||
|
||||
@@ -19,7 +19,7 @@ const CollectionListing = ({ collection }: Props) => {
|
||||
const router = useRouter();
|
||||
const { colorScheme } = useColorScheme();
|
||||
|
||||
const deleteCollection = useDeleteCollection({ auth, Alert });
|
||||
const deleteCollection = useDeleteCollection(auth);
|
||||
|
||||
return (
|
||||
<ContextMenu.Root>
|
||||
|
||||
@@ -40,12 +40,12 @@ type Props = {
|
||||
const LinkListing = ({ link, dashboard }: Props) => {
|
||||
const { auth } = useAuthStore();
|
||||
const router = useRouter();
|
||||
const updateLink = useUpdateLink({ auth, Alert });
|
||||
const updateLink = useUpdateLink(auth);
|
||||
const { data: user } = useUser(auth);
|
||||
const { colorScheme } = useColorScheme();
|
||||
const { data } = useDataStore();
|
||||
|
||||
const deleteLink = useDeleteLink({ auth, Alert });
|
||||
const deleteLink = useDeleteLink(auth);
|
||||
|
||||
const [url, setUrl] = useState("");
|
||||
|
||||
@@ -57,7 +57,7 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [link.url]);
|
||||
}, [link]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Root>
|
||||
@@ -122,8 +122,8 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
<View className="flex flex-row gap-1 items-center mt-1.5 pr-1.5 self-start rounded-md">
|
||||
<Folder
|
||||
size={16}
|
||||
fill={link.collection.color || "#0ea5e9"}
|
||||
color={link.collection.color || "#0ea5e9"}
|
||||
fill={link.collection.color || ""}
|
||||
color={link.collection.color || ""}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
@@ -215,11 +215,11 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
|
||||
<ContextMenu.Item
|
||||
key="pin-link"
|
||||
onSelect={() => {
|
||||
onSelect={async () => {
|
||||
const isAlreadyPinned =
|
||||
link?.pinnedBy && link.pinnedBy[0] ? true : false;
|
||||
|
||||
updateLink.mutateAsync({
|
||||
await updateLink.mutateAsync({
|
||||
...link,
|
||||
pinnedBy: (isAlreadyPinned
|
||||
? [{ id: undefined }]
|
||||
@@ -319,10 +319,12 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
{
|
||||
text: "Delete",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
deleteLink.mutate(link.id as number);
|
||||
|
||||
await deleteLinkCache(link.id as number);
|
||||
onPress: () => {
|
||||
deleteLink.mutate(link.id as number, {
|
||||
onSuccess: async () => {
|
||||
await deleteLinkCache(link.id as number);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import useOnScreen from "@/hooks/useOnScreen";
|
||||
import { useCollections } from "@linkwarden/router/collections";
|
||||
import { useUser } from "@linkwarden/router/user";
|
||||
import { useGetLink } from "@linkwarden/router/links";
|
||||
import { useGetLink, useLinks } from "@linkwarden/router/links";
|
||||
import { useRouter } from "next/router";
|
||||
import openLink from "@/lib/client/openLink";
|
||||
import LinkIcon from "./LinkViews/LinkComponents/LinkIcon";
|
||||
@@ -82,6 +82,8 @@ export function Card({ link, editMode, dashboardType }: Props) {
|
||||
settings: { show },
|
||||
} = useLocalSettingsStore();
|
||||
|
||||
const { links } = useLinks();
|
||||
|
||||
const router = useRouter();
|
||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||
|
||||
@@ -100,7 +102,7 @@ export function Card({ link, editMode, dashboardType }: Props) {
|
||||
(e) => e.id === link.collection.id
|
||||
) as CollectionIncludingMembersAndLinkCount
|
||||
);
|
||||
}, [collections, link]);
|
||||
}, [collections, links]);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const isVisible = useOnScreen(ref);
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function DragNDrop({
|
||||
onDragEnd: onDragEndProp,
|
||||
}: DragNDropProps) {
|
||||
const { t } = useTranslation();
|
||||
const updateLink = useUpdateLink({ toast, t });
|
||||
const updateLink = useUpdateLink();
|
||||
const pinLink = usePinLink();
|
||||
const { data: user } = useUser();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -104,7 +104,25 @@ export default function DragNDrop({
|
||||
updatedLink: LinkIncludingShortenedCollectionAndTags,
|
||||
opts?: { invalidateDashboardOnError?: boolean }
|
||||
) => {
|
||||
updateLink.mutateAsync(updatedLink);
|
||||
const load = toast.loading(t("updating"));
|
||||
await updateLink.mutateAsync(updatedLink, {
|
||||
onSettled: async (_, error) => {
|
||||
toast.dismiss(load);
|
||||
if (error) {
|
||||
if (
|
||||
opts?.invalidateDashboardOnError &&
|
||||
typeof queryClient !== "undefined"
|
||||
) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["dashboardData"],
|
||||
});
|
||||
}
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.success(t("updated"));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// DROP ON TAG
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function LinkDetails({
|
||||
);
|
||||
};
|
||||
|
||||
const updateLink = useUpdateLink({ toast, t });
|
||||
const updateLink = useUpdateLink();
|
||||
const updateFile = useUpdateFile();
|
||||
|
||||
const submit = async (e?: any) => {
|
||||
@@ -126,9 +126,21 @@ export default function LinkDetails({
|
||||
return;
|
||||
}
|
||||
|
||||
updateLink.mutateAsync(link);
|
||||
const load = toast.loading(t("updating"));
|
||||
|
||||
setMode && setMode("view");
|
||||
await updateLink.mutateAsync(link, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.success(t("updated"));
|
||||
setMode && setMode("view");
|
||||
setLink(data);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const setCollection = (e: any) => {
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function LinkActions({
|
||||
const [refreshPreservationsModal, setRefreshPreservationsModal] =
|
||||
useState(false);
|
||||
|
||||
const deleteLink = useDeleteLink({ toast, t });
|
||||
const deleteLink = useDeleteLink();
|
||||
|
||||
const updateArchive = async () => {
|
||||
const load = toast.loading(t("sending_request"));
|
||||
@@ -131,7 +131,13 @@ export default function LinkActions({
|
||||
onClick={async (e) => {
|
||||
if (e.shiftKey) {
|
||||
const load = toast.loading(t("deleting"));
|
||||
await deleteLink.mutateAsync(link.id as number);
|
||||
await deleteLink.mutateAsync(link.id as number, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
if (error) toast.error(error.message);
|
||||
else toast.success(t("deleted"));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setDeleteLinkModal(true);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@linkwarden/types/global";
|
||||
import Image from "next/image";
|
||||
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import Icon from "@/components/Icon";
|
||||
import { IconWeight } from "@phosphor-icons/react";
|
||||
import clsx from "clsx";
|
||||
@@ -30,10 +30,6 @@ function LinkIcon({
|
||||
|
||||
const [faviconLoaded, setFaviconLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setFaviconLoaded(false);
|
||||
}, [link.url]);
|
||||
|
||||
return (
|
||||
<div onClick={() => onClick && onClick()}>
|
||||
{link.icon ? (
|
||||
|
||||
@@ -66,13 +66,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)} />
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import TextInput from "@/components/TextInput";
|
||||
import { CollectionIncludingMembersAndLinkCount } from "@linkwarden/types/global";
|
||||
import { useRouter } from "next/router";
|
||||
import usePermissions from "@/hooks/usePermissions";
|
||||
@@ -21,6 +22,7 @@ export default function DeleteCollectionModal({
|
||||
const { t } = useTranslation();
|
||||
const [collection, setCollection] =
|
||||
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
const router = useRouter();
|
||||
const permissions = usePermissions(collection.id as number);
|
||||
|
||||
@@ -28,15 +30,32 @@ export default function DeleteCollectionModal({
|
||||
setCollection(activeCollection);
|
||||
}, []);
|
||||
|
||||
const deleteCollection = useDeleteCollection({ toast, t });
|
||||
const deleteCollection = useDeleteCollection();
|
||||
|
||||
const submit = async () => {
|
||||
if (!collection) return null;
|
||||
if (!submitLoader) {
|
||||
setSubmitLoader(true);
|
||||
if (!collection) return null;
|
||||
|
||||
deleteCollection.mutateAsync(collection.id as number);
|
||||
setSubmitLoader(true);
|
||||
|
||||
onClose();
|
||||
router.push("/collections");
|
||||
const load = toast.loading(t("deleting_collection"));
|
||||
|
||||
deleteCollection.mutateAsync(collection.id as number, {
|
||||
onSettled: (data, error) => {
|
||||
setSubmitLoader(false);
|
||||
toast.dismiss(load);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
onClose();
|
||||
toast.success(t("deleted"));
|
||||
router.push("/collections");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
|
||||
const [link, setLink] =
|
||||
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
||||
|
||||
const deleteLink = useDeleteLink({ toast, t });
|
||||
const deleteLink = useDeleteLink();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -26,15 +26,26 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
|
||||
}, []);
|
||||
|
||||
const submit = async () => {
|
||||
deleteLink.mutateAsync(link.id as number);
|
||||
const load = toast.loading(t("deleting"));
|
||||
|
||||
if (
|
||||
router.pathname.startsWith("/links/[id]") ||
|
||||
router.pathname.startsWith("/preserved/[id]")
|
||||
) {
|
||||
router.push("/dashboard");
|
||||
}
|
||||
onClose();
|
||||
await deleteLink.mutateAsync(link.id as number, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
if (
|
||||
router.pathname.startsWith("/links/[id]") ||
|
||||
router.pathname.startsWith("/preserved/[id]")
|
||||
) {
|
||||
router.push("/dashboard");
|
||||
}
|
||||
toast.success(t("deleted"));
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function LinkModal({
|
||||
|
||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||
|
||||
const deleteLink = useDeleteLink({ toast, t });
|
||||
const deleteLink = useDeleteLink();
|
||||
|
||||
const [mode, setMode] = useState<"view" | "edit">(activeMode || "view");
|
||||
|
||||
@@ -51,8 +51,13 @@ export default function LinkModal({
|
||||
setTimeout(() => (document.body.style.pointerEvents = ""), 0);
|
||||
|
||||
if (e.shiftKey && link.id) {
|
||||
deleteLink.mutateAsync(link.id);
|
||||
|
||||
const loading = toast.loading(t("deleting"));
|
||||
await deleteLink.mutateAsync(link.id, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(loading);
|
||||
error ? toast.error(error.message) : toast.success(t("deleted"));
|
||||
},
|
||||
});
|
||||
onClose();
|
||||
} else {
|
||||
onDelete();
|
||||
|
||||
@@ -7,17 +7,14 @@ import { useRouter } from "next/router";
|
||||
import Modal from "../Modal";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useCollections } from "@linkwarden/router/collections";
|
||||
import { useAddLink } from "@linkwarden/router/links";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
PostLinkSchema,
|
||||
PostLinkSchemaType,
|
||||
} from "@linkwarden/lib/schemaValidation";
|
||||
import { PostLinkSchemaType } from "@linkwarden/lib/schemaValidation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "../ui/separator";
|
||||
import { useAddLink } from "@linkwarden/router/links";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
onClose: Function;
|
||||
};
|
||||
|
||||
export default function NewLinkModal({ onClose }: Props) {
|
||||
@@ -34,13 +31,10 @@ export default function NewLinkModal({ onClose }: Props) {
|
||||
},
|
||||
} as PostLinkSchemaType;
|
||||
|
||||
const addLink = useAddLink({
|
||||
toast,
|
||||
t,
|
||||
});
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [link, setLink] = useState<PostLinkSchemaType>(initial);
|
||||
const addLink = useAddLink();
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
const [optionsExpanded, setOptionsExpanded] = useState(false);
|
||||
const router = useRouter();
|
||||
const { data: collections = [] } = useCollections();
|
||||
@@ -86,17 +80,22 @@ export default function NewLinkModal({ onClose }: Props) {
|
||||
}, []);
|
||||
|
||||
const submit = async () => {
|
||||
const dataValidation = PostLinkSchema.safeParse(link);
|
||||
|
||||
if (!dataValidation.success)
|
||||
return toast.error(
|
||||
`Error: ${
|
||||
dataValidation.error.issues[0].message
|
||||
} [${dataValidation.error.issues[0].path.join(", ")}]`
|
||||
);
|
||||
|
||||
addLink.mutateAsync(link);
|
||||
onClose();
|
||||
if (!submitLoader) {
|
||||
setSubmitLoader(true);
|
||||
const load = toast.loading(t("creating_link"));
|
||||
await addLink.mutateAsync(link, {
|
||||
onSettled: (data, error) => {
|
||||
setSubmitLoader(false);
|
||||
toast.dismiss(load);
|
||||
if (error) {
|
||||
toast.error(t(error.message));
|
||||
} else {
|
||||
onClose();
|
||||
toast.success(t("link_created"));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -182,11 +182,7 @@ export default function Navbar({
|
||||
</div>
|
||||
)}
|
||||
{newLinkModal && (
|
||||
<NewLinkModal
|
||||
onClose={() => {
|
||||
setNewLinkModal(false);
|
||||
}}
|
||||
/>
|
||||
<NewLinkModal onClose={() => setNewLinkModal(false)} />
|
||||
)}
|
||||
{newCollectionModal && (
|
||||
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
|
||||
|
||||
@@ -40,13 +40,7 @@ export default function NoLinksFound({ text }: Props) {
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
{newLinkModal && (
|
||||
<NewLinkModal
|
||||
onClose={() => {
|
||||
setNewLinkModal(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ export default function SearchBar({ placeholder }: Props) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* {user?.hasUnIndexedLinks && !dismissSearchNote ? (
|
||||
{user?.hasUnIndexedLinks && !dismissSearchNote ? (
|
||||
<div
|
||||
role="alert"
|
||||
className="border border-neutral p-2 my-1 rounded flex flex-col gap-2"
|
||||
@@ -204,7 +204,7 @@ export default function SearchBar({ placeholder }: Props) {
|
||||
Dismiss
|
||||
</Button>
|
||||
</div>
|
||||
) : undefined} */}
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,21 +6,39 @@ import { useUser } from "@linkwarden/router/user";
|
||||
|
||||
const usePinLink = () => {
|
||||
const { t } = useTranslation();
|
||||
const updateLink = useUpdateLink({ toast, t });
|
||||
const updateLink = useUpdateLink();
|
||||
const { data: user } = useUser();
|
||||
|
||||
// Return a function that can be used to pin/unpin the link
|
||||
const pinLink = async (link: LinkIncludingShortenedCollectionAndTags) => {
|
||||
const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0] ? true : false;
|
||||
|
||||
const load = toast.loading(t("updating"));
|
||||
|
||||
try {
|
||||
updateLink.mutateAsync({
|
||||
...link,
|
||||
pinnedBy: (isAlreadyPinned
|
||||
? [{ id: undefined }]
|
||||
: [{ id: user?.id }]) as any,
|
||||
});
|
||||
await updateLink.mutateAsync(
|
||||
{
|
||||
...link,
|
||||
pinnedBy: (isAlreadyPinned
|
||||
? [{ id: undefined }]
|
||||
: [{ id: user?.id }]) as any,
|
||||
},
|
||||
{
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.success(
|
||||
isAlreadyPinned ? t("link_unpinned") : t("link_pinned")
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
toast.dismiss(load);
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ const Page: NextPageWithLayout = () => {
|
||||
<ul className="px-5 list-disc">
|
||||
<Trans
|
||||
i18nKey="regenerate_broken_preserved_content_desc"
|
||||
components={[<li key={0} />]}
|
||||
components={[<li />]}
|
||||
values={{
|
||||
count: workerStats.data?.link.failed,
|
||||
}}
|
||||
@@ -181,7 +181,7 @@ const Page: NextPageWithLayout = () => {
|
||||
<ul className="px-5 list-disc">
|
||||
<Trans
|
||||
i18nKey="delete_all_preserved_content_and_regenerate_desc"
|
||||
components={[<li key={0} />]}
|
||||
components={[<li />]}
|
||||
/>
|
||||
</ul>
|
||||
<div role="alert" className="alert alert-warning mt-3">
|
||||
|
||||
@@ -26,6 +26,7 @@ import ViewDropdown from "@/components/ViewDropdown";
|
||||
import clsx from "clsx";
|
||||
import Icon from "@/components/Icon";
|
||||
import Droppable from "@/components/Droppable";
|
||||
import { useUpdateLink } from "@linkwarden/router/links";
|
||||
import { NextPageWithLayout } from "./_app";
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
@@ -94,6 +95,7 @@ const Page: NextPageWithLayout = () => {
|
||||
const [showSurveyModal, setShowsSurveyModal] = useState(false);
|
||||
|
||||
const updateUser = useUpdateUser();
|
||||
const updateLink = useUpdateLink();
|
||||
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
|
||||
@@ -190,13 +192,7 @@ const Page: NextPageWithLayout = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{newLinkModal && (
|
||||
<NewLinkModal
|
||||
onClose={() => {
|
||||
setNewLinkModal(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Benutzerverwaltung",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Suche nach Benutzern",
|
||||
"no_users_found": "Keine Benutzer gefunden.",
|
||||
"no_user_found_in_search": "Keine Benutzer mit den angegebenen Suchparametern gefunden.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Diese Sammlung wird öffentlich geteilt.",
|
||||
"search_for_links": "Nach Links suchen",
|
||||
"search_query_invalid_symbol": "Die Suchabfrage sollte nicht '%' enthalten.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Modal in einem neuen Tab öffnen",
|
||||
"file": "Datei",
|
||||
"tag_preservation_rule_label": "Regeln zur Erhaltung nach Tag (globale Einstellungen überschreiben):",
|
||||
"ai_tagging": "AI-Tagging",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Alle defekten oder fehlenden Speicherungen für alle Benutzer neu erzeugen.",
|
||||
"delete_all_preservations": "Löschen der Archivierung für alle Webseiten, gilt für alle Benutzer.",
|
||||
"delete_all_preservations_and_regenerate": "Löschen und Neuarchivierung aller Webseiten, gilt für alle Benutzer.",
|
||||
"delete_all_preservation_warning": "Diese Aktion löscht alle bestehenden Archivierungen auf dem Server.",
|
||||
"no_broken_preservations": "Keine beschädigten Archivierungen.",
|
||||
"links_are_being_represerved": "Links werden wiederhergestellt...",
|
||||
"cancel": "Abbrechen",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Administración de usuarios",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Buscar usuarios",
|
||||
"no_users_found": "No se ha encontrado el usuario.",
|
||||
"no_user_found_in_search": "No se ha encontrado el usuario con esos parámetros de búsqueda.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Esta colección está siendo compartida públicamente.",
|
||||
"search_for_links": "Buscar Enlaces",
|
||||
"search_query_invalid_symbol": "La consulta de búsqueda no debe contener '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Abrir este modal en una nueva pestaña",
|
||||
"file": "Archivo",
|
||||
"tag_preservation_rule_label": "Reglas de conservación por etiqueta (sobreescribe la configuración global):",
|
||||
"ai_tagging": "Etiquetado con AI",
|
||||
"worker": "Trabajador",
|
||||
"regenerate_broken_preservations": "Regenerar todas las conservaciones dañadas o perdidas para todos los usuarios.",
|
||||
"delete_all_preservations": "Eliminar la conservación para todas las páginas web, se aplica a todos los usuarios.",
|
||||
"delete_all_preservations_and_regenerate": "Eliminar y reconservar todas las páginas web, se aplica a todos los usuarios.",
|
||||
"delete_all_preservation_warning": "Esta acción eliminará todas las conservaciones existentes en el servidor.",
|
||||
"no_broken_preservations": "No hay conservaciones rotas.",
|
||||
"links_are_being_represerved": "Los enlaces están siendo reconservados...",
|
||||
"cancel": "Cancelar",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expandir barra lateral",
|
||||
"shrink_sidebar": "Contraer barra lateral",
|
||||
"trial_left_plural": "La prueba termina en {{count}} días. Suscríbete.",
|
||||
"trial_left_singular": "La prueba termina en 1 día. Suscríbete."
|
||||
"trial_left_singular": "La prueba termina en 1 día. Suscríbete.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Administration des utilisateurs",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Rechercher des utilisateurs",
|
||||
"no_users_found": "Aucun utilisateur trouvé.",
|
||||
"no_user_found_in_search": "Aucun utilisateur trouvé avec la requête de recherche donnée.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Cette collection est partagée publiquement.",
|
||||
"search_for_links": "Rechercher des liens",
|
||||
"search_query_invalid_symbol": "La recherche ne doit pas contenir '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Ouvrir cette modale dans un nouvel onglet",
|
||||
"file": "Ficher",
|
||||
"tag_preservation_rule_label": "Règles de préservation par étiquette (outrepasser les paramètres globaux) :",
|
||||
"ai_tagging": "Étiquetage par IA",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Régénérer toutes les conservations cassées ou manquantes pour tous les utilisateurs.",
|
||||
"delete_all_preservations": "Supprimer la conservation pour toutes les pages web, s'applique à tous les utilisateurs.",
|
||||
"delete_all_preservations_and_regenerate": "Supprimer et conserver de nouveau toutes les pages web, s'applique à tous les utilisateurs.",
|
||||
"delete_all_preservation_warning": "Cette action va supprimer toutes les conservations existantes sur le serveur.",
|
||||
"no_broken_preservations": "Aucune conservation cassée.",
|
||||
"links_are_being_represerved": "La conservation des liens est en cours de restauration...",
|
||||
"cancel": "Annuler",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Agrandir le panneau latéral",
|
||||
"shrink_sidebar": "Réduire le panneau latéral",
|
||||
"trial_left_plural": "La période d'essai se termine dans {{count}} jours. Abonnez-vous.",
|
||||
"trial_left_singular": "La période d'essai se termine dans 1 jour."
|
||||
"trial_left_singular": "La période d'essai se termine dans 1 jour.",
|
||||
"apply_members_roles_to_subcollections": "Appliquer les membres et les rôles aux sous-collections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Amministrazione Utenti",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Cerca Utenti",
|
||||
"no_users_found": "Nessun utente trovato.",
|
||||
"no_user_found_in_search": "Nessun utente trovato con la query di ricerca specificata.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Questa collezione è condivisa pubblicamente.",
|
||||
"search_for_links": "Cerca per Link",
|
||||
"search_query_invalid_symbol": "The search query should not contain '%'.",
|
||||
"search_operators": "Operatori di ricerca",
|
||||
"search_operator_name": "Titolo",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collezione",
|
||||
"search_operator_before": "Precedente",
|
||||
"search_operator_after": "Successivo",
|
||||
"search_operator_public": "Pubblico",
|
||||
"search_operator_description": "Descrizione",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Fissato",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Open this modal in a new tab",
|
||||
"file": "File",
|
||||
"tag_preservation_rule_label": "Preservation rules by tag (override global settings):",
|
||||
"ai_tagging": "AI Tagging",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Regenerate all broken or missing preservations for all users.",
|
||||
"delete_all_preservations": "Delete preservation for all webpages, applies to all users.",
|
||||
"delete_all_preservations_and_regenerate": "Delete and re-preserve all webpages, applies to all users.",
|
||||
"delete_all_preservation_warning": "This action will delete all existing preservations on the server.",
|
||||
"no_broken_preservations": "No broken preservations.",
|
||||
"links_are_being_represerved": "Links are being re-preserved...",
|
||||
"cancel": "Annulla",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "ユーザー管理",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "ユーザーを検索",
|
||||
"no_users_found": "ユーザーが見つかりません",
|
||||
"no_user_found_in_search": "指定された検索クエリでユーザーが見つかりませんでした。",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "このコレクションは全体に公開されています。",
|
||||
"search_for_links": "リンクを検索",
|
||||
"search_query_invalid_symbol": "検索クエリに'%'を含めることはできません",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "新しいタブでこのモーダルを開く",
|
||||
"file": "ファイル",
|
||||
"tag_preservation_rule_label": "タグごとの保存ルール(グローバル設定を上書き):",
|
||||
"ai_tagging": "AIタグ付け",
|
||||
"worker": "ワーカー",
|
||||
"regenerate_broken_preservations": "すべてのユーザーの壊れた保存または欠落している保存を再生成します。",
|
||||
"delete_all_preservations": "すべての Web ページの保存を削除し、すべてのユーザーに適用されます。",
|
||||
"delete_all_preservations_and_regenerate": "すべての Web ページを削除して再保存し、すべてのユーザーに適用されます。",
|
||||
"delete_all_preservation_warning": "この操作は、サーバー上の既存のすべての保存を削除します。",
|
||||
"no_broken_preservations": "壊れた保存はありません。",
|
||||
"links_are_being_represerved": "リンクを再保存しています…",
|
||||
"cancel": "キャンセル",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "サイドバーを展開",
|
||||
"shrink_sidebar": "サイドバーを縮小",
|
||||
"trial_left_plural": "お試し期間は {{count}} 日で終了します。登録してください。",
|
||||
"trial_left_singular": "試用期間はあと 1 日で終了します。登録してください。"
|
||||
"trial_left_singular": "試用期間はあと 1 日で終了します。登録してください。",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Gebruikersbeheer",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Zoeken naar Gebruikers",
|
||||
"no_users_found": "Geen gebruikers gevonden.",
|
||||
"no_user_found_in_search": "Geen gebruikers gevonden met de gegeven zoekopdracht.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "This collection is being shared publicly.",
|
||||
"search_for_links": "Search for Links",
|
||||
"search_query_invalid_symbol": "The search query should not contain '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Open this modal in a new tab",
|
||||
"file": "Bestand",
|
||||
"tag_preservation_rule_label": "Preservation rules by tag (override global settings):",
|
||||
"ai_tagging": "AI Tagging",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Regenerate all broken or missing preservations for all users.",
|
||||
"delete_all_preservations": "Delete preservation for all webpages, applies to all users.",
|
||||
"delete_all_preservations_and_regenerate": "Delete and re-preserve all webpages, applies to all users.",
|
||||
"delete_all_preservation_warning": "This action will delete all existing preservations on the server.",
|
||||
"no_broken_preservations": "No broken preservations.",
|
||||
"links_are_being_represerved": "Links are being re-preserved...",
|
||||
"cancel": "Cancel",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Zarządzanie użytkownikami",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Wyszukaj użytkowników",
|
||||
"no_users_found": "Nie znaleziono użytkowników.",
|
||||
"no_user_found_in_search": "Nie znaleziono użytkowników z podanym zapytaniem.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "This collection is being shared publicly.",
|
||||
"search_for_links": "Search for Links",
|
||||
"search_query_invalid_symbol": "The search query should not contain '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Open this modal in a new tab",
|
||||
"file": "Plik",
|
||||
"tag_preservation_rule_label": "Preservation rules by tag (override global settings):",
|
||||
"ai_tagging": "AI Tagging",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Regenerate all broken or missing preservations for all users.",
|
||||
"delete_all_preservations": "Delete preservation for all webpages, applies to all users.",
|
||||
"delete_all_preservations_and_regenerate": "Delete and re-preserve all webpages, applies to all users.",
|
||||
"delete_all_preservation_warning": "This action will delete all existing preservations on the server.",
|
||||
"no_broken_preservations": "No broken preservations.",
|
||||
"links_are_being_represerved": "Links are being re-preserved...",
|
||||
"cancel": "Cancel",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Administração de Usuários",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Pesquisar por Usuários",
|
||||
"no_users_found": "Nenhum usuário encontrado.",
|
||||
"no_user_found_in_search": "Nenhum usuário encontrado com a consulta de pesquisa fornecida.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Esta coleção está a ser compartilhada publicamente.",
|
||||
"search_for_links": "Pesquisar por Links",
|
||||
"search_query_invalid_symbol": "A consulta de pesquisa não deve conter '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Abrir este modal em uma nova aba",
|
||||
"file": "Arquivo",
|
||||
"tag_preservation_rule_label": "Regras de reserva por tag (sobrescrever configurações globais):",
|
||||
"ai_tagging": "Marcação por AI",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Regenerar todas as conservações danificadas ou faltando para todos os usuários.",
|
||||
"delete_all_preservations": "Excluir preservação para todas as páginas da Web, aplica-se a todos os usuários.",
|
||||
"delete_all_preservations_and_regenerate": "Excluir e re-preservar todas as páginas da Web, aplica-se a todos os usuários.",
|
||||
"delete_all_preservation_warning": "Esta ação irá apagar todas as conservações existentes no servidor.",
|
||||
"no_broken_preservations": "Nenhuma conservação quebrada.",
|
||||
"links_are_being_represerved": "Links estão sendo preservados",
|
||||
"cancel": "Cancelar",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expandir barra lateral",
|
||||
"shrink_sidebar": "Encolher barra lateral",
|
||||
"trial_left_plural": "O período de teste termina em {{count}} dias. Inscreva-se.",
|
||||
"trial_left_singular": "O período de teste termina em 1 dia. Inscreva-se."
|
||||
"trial_left_singular": "O período de teste termina em 1 dia. Inscreva-se.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Administrare Utilizatori",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Caută utilizatori",
|
||||
"no_users_found": "Nu s-a găsit niciun utilizator.",
|
||||
"no_user_found_in_search": "Niciun utilizator găsit care să îndeplinească cerințele menționate.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Această colecție este partajată public.",
|
||||
"search_for_links": "Caută linkuri",
|
||||
"search_query_invalid_symbol": "Căutarea nu trebuie să conțină '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Deschide această fereastră într-o filă nouă",
|
||||
"file": "Fișier",
|
||||
"tag_preservation_rule_label": "Reguli de prezervare după etichetă (suprascrie setările globale):",
|
||||
"ai_tagging": "Etichetare AI",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Regenerează toate prezervările stricate sau lipsite pentru toți utilizatorii.",
|
||||
"delete_all_preservations": "Ștergeți prezervarea pentru toate paginile web, se aplică tuturor utilizatorilor.",
|
||||
"delete_all_preservations_and_regenerate": "Ștergeți și re-prezervați toate paginile web, se aplică tuturor utilizatorilor.",
|
||||
"delete_all_preservation_warning": "Această acțiune va șterge toate prezervările existente de pe server.",
|
||||
"no_broken_preservations": "Nu există prezervări stricate.",
|
||||
"links_are_being_represerved": "Linkurile sunt în curs de re-prezervare...",
|
||||
"cancel": "Anulează",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Управление пользователями",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Поиск пользователей",
|
||||
"no_users_found": "Пользователи не найдены.",
|
||||
"no_user_found_in_search": "По данному запросу пользователей не нашлось.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Это общая (доступная другим пользователям) коллекция.",
|
||||
"search_for_links": "Найти ссылки",
|
||||
"search_query_invalid_symbol": "Поисковый запрос не должен содержать '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Открыть это модальное окно в новой вкладке",
|
||||
"file": "Файл",
|
||||
"tag_preservation_rule_label": "Правила сохранения по тегу (переопределяет глобальные настройки):",
|
||||
"ai_tagging": "ИИ-метки",
|
||||
"worker": "Обслуживание",
|
||||
"regenerate_broken_preservations": "Пересоздать все поврежденные или отсутствующие сохранения для всех пользователей.",
|
||||
"delete_all_preservations": "Удалить сохранения для всех веб-страниц, действие применится ко всем пользователям.",
|
||||
"delete_all_preservations_and_regenerate": "Удалить и сохранить заново все веб-страницы, действие применится ко всем пользователям.",
|
||||
"delete_all_preservation_warning": "Это действие удалит все существующие сохранения на сервере.",
|
||||
"no_broken_preservations": "Нет поврежденных сохранений.",
|
||||
"links_are_being_represerved": "Ссылки сохраняются заново...",
|
||||
"cancel": "Отменить",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Kullanıcı Yönetimi",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Kullanıcı Ara",
|
||||
"no_users_found": "Kullanıcı bulunamadı.",
|
||||
"no_user_found_in_search": "Verilen arama sorgusuyla kullanıcı bulunamadı.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Bu koleksiyon herkese paylaşılacak.",
|
||||
"search_for_links": "Linkleri Arama",
|
||||
"search_query_invalid_symbol": "Aramada '%' işareti olamaz.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Bu sekmenin yeni pencereden açın",
|
||||
"file": "Dosya",
|
||||
"tag_preservation_rule_label": "Etiket bazında koruma kuralları (genel ayarları geçersiz kıl):",
|
||||
"ai_tagging": "Yapay Zeka Etiketleri",
|
||||
"worker": "Çalışan",
|
||||
"regenerate_broken_preservations": "Tüm kullanıcılar için bozuk veya eksik kayıtları yeniden oluşturun.",
|
||||
"delete_all_preservations": "Kullanıcıların kaydettiği tüm web sayfalarını silin.",
|
||||
"delete_all_preservations_and_regenerate": "Tüm web sayfalarını silip tekrar kaydedin, bu tüm kullanıcılar için geçerli olacaktır.",
|
||||
"delete_all_preservation_warning": "Bu işlem sunucudaki mevcut tüm kayıtları silecektir.",
|
||||
"no_broken_preservations": "Bozuk kayıt dosyası bulunamadı.",
|
||||
"links_are_being_represerved": "Bağlantıyı yeniden kaydediyor...",
|
||||
"cancel": "İptal",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Управління користувачами",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "Пошук користувачів",
|
||||
"no_users_found": "Користувачів не знайдено.",
|
||||
"no_user_found_in_search": "За вказаним пошуковим запитом не знайдено користувачів.",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "Цю колекцію поширено публічно.",
|
||||
"search_for_links": "Шукати посилання",
|
||||
"search_query_invalid_symbol": "Пошуковий запит не може містити '%'.",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "Відкрити спливне вікно у новій вкладці",
|
||||
"file": "Файл",
|
||||
"tag_preservation_rule_label": "Правила збереження за міткою (пріоритетніше загальних налаштувань):",
|
||||
"ai_tagging": "Мітки ШІ",
|
||||
"worker": "Працівник",
|
||||
"regenerate_broken_preservations": "Повторно створити усі пошкоджені та відсутні збереження для усіх користувачів.",
|
||||
"delete_all_preservations": "Видалити збереження усіх вебсторінок, застосовується до усіх користувачів.",
|
||||
"delete_all_preservations_and_regenerate": "Видалити та зберегти усі вебсторінки заново, застосовується до усіх користувачів.",
|
||||
"delete_all_preservation_warning": "Ця дія видалить усі наявні збереження на сервері.",
|
||||
"no_broken_preservations": "Немає пошкоджених збережень.",
|
||||
"links_are_being_represerved": "Посилання перезберігаються...",
|
||||
"cancel": "Скасувати",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Розгорнути бічну панель",
|
||||
"shrink_sidebar": "Згорнути бічну панель",
|
||||
"trial_left_plural": "Лишилося {{count}} днів пробного доступу. Підпишіться.",
|
||||
"trial_left_singular": "Лишився 1 день пробного доступу. Підпишіться."
|
||||
"trial_left_singular": "Лишився 1 день пробного доступу. Підпишіться.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Назад на основну панель",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "使用者管理",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "搜尋使用者",
|
||||
"no_users_found": "找不到使用者。",
|
||||
"no_user_found_in_search": "在指定的搜尋條件下找不到使用者。",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "此收藏集已公開分享。",
|
||||
"search_for_links": "搜尋連結",
|
||||
"search_query_invalid_symbol": "搜尋查詢不應包含「%」符號。",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "在新分頁中開啟此對話框",
|
||||
"file": "檔案",
|
||||
"tag_preservation_rule_label": "依標籤設定保存規則(將覆寫全域設定):",
|
||||
"ai_tagging": "AI 標記",
|
||||
"worker": "工作程序",
|
||||
"regenerate_broken_preservations": "為所有使用者重新產生所有損壞或遺失的保存內容。",
|
||||
"delete_all_preservations": "刪除所有使用者的網頁保存內容。",
|
||||
"delete_all_preservations_and_regenerate": "刪除並重新保存所有使用者的網頁內容。",
|
||||
"delete_all_preservation_warning": "此動作將刪除伺服器上所有現有的保存內容。",
|
||||
"no_broken_preservations": "目前沒有損壞的保存內容。",
|
||||
"links_are_being_represerved": "連結正在重新保存中...",
|
||||
"cancel": "取消",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe.",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "用户管理",
|
||||
"user_administration": "User Administration",
|
||||
"search_users": "搜索用户",
|
||||
"no_users_found": "未找到用户。",
|
||||
"no_user_found_in_search": "在给定的搜索查询中未找到用户。",
|
||||
@@ -463,15 +463,23 @@
|
||||
"collection_publicly_shared": "此收藏集当前为公开分享状态。",
|
||||
"search_for_links": "搜索链接",
|
||||
"search_query_invalid_symbol": "搜索关键字不应该包含 '%'。",
|
||||
"search_operators": "Search Operators",
|
||||
"search_operator_name": "Title",
|
||||
"search_operator_url": "URL",
|
||||
"search_operator_tag": "Tag",
|
||||
"search_operator_collection": "Collection",
|
||||
"search_operator_before": "Before",
|
||||
"search_operator_after": "After",
|
||||
"search_operator_public": "Public",
|
||||
"search_operator_description": "Description",
|
||||
"search_operator_type": "Type",
|
||||
"search_operator_pinned": "Pinned",
|
||||
"search_operator_exclude": "Exclude",
|
||||
"open_modal_new_tab": "在新标签页中打开",
|
||||
"file": "文件",
|
||||
"tag_preservation_rule_label": "基于标签的网页存档规则 (覆盖全局设置):",
|
||||
"ai_tagging": "AI 标签",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "为所有用户重新生成损坏或缺失的网页存档。",
|
||||
"delete_all_preservations": "删除所有的网页存档,此操作将影响所有用户。",
|
||||
"delete_all_preservations_and_regenerate": "删除所有用户的网页存档,并重新获取存档。",
|
||||
"delete_all_preservation_warning": "此操作将删除服务器上的所有已存档内容。",
|
||||
"no_broken_preservations": "未发现损坏的存档。",
|
||||
"links_are_being_represerved": "链接正在被重新保存……",
|
||||
"cancel": "取消",
|
||||
@@ -525,5 +533,22 @@
|
||||
"expand_sidebar": "展开侧边栏",
|
||||
"shrink_sidebar": "收缩侧边栏",
|
||||
"trial_left_plural": "试用期将于 {{count}} 天后结束,请订阅。",
|
||||
"trial_left_singular": "试用期将于 1 天后结束,请订阅。"
|
||||
"trial_left_singular": "试用期将于 1 天后结束,请订阅。",
|
||||
"apply_members_roles_to_subcollections": "Apply members and roles to subcollections",
|
||||
"apply_members_roles_to_subcollections_desc": "This will apply the members and their roles to all existing subcollections.",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"background_jobs": "Background Jobs",
|
||||
"background_jobs_desc": "Background jobs run 24/7 behind the scenes to process all queued links and tasks.",
|
||||
"link_preservation_job": "Link Preservation Job",
|
||||
"link_preservation_job_desc": "Crawls, preserves, and extracts text from your links.",
|
||||
"search_indexing_job": "Search Indexing Job",
|
||||
"search_indexing_job_desc": "Prepares links and documents for full-text search.",
|
||||
"done": "Done",
|
||||
"worker_pending_indexing_desc": "Pending items first need to be preserved before they can be indexed if they're a link.",
|
||||
"regenerate_broken_links": "Regenerate Broken Links",
|
||||
"regenerate_all_links": "Regenerate All Links",
|
||||
"regenerate_broken_preserved_content_desc": "<0>Delete and re-preserve broken links with at least one missing preserved format.</0><0>This action will immediately delete any existing preserved format for {{count}} links.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"delete_all_preserved_content_and_regenerate_desc": "<0>This action will immediately delete all your existing preserved formats.</0><0>Affected links will be scheduled to have their preserved formats rebuilt.</0>",
|
||||
"note": "Note",
|
||||
"search_unindexed_links_in_bg_info": "Search may be less accurate because some links are still being processed."
|
||||
}
|
||||
|
||||
@@ -50,17 +50,14 @@ export default async function archiveHandler(
|
||||
process.env.PERPLEXITY_API_KEY)
|
||||
? true
|
||||
: undefined,
|
||||
indexVersion: null,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
abortController.abort();
|
||||
reject(
|
||||
new Error(
|
||||
@@ -211,10 +208,6 @@ export default async function archiveHandler(
|
||||
console.log("Reason:", err);
|
||||
throw err;
|
||||
} finally {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
const finalLink = await prisma.link.findUnique({
|
||||
where: { id: link.id },
|
||||
});
|
||||
@@ -234,7 +227,6 @@ export default async function archiveHandler(
|
||||
!finalLink.aiTagged
|
||||
? true
|
||||
: undefined,
|
||||
indexVersion: null,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -25,10 +25,17 @@ export default async function handleMonolith(
|
||||
|
||||
const child = spawn("monolith", args, {
|
||||
stdio: ["pipe", "pipe", "inherit"],
|
||||
signal,
|
||||
killSignal: "SIGKILL",
|
||||
detached: true,
|
||||
});
|
||||
|
||||
const abortListener = () => {
|
||||
try {
|
||||
if (child.pid) killProcess(child.pid);
|
||||
} catch {}
|
||||
reject(new Error("Monolith aborted"));
|
||||
};
|
||||
signal?.addEventListener("abort", abortListener);
|
||||
|
||||
child.stdin.write(htmlFromPage);
|
||||
child.stdin.end();
|
||||
|
||||
@@ -36,11 +43,14 @@ export default async function handleMonolith(
|
||||
child.stdout.on("data", (c) => chunks.push(c));
|
||||
|
||||
child.on("error", (err) => {
|
||||
cleanup();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
child.on("close", async (code) => {
|
||||
if (code !== 0 && code !== null) {
|
||||
cleanup();
|
||||
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Monolith exited with code ${code}`));
|
||||
}
|
||||
|
||||
@@ -54,21 +64,29 @@ export default async function handleMonolith(
|
||||
return reject(new Error("Monolith output exceeded buffer limit"));
|
||||
}
|
||||
|
||||
try {
|
||||
await createFile({
|
||||
data: html,
|
||||
filePath: `archives/${link.collectionId}/${link.id}.html`,
|
||||
});
|
||||
await createFile({
|
||||
data: html,
|
||||
filePath: `archives/${link.collectionId}/${link.id}.html`,
|
||||
});
|
||||
|
||||
await prisma.link.update({
|
||||
where: { id: link.id },
|
||||
data: { monolith: `archives/${link.collectionId}/${link.id}.html` },
|
||||
});
|
||||
await prisma.link.update({
|
||||
where: { id: link.id },
|
||||
data: { monolith: `archives/${link.collectionId}/${link.id}.html` },
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
function cleanup() {
|
||||
signal?.removeEventListener("abort", abortListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const killProcess = async (PID: number) => {
|
||||
if (process.platform === "win32") {
|
||||
process.kill(PID, "SIGKILL");
|
||||
} else {
|
||||
process.kill(-PID, "SIGKILL");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,6 +80,14 @@ export async function startIndexing(interval = 10) {
|
||||
{ indexVersion: null },
|
||||
],
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ lastPreserved: { not: null } },
|
||||
{
|
||||
url: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...(process.env.STRIPE_SECRET_KEY
|
||||
? {
|
||||
@@ -164,6 +172,7 @@ export async function startIndexing(interval = 10) {
|
||||
{ indexVersion: { not: MEILI_INDEX_VERSION } },
|
||||
{ indexVersion: null },
|
||||
],
|
||||
lastPreserved: { not: null },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@ import {
|
||||
MobileAuth,
|
||||
} from "@linkwarden/types/global";
|
||||
import { useSession } from "next-auth/react";
|
||||
import type toaster from "react-hot-toast";
|
||||
import { TFunction } from "next-i18next";
|
||||
import type { Alert as Alert_ } from "react-native";
|
||||
|
||||
const useCollections = (auth?: MobileAuth) => {
|
||||
let status: "loading" | "authenticated" | "unauthenticated";
|
||||
@@ -112,17 +109,7 @@ const useUpdateCollection = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const useDeleteCollection = ({
|
||||
auth,
|
||||
Alert,
|
||||
toast,
|
||||
t,
|
||||
}: {
|
||||
auth?: MobileAuth;
|
||||
Alert?: typeof Alert_;
|
||||
toast?: typeof toaster;
|
||||
t?: TFunction;
|
||||
}) => {
|
||||
const useDeleteCollection = (auth?: MobileAuth) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
@@ -146,44 +133,6 @@ const useDeleteCollection = ({
|
||||
|
||||
return data.response;
|
||||
},
|
||||
onMutate: async (id) => {
|
||||
await queryClient.cancelQueries({ queryKey: ["collections"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["dashboardData"] });
|
||||
|
||||
const previousCollections = queryClient.getQueryData(["collections"]);
|
||||
const previousDashboard = queryClient.getQueryData(["dashboardData"]);
|
||||
|
||||
queryClient.setQueryData(["collections"], (oldData: any) => {
|
||||
return oldData?.filter((collection: any) => collection.id !== id);
|
||||
});
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], (oldData: any) => {
|
||||
if (!oldData) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
links:
|
||||
oldData.links?.filter((link: any) => link.collectionId !== id) ||
|
||||
[],
|
||||
numberOfPinnedLinks:
|
||||
oldData.links?.filter(
|
||||
(link: any) => link.collectionId !== id && link.isPinned
|
||||
).length || 0,
|
||||
};
|
||||
});
|
||||
|
||||
return { previousCollections, previousDashboard };
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
if (toast && t) toast.error(t(error.message));
|
||||
else if (Alert)
|
||||
Alert.alert("Error", "There was an error deleting the collection.");
|
||||
|
||||
if (!context) return;
|
||||
|
||||
queryClient.setQueryData(["collections"], context.previousCollections);
|
||||
queryClient.setQueryData(["dashboardData"], context.previousDashboard);
|
||||
},
|
||||
onSuccess: (data, id) => {
|
||||
queryClient.setQueryData(["collections"], (oldData: any) => {
|
||||
return oldData.filter((collection: any) => collection.id !== id);
|
||||
@@ -204,9 +153,6 @@ const useDeleteCollection = ({
|
||||
).length || 0,
|
||||
};
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["tags"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["links"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
useQueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
QueryKey,
|
||||
} from "@tanstack/react-query";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
@@ -19,9 +18,6 @@ import {
|
||||
} from "@linkwarden/lib/schemaValidation";
|
||||
import getFormatFromContentType from "@linkwarden/lib/getFormatFromContentType";
|
||||
import getLinkTypeFromFormat from "@linkwarden/lib/getLinkTypeFromFormat";
|
||||
import type toaster from "react-hot-toast";
|
||||
import { TFunction } from "next-i18next";
|
||||
import type { Alert as Alert_ } from "react-native";
|
||||
|
||||
const useLinks = (params: LinkRequestQuery = {}, auth?: MobileAuth) => {
|
||||
const sort =
|
||||
@@ -115,254 +111,7 @@ const buildQueryString = (params: LinkRequestQuery) => {
|
||||
.join("&");
|
||||
};
|
||||
|
||||
const upsertLinkInList = (
|
||||
links: LinkIncludingShortenedCollectionAndTags[] = [],
|
||||
link: LinkIncludingShortenedCollectionAndTags,
|
||||
optimisticId?: number
|
||||
) => {
|
||||
const existingIndex = links.findIndex(
|
||||
(item) => item.id === optimisticId || item.id === link.id
|
||||
);
|
||||
|
||||
if (existingIndex === -1) return [link, ...links];
|
||||
|
||||
const nextLinks = [...links];
|
||||
nextLinks[existingIndex] = link;
|
||||
return nextLinks;
|
||||
};
|
||||
|
||||
const upsertLinkInInfiniteData = (
|
||||
oldData: any,
|
||||
link: LinkIncludingShortenedCollectionAndTags,
|
||||
optimisticId?: number
|
||||
) => {
|
||||
if (!oldData?.pages?.length) return oldData;
|
||||
|
||||
let replaced = false;
|
||||
const pages = oldData.pages.map((page: any) => {
|
||||
const links = page?.links?.map((item: any) => {
|
||||
if (item.id === optimisticId || item.id === link.id) {
|
||||
replaced = true;
|
||||
return link;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
return { ...page, links };
|
||||
});
|
||||
|
||||
if (!replaced) {
|
||||
const firstPage = pages[0];
|
||||
pages[0] = {
|
||||
...firstPage,
|
||||
links: upsertLinkInList(firstPage?.links ?? [], link, optimisticId),
|
||||
};
|
||||
}
|
||||
|
||||
return { ...oldData, pages };
|
||||
};
|
||||
|
||||
const upsertLinkInDashboardData = (
|
||||
oldData: any,
|
||||
link: LinkIncludingShortenedCollectionAndTags,
|
||||
optimisticId?: number
|
||||
) => {
|
||||
if (!oldData) return oldData;
|
||||
|
||||
const updatedLinks = upsertLinkInList(
|
||||
oldData.links ?? [],
|
||||
link,
|
||||
optimisticId
|
||||
).slice(0, 16);
|
||||
|
||||
const collectionLinks = { ...(oldData.collectionLinks ?? {}) };
|
||||
const collectionId = link.collection?.id;
|
||||
|
||||
if (collectionId != null && collectionLinks[collectionId]) {
|
||||
collectionLinks[collectionId] = upsertLinkInList(
|
||||
collectionLinks[collectionId],
|
||||
link,
|
||||
optimisticId
|
||||
).slice(0, 16);
|
||||
}
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
links: updatedLinks,
|
||||
collectionLinks,
|
||||
};
|
||||
};
|
||||
|
||||
const removeLinkFromInfiniteData = (oldData: any, linkId: number) => {
|
||||
if (!oldData?.pages?.length) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
pages: oldData.pages.map((page: any) => ({
|
||||
...page,
|
||||
links: (page.links ?? []).filter((item: any) => item.id !== linkId),
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
const removeLinkFromDashboardData = (oldData: any, linkId: number) => {
|
||||
if (!oldData) return oldData;
|
||||
|
||||
const removedLink = (oldData.links ?? []).find(
|
||||
(item: any) => item.id === linkId
|
||||
);
|
||||
const numberOfPinnedLinks = removedLink?.pinnedBy?.length
|
||||
? Math.max(0, (oldData.numberOfPinnedLinks ?? 0) - 1)
|
||||
: oldData.numberOfPinnedLinks;
|
||||
|
||||
const hasCollectionLinks = oldData.collectionLinks != null;
|
||||
const collectionLinks = hasCollectionLinks
|
||||
? { ...oldData.collectionLinks }
|
||||
: oldData.collectionLinks;
|
||||
if (hasCollectionLinks) {
|
||||
for (const [collectionId, links] of Object.entries(collectionLinks)) {
|
||||
collectionLinks[Number(collectionId)] = (links as any[]).filter(
|
||||
(item: any) => item.id !== linkId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
links: (oldData.links ?? []).filter((item: any) => item.id !== linkId),
|
||||
collectionLinks,
|
||||
numberOfPinnedLinks,
|
||||
};
|
||||
};
|
||||
|
||||
const isLinkPinned = (link?: LinkIncludingShortenedCollectionAndTags) => {
|
||||
return Boolean(link?.pinnedBy && link.pinnedBy.length > 0);
|
||||
};
|
||||
|
||||
const findLinkInInfiniteData = (data: any, linkId: number) => {
|
||||
if (!data?.pages?.length) return undefined;
|
||||
for (const page of data.pages) {
|
||||
const match = page?.links?.find((item: any) => item.id === linkId);
|
||||
if (match) return match;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const findLinkInQueriesData = (
|
||||
queries: [QueryKey, unknown][],
|
||||
linkId: number
|
||||
) => {
|
||||
for (const [, data] of queries ?? []) {
|
||||
const match = findLinkInInfiniteData(data as any, linkId);
|
||||
if (match) return match;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const findLinkInDashboardData = (data: any, linkId: number) => {
|
||||
const match = data?.links?.find((item: any) => item.id === linkId);
|
||||
if (match) return match;
|
||||
|
||||
const collectionLinks = data?.collectionLinks;
|
||||
if (!collectionLinks) return undefined;
|
||||
|
||||
for (const links of Object.values(collectionLinks)) {
|
||||
const collectionMatch = (links as any[])?.find(
|
||||
(item: any) => item.id === linkId
|
||||
);
|
||||
if (collectionMatch) return collectionMatch;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const replaceLinkInInfiniteData = (
|
||||
oldData: any,
|
||||
link: LinkIncludingShortenedCollectionAndTags
|
||||
) => {
|
||||
if (!oldData?.pages?.length) return oldData;
|
||||
|
||||
let updated = false;
|
||||
const pages = oldData.pages.map((page: any) => {
|
||||
const links = (page.links ?? []).map((item: any) => {
|
||||
if (item.id === link.id) {
|
||||
updated = true;
|
||||
return link;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return { ...page, links };
|
||||
});
|
||||
|
||||
if (!updated) return oldData;
|
||||
|
||||
return { ...oldData, pages };
|
||||
};
|
||||
|
||||
const replaceLinkInDashboardData = (
|
||||
oldData: any,
|
||||
link: LinkIncludingShortenedCollectionAndTags
|
||||
) => {
|
||||
if (!oldData) return oldData;
|
||||
|
||||
let updated = false;
|
||||
const links = (oldData.links ?? []).map((item: any) => {
|
||||
if (item.id === link.id) {
|
||||
updated = true;
|
||||
return link;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
let collectionLinks = oldData.collectionLinks;
|
||||
if (oldData.collectionLinks != null) {
|
||||
const nextCollectionLinks = { ...oldData.collectionLinks };
|
||||
for (const [collectionId, linksForCollection] of Object.entries(
|
||||
nextCollectionLinks
|
||||
)) {
|
||||
const linkList = linksForCollection as any[];
|
||||
if (!Array.isArray(linkList)) continue;
|
||||
if (!linkList.some((item) => item.id === link.id)) continue;
|
||||
updated = true;
|
||||
nextCollectionLinks[Number(collectionId)] = linkList.map((item) =>
|
||||
item.id === link.id ? link : item
|
||||
);
|
||||
}
|
||||
collectionLinks = nextCollectionLinks;
|
||||
}
|
||||
|
||||
if (!updated) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
links,
|
||||
collectionLinks,
|
||||
};
|
||||
};
|
||||
|
||||
const applyPinnedDelta = (oldData: any, delta: number) => {
|
||||
if (!oldData || !delta) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
numberOfPinnedLinks: Math.max(
|
||||
0,
|
||||
(oldData.numberOfPinnedLinks ?? 0) + delta
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const useAddLink = ({
|
||||
auth,
|
||||
Alert,
|
||||
toast,
|
||||
t,
|
||||
}: {
|
||||
auth?: MobileAuth;
|
||||
Alert?: typeof Alert_;
|
||||
toast?: typeof toaster;
|
||||
t?: TFunction;
|
||||
}) => {
|
||||
const useAddLink = (auth?: MobileAuth) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
@@ -395,118 +144,20 @@ const useAddLink = ({
|
||||
|
||||
return data.response;
|
||||
},
|
||||
onMutate: async (link) => {
|
||||
await queryClient.cancelQueries({ queryKey: ["links"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["dashboardData"] });
|
||||
|
||||
const previousLinks = queryClient.getQueriesData({
|
||||
queryKey: ["links"],
|
||||
onSuccess: (data: LinkIncludingShortenedCollectionAndTags[]) => {
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
||||
if (!oldData) return undefined;
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
links: [data, ...oldData?.pages?.[0]?.links],
|
||||
nextCursor: oldData?.pages?.[0]?.nextCursor,
|
||||
},
|
||||
...oldData?.pages?.slice(1),
|
||||
],
|
||||
pageParams: oldData?.pageParams,
|
||||
};
|
||||
});
|
||||
const previousDashboard = queryClient.getQueryData(["dashboardData"]);
|
||||
|
||||
const collections =
|
||||
(queryClient.getQueryData(["collections"]) as any[]) ?? [];
|
||||
const tags = (queryClient.getQueryData(["tags"]) as any[]) ?? [];
|
||||
const user = queryClient.getQueryData(["user"]) as any;
|
||||
|
||||
const collectionFromId =
|
||||
link.collection?.id != null
|
||||
? collections.find(
|
||||
(collection) => collection.id === link.collection?.id
|
||||
)
|
||||
: undefined;
|
||||
const collectionFromName =
|
||||
!collectionFromId && link.collection?.name
|
||||
? collections.find(
|
||||
(collection) => collection.name === link.collection?.name
|
||||
)
|
||||
: undefined;
|
||||
const resolvedCollection = collectionFromId ?? collectionFromName;
|
||||
|
||||
const tempId = -Date.now();
|
||||
const tempCollectionId = tempId - 1;
|
||||
const collectionId =
|
||||
resolvedCollection?.id ?? link.collection?.id ?? tempCollectionId;
|
||||
const collectionName =
|
||||
resolvedCollection?.name ?? link.collection?.name ?? "Unorganized";
|
||||
|
||||
const resolvedTags =
|
||||
link.tags?.map((tag, index) => {
|
||||
if (tag.id != null) {
|
||||
return (
|
||||
tags.find((existing) => existing.id === tag.id) ?? {
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const existingTag = tags.find(
|
||||
(existing) => existing.name === tag.name
|
||||
);
|
||||
return (
|
||||
existingTag ?? {
|
||||
id: tempId - 2 - index,
|
||||
name: tag.name,
|
||||
}
|
||||
);
|
||||
}) ?? [];
|
||||
|
||||
const optimisticLink = {
|
||||
id: tempId,
|
||||
name: link.name?.trim() || link.url || "",
|
||||
url: link.url || "",
|
||||
description: link.description || "",
|
||||
type: link.type || "url",
|
||||
preview: "",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
collectionId,
|
||||
collection:
|
||||
resolvedCollection ??
|
||||
({
|
||||
id: collectionId,
|
||||
name: collectionName,
|
||||
ownerId: user?.id ?? 0,
|
||||
} as any),
|
||||
tags: resolvedTags,
|
||||
} as LinkIncludingShortenedCollectionAndTags;
|
||||
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) =>
|
||||
upsertLinkInInfiniteData(oldData, optimisticLink, tempId)
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], (oldData: any) =>
|
||||
upsertLinkInDashboardData(oldData, optimisticLink, tempId)
|
||||
);
|
||||
|
||||
return {
|
||||
previousLinks,
|
||||
previousDashboard,
|
||||
optimisticId: tempId,
|
||||
};
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
if (toast && t) toast.error(t(error.message));
|
||||
else if (Alert)
|
||||
Alert.alert("Error", "There was an error adding the link.");
|
||||
|
||||
if (!context) return;
|
||||
|
||||
context.previousLinks?.forEach(([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
});
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], context.previousDashboard);
|
||||
},
|
||||
onSuccess: (
|
||||
data: LinkIncludingShortenedCollectionAndTags,
|
||||
_link,
|
||||
context
|
||||
) => {
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) =>
|
||||
upsertLinkInInfiniteData(oldData, data, context?.optimisticId)
|
||||
);
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["dashboardData"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["collections"] });
|
||||
@@ -516,17 +167,7 @@ const useAddLink = ({
|
||||
});
|
||||
};
|
||||
|
||||
const useUpdateLink = ({
|
||||
auth,
|
||||
Alert,
|
||||
toast,
|
||||
t,
|
||||
}: {
|
||||
auth?: MobileAuth;
|
||||
Alert?: typeof Alert_;
|
||||
toast?: typeof toaster;
|
||||
t?: TFunction;
|
||||
}) => {
|
||||
const useUpdateLink = (auth?: MobileAuth) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
@@ -551,117 +192,6 @@ const useUpdateLink = ({
|
||||
|
||||
return data.response;
|
||||
},
|
||||
onMutate: async (link) => {
|
||||
const linkId = link.id;
|
||||
if (linkId == null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
await queryClient.cancelQueries({ queryKey: ["links"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["publicLinks"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["dashboardData"] });
|
||||
|
||||
const previousLinks = queryClient.getQueriesData({
|
||||
queryKey: ["links"],
|
||||
});
|
||||
const previousPublicLinks = queryClient.getQueriesData({
|
||||
queryKey: ["publicLinks"],
|
||||
});
|
||||
const previousDashboard = queryClient.getQueryData(["dashboardData"]);
|
||||
const previousLinkQueries = queryClient.getQueriesData({
|
||||
queryKey: ["link", linkId],
|
||||
});
|
||||
|
||||
const cachedLink =
|
||||
findLinkInQueriesData(previousLinks, linkId) ??
|
||||
findLinkInDashboardData(previousDashboard, linkId);
|
||||
const collections =
|
||||
(queryClient.getQueryData(["collections"]) as any[]) ?? [];
|
||||
const nextCollectionId =
|
||||
link.collection?.id ?? cachedLink?.collection?.id;
|
||||
const resolvedCollection =
|
||||
nextCollectionId != null
|
||||
? collections.find((collection) => collection.id === nextCollectionId)
|
||||
: undefined;
|
||||
const optimisticCollection =
|
||||
resolvedCollection ??
|
||||
(link.collection?.id &&
|
||||
cachedLink?.collection?.id === link.collection.id
|
||||
? { ...cachedLink.collection, ...link.collection }
|
||||
: link.collection ?? cachedLink?.collection);
|
||||
|
||||
const previousPinned = isLinkPinned(cachedLink);
|
||||
const nextPinned =
|
||||
typeof link.pinnedBy === "undefined"
|
||||
? previousPinned
|
||||
: isLinkPinned(link);
|
||||
const pinnedDelta =
|
||||
nextPinned === previousPinned ? 0 : nextPinned ? 1 : -1;
|
||||
|
||||
const optimisticLink = {
|
||||
...(cachedLink ?? {}),
|
||||
...link,
|
||||
collection: optimisticCollection,
|
||||
collectionId:
|
||||
optimisticCollection?.id ??
|
||||
link.collection?.id ??
|
||||
cachedLink?.collectionId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as LinkIncludingShortenedCollectionAndTags;
|
||||
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) =>
|
||||
replaceLinkInInfiniteData(oldData, optimisticLink)
|
||||
);
|
||||
|
||||
queryClient.setQueriesData(
|
||||
{ queryKey: ["publicLinks"] },
|
||||
(oldData: any) => replaceLinkInInfiniteData(oldData, optimisticLink)
|
||||
);
|
||||
|
||||
queryClient.setQueriesData(
|
||||
{ queryKey: ["link", linkId] },
|
||||
() => optimisticLink
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], (oldData: any) =>
|
||||
applyPinnedDelta(
|
||||
replaceLinkInDashboardData(oldData, optimisticLink),
|
||||
pinnedDelta
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
previousLinks,
|
||||
previousPublicLinks,
|
||||
previousDashboard,
|
||||
previousLinkQueries,
|
||||
};
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
if (toast && t) toast.error(t(error.message));
|
||||
else if (Alert)
|
||||
Alert.alert("Error", "There was an error updating the link.");
|
||||
|
||||
if (!context) return;
|
||||
|
||||
context.previousLinks?.forEach(([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
});
|
||||
|
||||
context.previousPublicLinks?.forEach(
|
||||
([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
}
|
||||
);
|
||||
|
||||
context.previousLinkQueries?.forEach(
|
||||
([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
}
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], context.previousDashboard);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["links"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["link", data.id] });
|
||||
@@ -673,17 +203,7 @@ const useUpdateLink = ({
|
||||
});
|
||||
};
|
||||
|
||||
const useDeleteLink = ({
|
||||
auth,
|
||||
Alert,
|
||||
toast,
|
||||
t,
|
||||
}: {
|
||||
auth?: MobileAuth;
|
||||
Alert?: typeof Alert_;
|
||||
toast?: typeof toaster;
|
||||
t?: TFunction;
|
||||
}) => {
|
||||
const useDeleteLink = (auth?: MobileAuth) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
@@ -706,57 +226,6 @@ const useDeleteLink = ({
|
||||
|
||||
return data.response;
|
||||
},
|
||||
onMutate: async (id) => {
|
||||
await queryClient.cancelQueries({ queryKey: ["links"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["dashboardData"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["publicLinks"] });
|
||||
|
||||
const previousLinks = queryClient.getQueriesData({
|
||||
queryKey: ["links"],
|
||||
});
|
||||
const previousPublicLinks = queryClient.getQueriesData({
|
||||
queryKey: ["publicLinks"],
|
||||
});
|
||||
const previousDashboard = queryClient.getQueryData(["dashboardData"]);
|
||||
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) =>
|
||||
removeLinkFromInfiniteData(oldData, id)
|
||||
);
|
||||
|
||||
queryClient.setQueriesData(
|
||||
{ queryKey: ["publicLinks"] },
|
||||
(oldData: any) => removeLinkFromInfiniteData(oldData, id)
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], (oldData: any) =>
|
||||
removeLinkFromDashboardData(oldData, id)
|
||||
);
|
||||
|
||||
return {
|
||||
previousLinks,
|
||||
previousPublicLinks,
|
||||
previousDashboard,
|
||||
};
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
if (toast && t) toast.error(t(error.message));
|
||||
else if (Alert)
|
||||
Alert.alert("Error", "There was an error deleting the link.");
|
||||
|
||||
if (!context) return;
|
||||
|
||||
context.previousLinks?.forEach(([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
});
|
||||
|
||||
context.previousPublicLinks?.forEach(
|
||||
([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
}
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], context.previousDashboard);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
||||
if (!oldData?.pages?.[0]) return undefined;
|
||||
|
||||
Reference in New Issue
Block a user