aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/lib
diff options
context:
space:
mode:
authorYuiki Saito <yuikisaito@ysaito.win>2025-05-12 00:11:07 +0900
committerGitHub <noreply@github.com>2025-05-11 16:11:07 +0100
commitc03dcfdbbc5a99abdb7517a03482bccf875d1953 (patch)
tree8e38cb4ed1bcfc170350bcd0c5f1be1d7f38196f /apps/web/lib
parent8b05515b565e2b63500dc93c57855b555b2880df (diff)
downloadkarakeep-c03dcfdbbc5a99abdb7517a03482bccf875d1953.tar.zst
feat: Add NETSCAPE-Bookmark-file-1 export format support (#1374)
* Add function to export bookmarks in NETSCAPE-Bookmark-file-1 format * Update export endpoint to support NETSCAPE format * Add format selection to export UI * include tags in the export --------- Co-authored-by: Mohamed Bassem <me@mbassem.com>
Diffstat (limited to 'apps/web/lib')
-rw-r--r--apps/web/lib/exportBookmarks.ts49
1 files changed, 49 insertions, 0 deletions
diff --git a/apps/web/lib/exportBookmarks.ts b/apps/web/lib/exportBookmarks.ts
index 45db104f..67b0b5da 100644
--- a/apps/web/lib/exportBookmarks.ts
+++ b/apps/web/lib/exportBookmarks.ts
@@ -58,3 +58,52 @@ export function toExportFormat(
note: bookmark.note ?? null,
};
}
+
+export function toNetscapeFormat(bookmarks: ZBookmark[]): string {
+ const header = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
+<!-- This is an automatically generated file.
+ It will be read and overwritten.
+ DO NOT EDIT! -->
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
+<TITLE>Bookmarks</TITLE>
+<H1>Bookmarks</H1>
+<DL><p>`;
+
+ const footer = `</DL><p>`;
+
+ 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 ` <DT><A HREF="${encodedUrl}" ${addDate} ${tags}>${encodedTitle}</A>`;
+ })
+ .filter(Boolean)
+ .join("\n");
+
+ return `${header}\n${bookmarkEntries}\n${footer}`;
+}
+
+function escapeHtml(input: string): string {
+ const escapeMap: Record<string, string> = {
+ "&": "&amp;",
+ "'": "&#x27;",
+ "`": "&#x60;",
+ '"': "&quot;",
+ "<": "&lt;",
+ ">": "&gt;",
+ };
+
+ return input.replace(/[&'`"<>]/g, (match) => escapeMap[match] || "");
+}