aboutsummaryrefslogtreecommitdiffstats
path: root/packages/shared/import-export/parsers.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-11-15 16:47:20 +0000
committerGitHub <noreply@github.com>2025-11-15 16:47:20 +0000
commit0c80f515ec9e20c70b69380031886ccc0e4bc06d (patch)
treea5f1c9a3c063761671ede8935d9913e8f51c3577 /packages/shared/import-export/parsers.ts
parent4350666961132ffe68afea8686b567f361a58f57 (diff)
downloadkarakeep-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.ts67
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);
}