1// Hash-based router for the archive SPA: the window-touching navigation layer. 2// 3// The route vocabulary (ROUTES / DEFAULT_ROUTE) is owned by viewState.js, the 4// single source of truth for view state called for in issue #133. This module 5// imports it rather than keeping a second copy â two hand-maintained ROUTES 6// objects could silently drift â and re-exports ROUTES so existing importers 7// (App.js) keep working unchanged. The pure URL serialisation lives in 8// viewState.js too; what remains here is the imperative, window-bound 9// navigation helpers. 10 11import { ROUTES, DEFAULT_ROUTE, parseViewState } from './viewState.js?v=3.8.36'; 12import { parseRecordId, setRecordParam } from '../utils/recordDeepLink.js?v=3.8.36'; 13 14export { ROUTES }; 15 16/** 17 * Read the current hash and return the matching route name. 18 * Falls back to 'archive' for unknown or empty hashes. 19 */ 20export function getCurrentRoute() { 21 return parseViewState(window.location.href).route || DEFAULT_ROUTE; 22} 23 24/** 25 * Read the optional in-shell desktop app id from the current URL. 26 */ 27export function getDesktopAppIdFromUrl() { 28 const state = parseViewState(window.location.href); 29 return state.route === ROUTES.desktop ? state.routeParams.desktopAppId || null : null; 30} 31 32/** 33 * Read the optional selected entity from a canonical entity route. 34 */ 35export function getEntityIdFromUrl() { 36 return parseViewState(window.location.href).routeParams.entityId || null; 37} 38 39/** 40 * Navigate to a route by updating the hash. 41 * Optionally sets a ?record=ID query parameter and canonical entity context. 42 */ 43export function navigateTo(route, recordId, entityId) { 44 const url = new URL(window.location.href); 45 46 // Clean up legacy query params 47 url.searchParams.delete('view'); 48 if (route === ROUTES.entities && entityId !== undefined) { 49 if (/^[A-Za-z0-9_.:-]+$/.test(entityId || '')) url.searchParams.set('entity', entityId); 50 else url.searchParams.delete('entity'); 51 } else if (route !== ROUTES.entities) { 52 url.searchParams.delete('entity'); 53 } 54 55 setRecordParam(url.searchParams, recordId); 56 57 url.hash = route === DEFAULT_ROUTE ? '' : route; 58 window.history.pushState({}, '', url); 59 window.dispatchEvent(new HashChangeEvent('hashchange')); 60} 61 62/** 63 * Navigate within the desktop shell while keeping the app id in the hash. 64 */ 65export function navigateToDesktop(appId = null, entityId) { 66 if (appId !== null && ( 67 typeof appId !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(appId) 68 )) { 69 throw new TypeError('Desktop app id must be a lowercase URL-safe id'); 70 } 71 72 const url = new URL(window.location.href); 73 url.searchParams.delete('view'); 74 if (appId === 'entities' && entityId !== undefined) { 75 if (/^[A-Za-z0-9_.:-]+$/.test(entityId || '')) url.searchParams.set('entity', entityId); 76 else url.searchParams.delete('entity'); 77 } else if (appId !== 'entities') { 78 url.searchParams.delete('entity'); 79 } 80 setRecordParam(url.searchParams, null); 81 url.hash = appId ? `${ROUTES.desktop}/${appId}` : ROUTES.desktop; 82 window.history.pushState({}, '', url); 83 window.dispatchEvent(new HashChangeEvent('hashchange')); 84} 85 86/** 87 * Read ?record= from the current URL. 88 */ 89export function getRecordIdFromUrl() { 90 return parseRecordId(window.location.search); 91} 92 93/** 94 * Migrate legacy ?view= URLs to hash routes. 95 * Call once on app init. 96 */ 97export function migrateLegacyUrl() { 98 const params = new URLSearchParams(window.location.search); 99 const view = params.get('view'); 100 if (view === 'dissertation' || view === 'about') { 101 navigateTo(ROUTES[view]); 102 } 103}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.