127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
import { useState, useCallback, useEffect, useRef } from 'react';
|
||
|
||
const DRAFT_STORAGE_KEY = 'room-drafts';
|
||
const AUTO_SAVE_INTERVAL = 3000; // 3 seconds
|
||
|
||
interface DraftData {
|
||
content: string;
|
||
savedAt: string;
|
||
}
|
||
|
||
interface UseRoomDraftReturn {
|
||
draft: string;
|
||
setDraft: (content: string) => void;
|
||
clearDraft: () => void;
|
||
lastSavedAt: string | null;
|
||
}
|
||
|
||
export function useRoomDraft(roomId: string | null): UseRoomDraftReturn {
|
||
const [draft, setDraftState] = useState('');
|
||
const [lastSavedAt, setLastSavedAt] = useState<string | null>(null);
|
||
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||
const draftRef = useRef(draft);
|
||
const roomIdRef = useRef(roomId);
|
||
draftRef.current = draft;
|
||
roomIdRef.current = roomId;
|
||
|
||
// Load draft from localStorage on mount
|
||
useEffect(() => {
|
||
if (!roomId) {
|
||
setDraftState('');
|
||
setLastSavedAt(null);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const stored = localStorage.getItem(DRAFT_STORAGE_KEY);
|
||
if (stored) {
|
||
const drafts: Record<string, DraftData> = JSON.parse(stored);
|
||
const roomDraft = drafts[roomId];
|
||
if (roomDraft) {
|
||
setDraftState(roomDraft.content);
|
||
setLastSavedAt(roomDraft.savedAt);
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to load draft:', err);
|
||
}
|
||
}, [roomId]);
|
||
|
||
// Auto-save draft to localStorage
|
||
const saveDraft = useCallback((content: string, targetRoomId: string | null) => {
|
||
if (!targetRoomId) return;
|
||
|
||
try {
|
||
const stored = localStorage.getItem(DRAFT_STORAGE_KEY);
|
||
const drafts: Record<string, DraftData> = stored ? JSON.parse(stored) : {};
|
||
|
||
drafts[targetRoomId] = {
|
||
content,
|
||
savedAt: new Date().toISOString(),
|
||
};
|
||
|
||
localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(drafts));
|
||
setLastSavedAt(new Date().toISOString());
|
||
} catch (err) {
|
||
console.error('Failed to save draft:', err);
|
||
}
|
||
}, []);
|
||
|
||
// Debounced auto-save
|
||
const setDraft = useCallback((content: string) => {
|
||
setDraftState(content);
|
||
|
||
// Clear existing timeout
|
||
if (saveTimeoutRef.current) {
|
||
clearTimeout(saveTimeoutRef.current);
|
||
}
|
||
|
||
saveTimeoutRef.current = setTimeout(() => {
|
||
// 使用最新的 roomId(通过 ref)
|
||
saveDraft(content, roomIdRef.current);
|
||
}, AUTO_SAVE_INTERVAL);
|
||
}, [saveDraft]);
|
||
|
||
// Clear draft
|
||
const clearDraft = useCallback(() => {
|
||
if (!roomId) return;
|
||
|
||
try {
|
||
const stored = localStorage.getItem(DRAFT_STORAGE_KEY);
|
||
if (stored) {
|
||
const drafts: Record<string, DraftData> = JSON.parse(stored);
|
||
delete drafts[roomId];
|
||
localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(drafts));
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to clear draft:', err);
|
||
}
|
||
|
||
setDraftState('');
|
||
setLastSavedAt(null);
|
||
}, [roomId]);
|
||
|
||
// Cleanup on unmount or roomId change
|
||
useEffect(() => {
|
||
return () => {
|
||
if (saveTimeoutRef.current) {
|
||
clearTimeout(saveTimeoutRef.current);
|
||
}
|
||
// Save immediately on unmount/roomId change if there's content
|
||
// 使用 ref 中的最新值而不是闭包中的值
|
||
const currentDraft = draftRef.current;
|
||
const currentRoomId = roomIdRef.current;
|
||
if (currentDraft && currentRoomId) {
|
||
saveDraft(currentDraft, currentRoomId);
|
||
}
|
||
};
|
||
}, [roomId, saveDraft]);
|
||
|
||
return {
|
||
draft,
|
||
setDraft,
|
||
clearDraft,
|
||
lastSavedAt,
|
||
};
|
||
}
|