refactoring
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Per-tab wiki page history.
|
||||
*
|
||||
* BreadcrumbTrail owns the ordered page slugs and their session-storage
|
||||
* representation. It deliberately knows nothing about page titles, routes or
|
||||
* DOM elements; the application supplies the current set of valid page slugs.
|
||||
*/
|
||||
|
||||
const storageKey = "racket-wiki-breadcrumb-trail";
|
||||
const trailLimit = 8;
|
||||
|
||||
class BreadcrumbTrail {
|
||||
/**
|
||||
* goal : Create an empty breadcrumb history backed by session storage.
|
||||
* pre : storage implements the Web Storage getItem/setItem interface.
|
||||
* post : No stored value is read until load is called with valid pages.
|
||||
* result : A directly usable breadcrumb history.
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
this.pageSlugs = [];
|
||||
}
|
||||
|
||||
/** Return a copy of the ordered non-home page slugs. */
|
||||
get slugs() {
|
||||
return this.pageSlugs.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Restore the trail and discard references to missing pages.
|
||||
* pre : validSlugs contains the current readable page slugs.
|
||||
* post : The normalized trail is stored again for this browser tab.
|
||||
* result : A copy of the restored trail.
|
||||
* internals: JSON is decoded first, then filtered through validSlugs so stale
|
||||
* browser state cannot become application navigation.
|
||||
*/
|
||||
load(validSlugs) {
|
||||
let stored = [];
|
||||
try {
|
||||
stored = JSON.parse(this.storage.getItem(storageKey) || "[]");
|
||||
} catch (_error) {
|
||||
stored = [];
|
||||
}
|
||||
|
||||
const valid = new Set(validSlugs);
|
||||
if (Array.isArray(stored)) {
|
||||
this.pageSlugs = stored.filter((slug) =>
|
||||
typeof slug === "string" && valid.has(slug));
|
||||
} else {
|
||||
this.pageSlugs = [];
|
||||
}
|
||||
this.#save();
|
||||
return this.slugs;
|
||||
}
|
||||
|
||||
/** Empty and persist the breadcrumb history. */
|
||||
clear() {
|
||||
this.pageSlugs = [];
|
||||
this.#save();
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Record navigation to one wiki page.
|
||||
* pre : pageSlug identifies the visited page; homeSlug may be null.
|
||||
* post : Home clears the trail, revisits truncate it, and new visits append.
|
||||
* result : A copy of the updated trail.
|
||||
* internals: The trail contains only non-home pages and retains at most the
|
||||
* eight most recent entries, matching the visible breadcrumb.
|
||||
*/
|
||||
record(pageSlug, homeSlug = null) {
|
||||
if (!pageSlug) return this.slugs;
|
||||
if (homeSlug && pageSlug === homeSlug) {
|
||||
this.clear();
|
||||
return this.slugs;
|
||||
}
|
||||
|
||||
const existingIndex = this.pageSlugs.indexOf(pageSlug);
|
||||
if (existingIndex >= 0) {
|
||||
this.pageSlugs = this.pageSlugs.slice(0, existingIndex + 1);
|
||||
} else {
|
||||
this.pageSlugs.push(pageSlug);
|
||||
if (this.pageSlugs.length > trailLimit) {
|
||||
this.pageSlugs = this.pageSlugs.slice(-trailLimit);
|
||||
}
|
||||
}
|
||||
this.#save();
|
||||
return this.slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Discard breadcrumb entries that follow a selected trail page.
|
||||
* pre : pageSlug is a breadcrumb destination; homeSlug may be null.
|
||||
* post : Home clears the trail; an existing page becomes its final entry.
|
||||
* result : A copy of the updated trail.
|
||||
*/
|
||||
truncate(pageSlug, homeSlug = null) {
|
||||
if (homeSlug && pageSlug === homeSlug) {
|
||||
this.clear();
|
||||
return this.slugs;
|
||||
}
|
||||
|
||||
const index = this.pageSlugs.indexOf(pageSlug);
|
||||
if (index >= 0) {
|
||||
this.pageSlugs = this.pageSlugs.slice(0, index + 1);
|
||||
this.#save();
|
||||
}
|
||||
return this.slugs;
|
||||
}
|
||||
|
||||
/** Persist the current representation for this browser tab. */
|
||||
#save() {
|
||||
this.storage.setItem(storageKey, JSON.stringify(this.pageSlugs));
|
||||
}
|
||||
}
|
||||
|
||||
export { BreadcrumbTrail };
|
||||
Reference in New Issue
Block a user