import { Highlight, Category, FavoritePaper } from '../types'; const KEYS = { highlights: 'arxiv_explorer_highlights', categories: 'arxiv_explorer_categories', favorites: 'arxiv_explorer_favorites', cachedSections: 'arxiv_explorer_sections_cache', }; function loadJson(key: string, fallback: T): T { try { const data = localStorage.getItem(key); return data ? JSON.parse(data) : fallback; } catch { return fallback; } } function saveJson(key: string, data: T) { try { localStorage.setItem(key, JSON.stringify(data)); } catch (e) { console.warn('Failed to save to localStorage:', e); } } // ==================== Highlights ==================== export function getHighlights(): Highlight[] { return loadJson(KEYS.highlights, []); } export function addHighlight(highlight: Omit): Highlight { const highlights = getHighlights(); const newHighlight: Highlight = { ...highlight, id: `hl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now(), }; highlights.unshift(newHighlight); saveJson(KEYS.highlights, highlights); return newHighlight; } export function removeHighlight(id: string) { const highlights = getHighlights().filter((h) => h.id !== id); saveJson(KEYS.highlights, highlights); } // ==================== Categories ==================== const defaultCategories: Category[] = [ { id: 'uncategorized', name: '未分类 / Uncategorized', color: '#6b7280' }, ]; export function getCategories(): Category[] { return loadJson(KEYS.categories, defaultCategories); } export function addCategory(name: string, color: string): Category { const categories = getCategories(); const newCat: Category = { id: `cat_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`, name, color, }; categories.push(newCat); saveJson(KEYS.categories, categories); return newCat; } export function removeCategory(id: string) { if (id === 'uncategorized') return; const categories = getCategories().filter((c) => c.id !== id); saveJson(KEYS.categories, categories); // Move papers in deleted category to uncategorized const favorites = getFavorites(); favorites.forEach((f) => { if (f.categoryId === id) f.categoryId = 'uncategorized'; }); saveJson(KEYS.favorites, favorites); } export function updateCategory(id: string, name: string, color: string) { const categories = getCategories().map((c) => c.id === id ? { ...c, name, color } : c ); saveJson(KEYS.categories, categories); } // ==================== Favorites ==================== export function getFavorites(): FavoritePaper[] { return loadJson(KEYS.favorites, []); } export function addFavorite( paperId: string, paperTitle: string, paperAuthors: string[], categoryId: string = 'uncategorized' ): FavoritePaper { const favorites = getFavorites(); const existing = favorites.find((f) => f.paperId === paperId); if (existing) return existing; const fav: FavoritePaper = { paperId, paperTitle, paperAuthors, categoryId, timestamp: Date.now(), }; favorites.unshift(fav); saveJson(KEYS.favorites, favorites); return fav; } export function removeFavorite(paperId: string) { const favorites = getFavorites().filter((f) => f.paperId !== paperId); saveJson(KEYS.favorites, favorites); } export function moveFavoriteToCategory(paperId: string, categoryId: string) { const favorites = getFavorites().map((f) => f.paperId === paperId ? { ...f, categoryId } : f ); saveJson(KEYS.favorites, favorites); } export function isFavorited(paperId: string): boolean { return getFavorites().some((f) => f.paperId === paperId); } // ==================== Section Cache ==================== interface CachedSections { [arxivId: string]: { introduction?: string; relatedWork?: string; methods?: string; references?: Array<{ key: string; number: string; text: string; arxivId?: string }>; timestamp: number; }; } export function getCachedSections(arxivId: string) { const cache = loadJson(KEYS.cachedSections, {}); const entry = cache[arxivId]; if (entry && Date.now() - entry.timestamp < 7 * 24 * 60 * 60 * 1000) { return entry; } return null; } export function setCachedSections( arxivId: string, data: { introduction?: string; relatedWork?: string; methods?: string; references?: Array<{ key: string; number: string; text: string; arxivId?: string }>; } ) { const cache = loadJson(KEYS.cachedSections, {}); // Limit cache size const keys = Object.keys(cache); if (keys.length > 100) { const sorted = keys.sort((a, b) => (cache[a].timestamp || 0) - (cache[b].timestamp || 0)); sorted.slice(0, 20).forEach((k) => delete cache[k]); } cache[arxivId] = { ...data, timestamp: Date.now() }; saveJson(KEYS.cachedSections, cache); }