117 lines
3.5 KiB
JavaScript
117 lines
3.5 KiB
JavaScript
/*
|
|
* 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 };
|