cedarstalking with keyboard shortcuts
1import { LocalStorage } from "@raycast/api";
2import { DirectoryPerson } from "./api";
3
4const CACHE_KEY = "directory_cache";
5
6interface CacheStore {
7 [id: string]: DirectoryPerson;
8}
9
10let memoryCache: CacheStore | null = null;
11
12export async function loadCache(): Promise<CacheStore> {
13 if (memoryCache) return memoryCache;
14 const raw = await LocalStorage.getItem<string>(CACHE_KEY);
15 memoryCache = raw ? (JSON.parse(raw) as CacheStore) : {};
16 return memoryCache;
17}
18
19export async function mergePeopleIntoCache(
20 people: DirectoryPerson[],
21): Promise<void> {
22 const cache = await loadCache();
23 for (const p of people) {
24 cache[p.Id] = p;
25 }
26 memoryCache = cache;
27 await LocalStorage.setItem(CACHE_KEY, JSON.stringify(cache));
28}
29
30export async function searchCache(query: string): Promise<DirectoryPerson[]> {
31 const cache = await loadCache();
32 const all = Object.values(cache);
33 if (!query.trim()) return all;
34
35 const terms = query.toLowerCase().split(/\s+/);
36 const scored = all.map((p) => ({ p, score: scoreMatch(p, terms) }));
37 return scored
38 .filter((x) => x.score > 0)
39 .sort((a, b) => b.score - a.score)
40 .map((x) => x.p);
41}
42
43function scoreMatch(p: DirectoryPerson, terms: string[]): number {
44 const fields = [
45 p.FirstName?.toLowerCase(),
46 p.LastName?.toLowerCase(),
47 p.Nickname?.toLowerCase(),
48 p.Username?.toLowerCase(),
49 p.DormName?.toLowerCase(),
50 p.DepartmentDescription?.toLowerCase(),
51 ].filter(Boolean) as string[];
52
53 let score = 0;
54 for (const term of terms) {
55 let hit = false;
56 for (const field of fields) {
57 if (field.startsWith(term)) {
58 score += 2;
59 hit = true;
60 } else if (field.includes(term)) {
61 score += 1;
62 hit = true;
63 }
64 }
65 if (!hit) return 0; // all terms must match something
66 }
67 return score;
68}
69
70export async function getCacheSize(): Promise<number> {
71 const cache = await loadCache();
72 return Object.keys(cache).length;
73}