feat(07-03): implement localStorage utilities for lastReadAt
- Add saveLastReadAt: saves timestamp with merge support - Add loadLastReadAt: loads all timestamps with parse error handling - Add clearLastReadAt: clears specific channel or all channels - All functions handle localStorage errors gracefully (quota, parse errors) - Storage key: 'jk_chat_lastReadAt' - All 8 tests passing (GREEN phase) Part of Phase 7 Plan 3 (WebSocket Integration & Selectors) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f2bd7c6501
commit
33deac659b
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* localStorage utilities for chat lastReadAt persistence
|
||||
*
|
||||
* Stores lastReadAt timestamps for each chat channel to track unread status
|
||||
* across browser sessions. Data is stored as JSON object keyed by channel ID.
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = 'jk_chat_lastReadAt';
|
||||
|
||||
/**
|
||||
* Save lastReadAt timestamp for a channel
|
||||
*
|
||||
* Merges with existing data to preserve timestamps for other channels.
|
||||
* Handles localStorage quota errors gracefully.
|
||||
*
|
||||
* @param {string} channel - Channel ID (session ID, lesson ID, or 'global')
|
||||
* @param {string} timestamp - ISO timestamp (e.g., '2026-01-26T12:00:00Z')
|
||||
*/
|
||||
export const saveLastReadAt = (channel, timestamp) => {
|
||||
try {
|
||||
const existing = loadLastReadAt();
|
||||
existing[channel] = timestamp;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(existing));
|
||||
} catch (error) {
|
||||
console.error('Failed to save lastReadAt:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load all lastReadAt timestamps from localStorage
|
||||
*
|
||||
* Returns empty object if no data exists or if JSON parsing fails.
|
||||
*
|
||||
* @returns {Object} Map of channel IDs to ISO timestamp strings
|
||||
*/
|
||||
export const loadLastReadAt = () => {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (!stored) return {};
|
||||
return JSON.parse(stored);
|
||||
} catch (error) {
|
||||
console.error('Failed to load lastReadAt:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear lastReadAt timestamp for a channel, or all channels
|
||||
*
|
||||
* If channel is specified, removes only that channel's timestamp.
|
||||
* If no channel is specified, removes entire storage key.
|
||||
*
|
||||
* @param {string} [channel] - Channel ID to clear, or undefined to clear all
|
||||
*/
|
||||
export const clearLastReadAt = (channel) => {
|
||||
try {
|
||||
if (channel) {
|
||||
const existing = loadLastReadAt();
|
||||
delete existing[channel];
|
||||
if (Object.keys(existing).length === 0) {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
} else {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(existing));
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to clear lastReadAt:', error);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue