import { z } from "zod"; import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; export const zExportBookmarkSchema = z.object({ createdAt: z.number(), title: z.string().nullable(), tags: z.array(z.string()), content: z .discriminatedUnion("type", [ z.object({ type: z.literal(BookmarkTypes.LINK), url: z.string(), }), z.object({ type: z.literal(BookmarkTypes.TEXT), text: z.string(), }), ]) .nullable(), note: z.string().nullable(), archived: z.boolean().optional().default(false), }); export const zExportSchema = z.object({ bookmarks: z.array(zExportBookmarkSchema), }); export function toExportFormat( bookmark: ZBookmark, ): z.infer { let content = null; switch (bookmark.content.type) { case BookmarkTypes.LINK: { content = { type: bookmark.content.type, url: bookmark.content.url, }; break; } case BookmarkTypes.TEXT: { content = { type: bookmark.content.type, text: bookmark.content.text, }; break; } // Exclude asset types for now } return { createdAt: Math.floor(bookmark.createdAt.getTime() / 1000), title: bookmark.title ?? (bookmark.content.type === BookmarkTypes.LINK ? (bookmark.content.title ?? null) : null), tags: bookmark.tags.map((t) => t.name), content, note: bookmark.note ?? null, archived: bookmark.archived, }; } export function toNetscapeFormat(bookmarks: ZBookmark[]): string { const header = ` Bookmarks

Bookmarks

`; const footer = `

`; const bookmarkEntries = bookmarks .map((bookmark) => { if (bookmark.content?.type !== BookmarkTypes.LINK) { return ""; } const addDate = bookmark.createdAt ? `ADD_DATE="${Math.floor(bookmark.createdAt.getTime() / 1000)}"` : ""; const tagNames = bookmark.tags.map((t) => t.name).join(","); const tags = tagNames.length > 0 ? `TAGS="${tagNames}"` : ""; const encodedUrl = encodeURI(bookmark.content.url); const displayTitle = bookmark.title ?? bookmark.content.url; const encodedTitle = escapeHtml(displayTitle); return `

${encodedTitle}`; }) .filter(Boolean) .join("\n"); return `${header}\n${bookmarkEntries}\n${footer}`; } function escapeHtml(input: string): string { const escapeMap: Record = { "&": "&", "'": "'", "`": "`", '"': """, "<": "<", ">": ">", }; return input.replace(/[&'`"<>]/g, (match) => escapeMap[match] || ""); }