diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-11-15 16:47:20 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-15 16:47:20 +0000 |
| commit | 0c80f515ec9e20c70b69380031886ccc0e4bc06d (patch) | |
| tree | a5f1c9a3c063761671ede8935d9913e8f51c3577 /packages/shared/import-export/parsers.ts | |
| parent | 4350666961132ffe68afea8686b567f361a58f57 (diff) | |
| download | karakeep-0c80f515ec9e20c70b69380031886ccc0e4bc06d.tar.zst | |
feat: import from mymind (#2138)
* feat: add mymind importer support
This commit adds support for importing bookmarks from mymind CSV exports.
Changes:
- Added mymind to ImportSource type in parsers.ts
- Implemented parseMymindBookmarkFile() to parse mymind CSV format
- Added mymind case to parseImportFile() switch statement
- Added mymind import card to ImportExport UI component
- Added English translation for mymind import description
- Added comprehensive test for mymind CSV parsing
The mymind CSV format includes:
- WebPages (URLs with optional notes)
- Notes (text content without URLs)
- Tags (comma-separated)
- Created timestamps (ISO format)
Fixes #654
* format
* use zod for parsing
---------
Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to '')
| -rw-r--r-- | packages/shared/import-export/parsers.ts | 67 |
1 files changed, 66 insertions, 1 deletions
diff --git a/packages/shared/import-export/parsers.ts b/packages/shared/import-export/parsers.ts index c969c615..f4d3f862 100644 --- a/packages/shared/import-export/parsers.ts +++ b/packages/shared/import-export/parsers.ts @@ -13,7 +13,8 @@ export type ImportSource = | "omnivore" | "karakeep" | "linkwarden" - | "tab-session-manager"; + | "tab-session-manager" + | "mymind"; export interface ParsedBookmark { title: string; @@ -230,6 +231,67 @@ function parseTabSessionManagerStateFile( ); } +function parseMymindBookmarkFile(textContent: string): ParsedBookmark[] { + const zMymindRecordSchema = z.object({ + id: z.string(), + type: z.string(), + title: z.string(), + url: z.string(), + content: z.string(), + note: z.string(), + tags: z.string(), + created: z.string(), + }); + + const zMymindExportSchema = z.array(zMymindRecordSchema); + + const records = parse(textContent, { + columns: true, + skip_empty_lines: true, + }); + + const parsed = zMymindExportSchema.safeParse(records); + if (!parsed.success) { + throw new Error( + `The uploaded CSV file contains an invalid mymind bookmark file: ${parsed.error.toString()}`, + ); + } + + return parsed.data.map((record) => { + // Determine content type based on presence of URL and content fields + let content: ParsedBookmark["content"]; + if (record.url && record.url.trim().length > 0) { + content = { type: BookmarkTypes.LINK as const, url: record.url.trim() }; + } else if (record.content && record.content.trim().length > 0) { + content = { + type: BookmarkTypes.TEXT as const, + text: record.content.trim(), + }; + } + + // Parse tags from comma-separated string + const tags = + record.tags && record.tags.trim().length > 0 + ? record.tags.split(",").map((tag) => tag.trim()) + : []; + + // Parse created date to timestamp (in seconds) + const addDate = record.created + ? new Date(record.created).getTime() / 1000 + : undefined; + + return { + title: record.title || "", + content, + tags, + addDate, + notes: + record.note && record.note.trim().length > 0 ? record.note : undefined, + paths: [], // mymind doesn't have folder structure + }; + }); +} + function deduplicateBookmarks(bookmarks: ParsedBookmark[]): ParsedBookmark[] { const deduplicatedBookmarksMap = new Map<string, ParsedBookmark>(); const textBookmarks: ParsedBookmark[] = []; @@ -295,6 +357,9 @@ export function parseImportFile( case "tab-session-manager": result = parseTabSessionManagerStateFile(textContent); break; + case "mymind": + result = parseMymindBookmarkFile(textContent); + break; } return deduplicateBookmarks(result); } |
