From 098e56a8950efbef79e551e12622ae7c8cd90c03 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sat, 8 Nov 2025 18:52:30 +0000 Subject: feat(extension): Allow writing notes directly in the extension (#2104) * feat(extension): add notes editor to bookmark hoarded screen Adds the ability to directly add and edit notes for bookmarks in the browser extension's hoarded screen (the page shown after saving a bookmark). Changes: - Created Textarea UI component for the browser extension - Created NoteEditor component that uses useUpdateBookmark hook - Added Notes section to BookmarkSavedPage, displayed between the header and tags - Notes auto-save when the user clicks away from the textarea (onBlur) - Shows saving state and error messages to the user This brings feature parity with the web app's notes functionality. * add explicit button * more fixes --------- Co-authored-by: Claude --- apps/browser-extension/src/BookmarkSavedPage.tsx | 4 + .../src/components/NoteEditor.tsx | 105 +++++++++++++++++++++ .../src/components/ui/textarea.tsx | 23 +++++ 3 files changed, 132 insertions(+) create mode 100644 apps/browser-extension/src/components/NoteEditor.tsx create mode 100644 apps/browser-extension/src/components/ui/textarea.tsx diff --git a/apps/browser-extension/src/BookmarkSavedPage.tsx b/apps/browser-extension/src/BookmarkSavedPage.tsx index 67e6f753..3be5f9d0 100644 --- a/apps/browser-extension/src/BookmarkSavedPage.tsx +++ b/apps/browser-extension/src/BookmarkSavedPage.tsx @@ -6,6 +6,7 @@ import { useDeleteBookmark } from "@karakeep/shared-react/hooks/bookmarks"; import BookmarkLists from "./components/BookmarkLists"; import { ListsSelector } from "./components/ListsSelector"; +import { NoteEditor } from "./components/NoteEditor"; import TagList from "./components/TagList"; import { TagsSelector } from "./components/TagsSelector"; import { Button, buttonVariants } from "./components/ui/button"; @@ -79,6 +80,9 @@ export default function BookmarkSavedPage() {
+

Notes

+ +

Tags

diff --git a/apps/browser-extension/src/components/NoteEditor.tsx b/apps/browser-extension/src/components/NoteEditor.tsx new file mode 100644 index 00000000..15f1515b --- /dev/null +++ b/apps/browser-extension/src/components/NoteEditor.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from "react"; +import { Check, Save } from "lucide-react"; + +import { + useAutoRefreshingBookmarkQuery, + useUpdateBookmark, +} from "@karakeep/shared-react/hooks/bookmarks"; + +import { Button } from "./ui/button"; +import { Textarea } from "./ui/textarea"; + +export function NoteEditor({ bookmarkId }: { bookmarkId: string }) { + const { data: bookmark } = useAutoRefreshingBookmarkQuery({ bookmarkId }); + const [error, setError] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [noteValue, setNoteValue] = useState(""); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + // Update local state when bookmark changes, but only if there are no unsaved changes + // This prevents overwriting user's edits while they're typing + useEffect(() => { + if (bookmark && !hasUnsavedChanges) { + setNoteValue(bookmark.note ?? ""); + } + }, [bookmark?.note, bookmark, hasUnsavedChanges]); + + const updateBookmarkMutator = useUpdateBookmark({ + onSuccess: () => { + setError(null); + setIsSaving(false); + setHasUnsavedChanges(false); + }, + onError: (e) => { + setError(e.message || "Failed to save note"); + setIsSaving(false); + }, + }); + + const handleSave = () => { + if (!bookmark || noteValue === bookmark.note || isSaving) { + return; + } + setIsSaving(true); + setError(null); + updateBookmarkMutator.mutate({ + bookmarkId: bookmark.id, + note: noteValue, + }); + }; + + if (!bookmark) { + return null; + } + + return ( +
+