142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
/**
|
||
* 学习进度、继续学习、收藏、笔记 - localStorage(来自 nomadlms)
|
||
*/
|
||
|
||
const PREFIX = "lms-";
|
||
const KEY_LAST = `${PREFIX}last`;
|
||
const KEY_COMPLETED = `${PREFIX}completed`;
|
||
const KEY_BOOKMARKS = `${PREFIX}bookmarks`;
|
||
const KEY_NOTES = `${PREFIX}notes`;
|
||
const KEY_RATING = `${PREFIX}rating`;
|
||
|
||
export type LastWatched = { moduleIndex: number; lessonIndex: number; name: string };
|
||
|
||
export function getLastWatched(): LastWatched | null {
|
||
if (typeof window === "undefined") return null;
|
||
try {
|
||
const raw = localStorage.getItem(KEY_LAST);
|
||
if (!raw) return null;
|
||
const d = JSON.parse(raw);
|
||
if (typeof d?.moduleIndex !== "number" || typeof d?.lessonIndex !== "number") return null;
|
||
return { moduleIndex: d.moduleIndex, lessonIndex: d.lessonIndex, name: d.name || "" };
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export function setLastWatched(m: number, l: number, name: string): void {
|
||
if (typeof window === "undefined") return;
|
||
try {
|
||
localStorage.setItem(KEY_LAST, JSON.stringify({ moduleIndex: m, lessonIndex: l, name }));
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
export function getCompletedLessons(): Set<string> {
|
||
if (typeof window === "undefined") return new Set();
|
||
try {
|
||
const raw = localStorage.getItem(KEY_COMPLETED);
|
||
if (!raw) return new Set();
|
||
const arr = JSON.parse(raw);
|
||
return new Set(Array.isArray(arr) ? arr : []);
|
||
} catch {
|
||
return new Set();
|
||
}
|
||
}
|
||
|
||
export function markCompleted(moduleIndex: number, lessonIndex: number): void {
|
||
if (typeof window === "undefined") return;
|
||
try {
|
||
const set = getCompletedLessons();
|
||
set.add(`${moduleIndex}-${lessonIndex}`);
|
||
localStorage.setItem(KEY_COMPLETED, JSON.stringify([...set]));
|
||
window.dispatchEvent(new CustomEvent("learning:progress"));
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
export function getProgress(totalLessons: number): { completed: number; percent: number } {
|
||
const set = getCompletedLessons();
|
||
const completed = set.size;
|
||
return { completed, percent: totalLessons > 0 ? Math.round((completed / totalLessons) * 100) : 0 };
|
||
}
|
||
|
||
export function getBookmarks(): Set<string> {
|
||
if (typeof window === "undefined") return new Set();
|
||
try {
|
||
const raw = localStorage.getItem(KEY_BOOKMARKS);
|
||
if (!raw) return new Set();
|
||
const arr = JSON.parse(raw);
|
||
return new Set(Array.isArray(arr) ? arr : []);
|
||
} catch {
|
||
return new Set();
|
||
}
|
||
}
|
||
|
||
export function toggleBookmark(moduleIndex: number, lessonIndex: number): boolean {
|
||
if (typeof window === "undefined") return false;
|
||
try {
|
||
const set = getBookmarks();
|
||
const key = `${moduleIndex}-${lessonIndex}`;
|
||
if (set.has(key)) set.delete(key);
|
||
else set.add(key);
|
||
localStorage.setItem(KEY_BOOKMARKS, JSON.stringify([...set]));
|
||
window.dispatchEvent(new CustomEvent("learning:bookmarks"));
|
||
return set.has(key);
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
export function isBookmarked(moduleIndex: number, lessonIndex: number): boolean {
|
||
return getBookmarks().has(`${moduleIndex}-${lessonIndex}`);
|
||
}
|
||
|
||
export function getNote(moduleIndex: number, lessonIndex: number): string {
|
||
if (typeof window === "undefined") return "";
|
||
try {
|
||
const raw = localStorage.getItem(KEY_NOTES);
|
||
if (!raw) return "";
|
||
const map = JSON.parse(raw);
|
||
return map[`${moduleIndex}-${lessonIndex}`] ?? "";
|
||
} catch {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
export function setNote(moduleIndex: number, lessonIndex: number, text: string): void {
|
||
if (typeof window === "undefined") return;
|
||
try {
|
||
const raw = localStorage.getItem(KEY_NOTES);
|
||
const map = raw ? JSON.parse(raw) : {};
|
||
map[`${moduleIndex}-${lessonIndex}`] = text;
|
||
localStorage.setItem(KEY_NOTES, JSON.stringify(map));
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
export function getRating(): { stars: number; comment: string } | null {
|
||
if (typeof window === "undefined") return null;
|
||
try {
|
||
const raw = localStorage.getItem(KEY_RATING);
|
||
if (!raw) return null;
|
||
const d = JSON.parse(raw);
|
||
return { stars: d?.stars ?? 0, comment: d?.comment ?? "" };
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export function setRating(stars: number, comment: string): void {
|
||
if (typeof window === "undefined") return;
|
||
try {
|
||
localStorage.setItem(KEY_RATING, JSON.stringify({ stars, comment }));
|
||
window.dispatchEvent(new CustomEvent("learning:rating"));
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|