1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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<string | null>(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 (
<div className="flex flex-col gap-2">
<Textarea
className="h-32 w-full overflow-auto rounded bg-background p-2 text-sm text-gray-400 dark:text-gray-300"
value={noteValue}
placeholder="Write some notes ..."
onChange={(e) => {
setNoteValue(e.currentTarget.value);
setHasUnsavedChanges(e.currentTarget.value !== bookmark.note);
}}
/>
<div className="flex items-center justify-between gap-2">
<div className="flex-1">
{isSaving && <p className="text-xs text-gray-500">Saving note...</p>}
{error && <p className="text-xs text-red-500">{error}</p>}
{!isSaving && !error && hasUnsavedChanges && (
<p className="text-xs text-amber-600 dark:text-amber-500">
Unsaved changes
</p>
)}
</div>
{hasUnsavedChanges && (
<Button
onClick={handleSave}
disabled={isSaving}
size="sm"
className="gap-1.5"
>
{isSaving ? (
<>
<Save className="h-3.5 w-3.5 animate-pulse" />
Saving...
</>
) : (
<>
<Save className="h-3.5 w-3.5" />
Save Note
</>
)}
</Button>
)}
{!hasUnsavedChanges && !isSaving && noteValue && (
<div className="flex items-center gap-1 text-xs text-green-600 dark:text-green-500">
<Check className="h-3.5 w-3.5" />
Saved
</div>
)}
</div>
</div>
);
}
|