1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
"use client";
import { useState } from "react";
import { toast } from "@/components/ui/sonner";
import { useTranslation } from "@/lib/i18n/client";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useCreateBookmarkList } from "@karakeep/shared-react/hooks/lists";
import { useTRPC } from "@karakeep/shared-react/trpc";
import {
importBookmarksFromFile,
ImportSource,
parseImportFile,
} from "@karakeep/shared/import-export";
import { useCreateImportSession } from "./useImportSessions";
export interface ImportProgress {
done: number;
total: number;
}
export function useBookmarkImport() {
const { t } = useTranslation();
const api = useTRPC();
const [importProgress, setImportProgress] = useState<ImportProgress | null>(
null,
);
const [quotaError, setQuotaError] = useState<string | null>(null);
const queryClient = useQueryClient();
const { mutateAsync: createImportSession } = useCreateImportSession();
const { mutateAsync: createList } = useCreateBookmarkList();
const { mutateAsync: stageImportedBookmarks } = useMutation(
api.importSessions.stageImportedBookmarks.mutationOptions(),
);
const { mutateAsync: finalizeImportStaging } = useMutation(
api.importSessions.finalizeImportStaging.mutationOptions(),
);
const uploadBookmarkFileMutation = useMutation({
mutationFn: async ({
file,
source,
}: {
file: File;
source: ImportSource;
}) => {
// Clear any previous quota error
setQuotaError(null);
// First, parse the file to count bookmarks
const textContent = await file.text();
const parsedBookmarks = parseImportFile(source, textContent);
const bookmarkCount = parsedBookmarks.length;
// Check quota before proceeding
if (bookmarkCount > 0) {
const quotaUsage = await queryClient.fetchQuery(
api.subscriptions.getQuotaUsage.queryOptions(),
);
if (
!quotaUsage.bookmarks.unlimited &&
quotaUsage.bookmarks.quota !== null
) {
const remaining =
quotaUsage.bookmarks.quota - quotaUsage.bookmarks.used;
if (remaining < bookmarkCount) {
const errorMsg = `Cannot import ${bookmarkCount} bookmarks. You have ${remaining} bookmark${remaining === 1 ? "" : "s"} remaining in your quota of ${quotaUsage.bookmarks.quota}.`;
setQuotaError(errorMsg);
throw new Error(errorMsg);
}
}
}
// Proceed with import if quota check passes
const result = await importBookmarksFromFile(
{
file,
source,
rootListName: t("settings.import.imported_bookmarks"),
deps: {
createImportSession,
createList,
stageImportedBookmarks,
finalizeImportStaging: async (sessionId: string) => {
await finalizeImportStaging({ importSessionId: sessionId });
},
},
onProgress: (done, total) => setImportProgress({ done, total }),
},
{
// Use a custom parser to avoid re-parsing the file
parsers: {
[source]: () => parsedBookmarks,
},
},
);
return result;
},
onSuccess: async (result) => {
setImportProgress(null);
if (result.counts.total === 0) {
toast({ description: "No bookmarks found in the file." });
return;
}
toast({
description: `Staged ${result.counts.total} bookmarks for import. Background processing will start automatically.`,
variant: "default",
});
},
onError: (error) => {
setImportProgress(null);
toast({
description: error.message,
variant: "destructive",
});
},
});
return {
importProgress,
quotaError,
clearQuotaError: () => setQuotaError(null),
runUploadBookmarkFile: uploadBookmarkFileMutation.mutateAsync,
isImporting: uploadBookmarkFileMutation.isPending,
};
}
|