mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): persist chat drafts across section navigation (#381)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,31 +8,46 @@ export type DraftState = {
|
||||
selectionEnd: number;
|
||||
};
|
||||
|
||||
export function useDrafts() {
|
||||
const draftsRef = React.useRef(new Map<string, DraftState>());
|
||||
const sharedDrafts = new Map<string, DraftState>();
|
||||
|
||||
export function useDrafts() {
|
||||
const saveDraft = React.useCallback(
|
||||
(channelId: string, draft: DraftState) => {
|
||||
if (draft.content.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
const drafts = draftsRef.current;
|
||||
drafts.set(channelId, draft);
|
||||
trimMapToSize(drafts, 50);
|
||||
sharedDrafts.set(channelId, draft);
|
||||
trimMapToSize(sharedDrafts, 50);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const loadDraft = React.useCallback(
|
||||
(channelId: string): DraftState | undefined => {
|
||||
return draftsRef.current.get(channelId);
|
||||
return sharedDrafts.get(channelId);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const clearDraft = React.useCallback((channelId: string) => {
|
||||
draftsRef.current.delete(channelId);
|
||||
sharedDrafts.delete(channelId);
|
||||
}, []);
|
||||
|
||||
return { saveDraft, loadDraft, clearDraft };
|
||||
/** Save draft if content is non-empty, otherwise clear it. */
|
||||
const persistDraft = React.useCallback(
|
||||
(channelId: string, content: string) => {
|
||||
if (content.trim().length > 0) {
|
||||
saveDraft(channelId, {
|
||||
content,
|
||||
selectionEnd: content.length,
|
||||
selectionStart: content.length,
|
||||
});
|
||||
} else {
|
||||
clearDraft(channelId);
|
||||
}
|
||||
},
|
||||
[saveDraft, clearDraft],
|
||||
);
|
||||
|
||||
return { saveDraft, loadDraft, clearDraft, persistDraft };
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ type MessageComposerProps = {
|
||||
channelId?: string | null;
|
||||
channelName: string;
|
||||
disabled?: boolean;
|
||||
draftKey?: string;
|
||||
editTarget?: {
|
||||
author: string;
|
||||
body: string;
|
||||
@@ -58,6 +59,7 @@ export function MessageComposer({
|
||||
channelId = null,
|
||||
channelName,
|
||||
disabled = false,
|
||||
draftKey,
|
||||
editTarget = null,
|
||||
isSending = false,
|
||||
onCancelEdit,
|
||||
@@ -83,7 +85,8 @@ export function MessageComposer({
|
||||
}, []);
|
||||
|
||||
const drafts = useDrafts();
|
||||
const previousChannelIdRef = React.useRef<string | null>(null);
|
||||
const effectiveDraftKey = draftKey ?? channelId;
|
||||
const previousDraftKeyRef = React.useRef<string | null>(null);
|
||||
|
||||
const mentions = useMentions(channelId);
|
||||
const channelLinks = useChannelLinks();
|
||||
@@ -151,24 +154,17 @@ export function MessageComposer({
|
||||
});
|
||||
|
||||
// ── Channel switching: save/restore drafts ──────────────────────────
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: channelId is the sole trigger
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: effectiveDraftKey is the sole trigger
|
||||
React.useEffect(() => {
|
||||
const prevId = previousChannelIdRef.current;
|
||||
if (prevId) {
|
||||
const currentContent = contentRef.current;
|
||||
if (currentContent.trim().length > 0) {
|
||||
drafts.saveDraft(prevId, {
|
||||
content: currentContent,
|
||||
selectionEnd: currentContent.length,
|
||||
selectionStart: currentContent.length,
|
||||
});
|
||||
} else {
|
||||
drafts.clearDraft(prevId);
|
||||
}
|
||||
const prevKey = previousDraftKeyRef.current;
|
||||
if (prevKey) {
|
||||
drafts.persistDraft(prevKey, contentRef.current);
|
||||
}
|
||||
previousChannelIdRef.current = channelId;
|
||||
previousDraftKeyRef.current = effectiveDraftKey;
|
||||
|
||||
const saved = channelId ? drafts.loadDraft(channelId) : undefined;
|
||||
const saved = effectiveDraftKey
|
||||
? drafts.loadDraft(effectiveDraftKey)
|
||||
: undefined;
|
||||
if (saved) {
|
||||
setContent(saved.content);
|
||||
contentRef.current = saved.content;
|
||||
@@ -184,7 +180,13 @@ export function MessageComposer({
|
||||
setIsEmojiPickerOpen(false);
|
||||
mentions.clearMentions();
|
||||
channelLinks.clearChannels();
|
||||
}, [channelId]);
|
||||
|
||||
return () => {
|
||||
if (effectiveDraftKey) {
|
||||
drafts.persistDraft(effectiveDraftKey, contentRef.current);
|
||||
}
|
||||
};
|
||||
}, [effectiveDraftKey]);
|
||||
|
||||
// ── Edit mode: pre-fill content ─────────────────────────────────────
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: editTarget?.id is the trigger
|
||||
|
||||
@@ -258,6 +258,7 @@ export function MessageThreadPanel({
|
||||
channelId={channelId}
|
||||
channelName={channelName}
|
||||
disabled={disabled || isSending || !channelId}
|
||||
draftKey={`thread:${threadHead.id}`}
|
||||
isSending={isSending}
|
||||
onCancelReply={composerReplyTarget ? onCancelReply : undefined}
|
||||
onSend={onSend}
|
||||
|
||||
Reference in New Issue
Block a user