feat(ui): static shell + router + api wrapper
Three-column grid (sidebar / main / right rail) with Cradle aesthetic: blackflame accent on Cinzel display headings + Cormorant Garamond body in cards, system UI for chrome. Hash-based router covers all entity routes plus search, inbox, sacred-valley. api.js stores OWNER_TOKEN in localStorage and prompts via a modal on 401. dom.js provides safe el() + mount() builders so no component ever assigns innerHTML from API data (the only exception is an explicit, scary-named html: opt-in for sanitizer output, used later by the markdown editor). state.js is a tiny event bus for shared chrome state (pending count). Components and views are loaded as ES modules — sidebar / topbar / rightrail + 9 view stubs that the later Phase E tasks fill in. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
47
public/router.js
Normal file
47
public/router.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// Hash-based router. Routes:
|
||||
// #/ home
|
||||
// #/space/:id space view
|
||||
// #/project/:id project view
|
||||
// #/page/:id page view
|
||||
// #/ref/:id reference detail
|
||||
// #/resource/:id resource detail
|
||||
// #/search?q= search results
|
||||
// #/inbox pending changes
|
||||
// #/sacred-valley dashboard placeholder
|
||||
// Anything unrecognized falls through to the home handler.
|
||||
|
||||
const ROUTES = [
|
||||
{ name: 'space', re: /^\/space\/([^/]+)$/, keys: ['id'] },
|
||||
{ name: 'project', re: /^\/project\/([^/]+)$/, keys: ['id'] },
|
||||
{ name: 'page', re: /^\/page\/([^/]+)$/, keys: ['id'] },
|
||||
{ name: 'ref', re: /^\/ref\/([^/]+)$/, keys: ['id'] },
|
||||
{ name: 'resource', re: /^\/resource\/([^/]+)$/, keys: ['id'] },
|
||||
{ name: 'search', re: /^\/search$/, keys: [] },
|
||||
{ name: 'inbox', re: /^\/inbox$/, keys: [] },
|
||||
{ name: 'sacred-valley', re: /^\/sacred-valley$/, keys: [] },
|
||||
{ name: 'home', re: /^\/?$/, keys: [] }
|
||||
];
|
||||
|
||||
export function current() {
|
||||
const raw = (location.hash || '#/').slice(1);
|
||||
const [path, queryString = ''] = raw.split('?');
|
||||
const query = Object.fromEntries(new URLSearchParams(queryString));
|
||||
for (const r of ROUTES) {
|
||||
const m = path.match(r.re);
|
||||
if (m) {
|
||||
const params = {};
|
||||
r.keys.forEach((k, i) => { params[k] = m[i + 1]; });
|
||||
return { name: r.name, params, query, hash: raw };
|
||||
}
|
||||
}
|
||||
return { name: 'home', params: {}, query: {}, hash: raw };
|
||||
}
|
||||
|
||||
export function navigate(hash) {
|
||||
location.hash = hash.startsWith('#') ? hash : '#' + hash;
|
||||
}
|
||||
|
||||
export function route(handler) {
|
||||
window.addEventListener('hashchange', () => handler(current()));
|
||||
handler(current());
|
||||
}
|
||||
Reference in New Issue
Block a user