diff options
| author | Jorge Barnaby <jorge.barnaby@gmail.com> | 2025-04-16 11:41:03 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-16 16:41:03 +0100 |
| commit | 5e0f4de1625957c6ce057ae272aa301fc459d31b (patch) | |
| tree | 184c642573bc95b356b31b5ad3515edc97bb65f0 | |
| parent | 2328dc3444b8bcae9a134bc859858521d4c31f19 (diff) | |
| download | karakeep-5e0f4de1625957c6ce057ae272aa301fc459d31b.tar.zst | |
feat: Add import support for Tab Session Manager (#1246)
* feat: Add import support for Tab Session Manager
* drop unneeded schema fields
---------
Co-authored-by: Mohamed Bassem <me@mbassem.com>
| -rw-r--r-- | apps/web/components/settings/ImportExport.tsx | 30 | ||||
| -rw-r--r-- | apps/web/lib/i18n/locales/en/translation.json | 1 | ||||
| -rw-r--r-- | apps/web/lib/i18n/locales/es/translation.json | 3 | ||||
| -rw-r--r-- | apps/web/lib/importBookmarkParser.ts | 40 |
4 files changed, 72 insertions, 2 deletions
diff --git a/apps/web/components/settings/ImportExport.tsx b/apps/web/components/settings/ImportExport.tsx index d6086c97..ab4fb640 100644 --- a/apps/web/components/settings/ImportExport.tsx +++ b/apps/web/components/settings/ImportExport.tsx @@ -16,6 +16,7 @@ import { parseNetscapeBookmarkFile, parseOmnivoreBookmarkFile, parsePocketBookmarkFile, + parseTabSessionManagerStateFile, } from "@/lib/importBookmarkParser"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; @@ -161,7 +162,13 @@ export function ImportExportRow() { source, }: { file: File; - source: "html" | "pocket" | "omnivore" | "hoarder" | "linkwarden"; + source: + | "html" + | "pocket" + | "omnivore" + | "hoarder" + | "linkwarden" + | "tab-session-manager"; }) => { if (source === "html") { return await parseNetscapeBookmarkFile(file); @@ -173,6 +180,8 @@ export function ImportExportRow() { return await parseOmnivoreBookmarkFile(file); } else if (source === "linkwarden") { return await parseLinkwardenBookmarkFile(file); + } else if (source === "tab-session-manager") { + return await parseTabSessionManagerStateFile(file); } else { throw new Error("Unknown source"); } @@ -346,6 +355,25 @@ export function ImportExportRow() { </FilePickerButton> </ImportCard> <ImportCard + text="Tab Session Manager" + description={t( + "settings.import.import_bookmarks_from_tab_session_manager_export", + )} + > + <FilePickerButton + size={"sm"} + loading={false} + accept=".json" + multiple={false} + className="flex items-center gap-2" + onFileSelect={(file) => + runUploadBookmarkFile({ file, source: "tab-session-manager" }) + } + > + <p>Import</p> + </FilePickerButton> + </ImportCard> + <ImportCard text="Hoarder" description={t( "settings.import.import_bookmarks_from_hoarder_export", diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index 3398045e..4e622df5 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -142,6 +142,7 @@ "import_bookmarks_from_omnivore_export": "Import Bookmarks from Omnivore export", "import_bookmarks_from_linkwarden_export": "Import Bookmarks from Linkwarden export", "import_bookmarks_from_hoarder_export": "Import Bookmarks from Hoarder export", + "import_bookmarks_from_tab_session_manager_export": "Import Bookmarks from Tab Session Manager", "export_links_and_notes": "Export Links and Notes", "imported_bookmarks": "Imported Bookmarks" }, diff --git a/apps/web/lib/i18n/locales/es/translation.json b/apps/web/lib/i18n/locales/es/translation.json index 9ee40bd2..89af3229 100644 --- a/apps/web/lib/i18n/locales/es/translation.json +++ b/apps/web/lib/i18n/locales/es/translation.json @@ -73,7 +73,8 @@ "import_bookmarks_from_hoarder_export": "Importar marcadores desde exportación de Hoarder", "import_bookmarks_from_html_file": "Importar marcadores desde archivo HTML", "import_bookmarks_from_omnivore_export": "Importar marcadores desde exportación de Omnivore", - "import_bookmarks_from_linkwarden_export": "Importar marcadores desde Linkwarden" + "import_bookmarks_from_linkwarden_export": "Importar marcadores desde Linkwarden", + "import_bookmarks_from_tab_session_manager_export": "Importar marcadores desde Tab Session Manager" }, "api_keys": { "api_keys": "Claves APIs", diff --git a/apps/web/lib/importBookmarkParser.ts b/apps/web/lib/importBookmarkParser.ts index bea92da1..25415975 100644 --- a/apps/web/lib/importBookmarkParser.ts +++ b/apps/web/lib/importBookmarkParser.ts @@ -177,6 +177,46 @@ export async function parseLinkwardenBookmarkFile( }); } +export async function parseTabSessionManagerStateFile( + file: File, +): Promise<ParsedBookmark[]> { + const textContent = await file.text(); + + const zTab = z.object({ + url: z.string(), + title: z.string(), + lastAccessed: z.number(), + }); + + const zSession = z.object({ + windows: z.record(z.string(), z.record(z.string(), zTab)), + date: z.number(), + }); + + const zTabSessionManagerSchema = z.array(zSession); + + const parsed = zTabSessionManagerSchema.safeParse(JSON.parse(textContent)); + if (!parsed.success) { + throw new Error( + `The uploaded JSON file contains an invalid Tab Session Manager bookmark file: ${parsed.error.toString()}`, + ); + } + + // Get the object in data that has the most recent `date` + const { windows } = parsed.data.reduce((prev, curr) => + prev.date > curr.date ? prev : curr, + ); + + return Object.values(windows).flatMap((window) => + Object.values(window).map((tab) => ({ + title: tab.title, + content: { type: BookmarkTypes.LINK as const, url: tab.url }, + tags: [], + addDate: tab.lastAccessed, + })), + ); +} + export function deduplicateBookmarks( bookmarks: ParsedBookmark[], ): ParsedBookmark[] { |
