A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4 163 lines 4.7 kB view raw
1import * as Build from "./code.js"; 2import * as Dashboard from "./dashboard.js"; 3import * as Grid from "./grid.js"; 4import * as Guide from "./guide.js"; 5import * as Nav from "./nav.js"; 6 7/** Base pathname of the app (e.g. "/" at root, "/diffuse/" in a subdirectory). */ 8const BASE_PATHNAME = new URL(document.baseURI).pathname; 9 10/** 11 * Strips the app's base path prefix from an absolute pathname, 12 * returning a root-relative path like "/code". 13 * 14 * @param {string} pathname 15 */ 16function relativePathname(pathname) { 17 const stripped = pathname.replace(/\/$/, ""); 18 const base = BASE_PATHNAME.replace(/\/$/, ""); 19 return base.length > 0 && stripped.startsWith(base) 20 ? stripped.slice(base.length) 21 : stripped; 22} 23 24/** 25 * @param {URL} url 26 */ 27async function initJsBasedOnPage(url) { 28 const path = relativePathname(url.pathname); 29 30 Nav.update(); 31 Nav.updateActiveLinks(); 32 Nav.watchResize(); 33 34 Grid.setupFilter(); 35 Grid.insertToggleButtons(); 36 await Grid.monitorToggleButtonStates(); 37 await Grid.setupOutputIndicator(); 38 39 switch (path) { 40 case "/code": 41 Build.renderEditor(); 42 Build.handleBuildFormSubmit(); 43 Build.listenForExamplesEdit(); 44 await Build.editFacetFromURL(); 45 break; 46 case "/dashboard": 47 await Dashboard.renderList(); 48 break; 49 case "/guide": 50 Guide.setupSampleButton(); 51 break; 52 default: 53 break; 54 } 55} 56 57initJsBasedOnPage(new URL(location.href)); 58 59// Partial page updates for kitchen navigation using the Navigation API. 60// Intercepts nav link clicks, fetches the new page, and swaps <main> content 61// instead of doing a full page load. 62 63if ("navigation" in globalThis) { 64 /** @type {any} */ (globalThis).navigation.addEventListener( 65 "navigate", 66 navigateHandler, 67 ); 68} 69 70/** @param {any} event */ 71function navigateHandler(event) { 72 if (!event.canIntercept) return; 73 74 const url = new URL(event.destination.url); 75 if (url.origin !== location.origin) return; 76 if (url.pathname === location.pathname) return; 77 78 // Only intercept paths one level deep 79 const relative = relativePathname(url.pathname); 80 const parts = relative.split("/").filter(Boolean); 81 if (parts.length === 0) return; 82 if (parts.length > 2) return; 83 84 // Skip the loader page 85 if (parts[0] === "l") return; 86 if (parts.includes("chronicle")) return; 87 88 event.intercept({ 89 scroll: "manual", 90 async handler() { 91 const navLinks = /** @type {HTMLAnchorElement[]} */ ([ 92 ...document.querySelectorAll("#diffuse-nav a, #nav-overflow-menu a"), 93 ]); 94 const stripSlash = (/** @type {string} */ p) => p.replace(/^\//, ""); 95 const navLink = navLinks.find( 96 (a) => 97 stripSlash(new URL(a.href).pathname) === stripSlash(url.pathname), 98 ); 99 100 const icon = navLink?.querySelector("i"); 101 const originalIconClass = icon?.className; 102 let addedSpinner = /** @type {HTMLElement | undefined} */ (undefined); 103 104 const loadingTimer = navLink 105 ? setTimeout(() => { 106 if (icon) { 107 icon.className = "ph-bold ph-spinner animate-spin"; 108 } else { 109 addedSpinner = document.createElement("i"); 110 addedSpinner.className = "ph-bold ph-spinner animate-spin"; 111 const span = navLink.querySelector("span"); 112 (span ?? navLink).prepend(addedSpinner); 113 } 114 }, 250) 115 : undefined; 116 117 let html; 118 119 try { 120 const response = await fetch(url); 121 if (!response.ok) throw new Error(`${response.status}`); 122 html = await response.text(); 123 } catch { 124 clearTimeout(loadingTimer); 125 if (icon && originalIconClass !== undefined) icon.className = originalIconClass; 126 addedSpinner?.remove(); 127 location.href = url.href; 128 return; 129 } finally { 130 clearTimeout(loadingTimer); 131 if (icon && originalIconClass !== undefined) icon.className = originalIconClass; 132 addedSpinner?.remove(); 133 } 134 135 const parser = new DOMParser(); 136 const doc = parser.parseFromString(html, "text/html"); 137 138 const newMain = doc.querySelector("main"); 139 const currentMain = document.querySelector("main"); 140 141 if (!newMain || !currentMain) { 142 location.href = url.href; 143 return; 144 } 145 146 document.title = doc.title; 147 148 // Replace <main> content 149 const range = document.createRange(); 150 range.selectNode(currentMain); 151 const documentFragment = range.createContextualFragment( 152 newMain.innerHTML ?? "", 153 ); 154 155 currentMain.innerHTML = ""; 156 currentMain.append(documentFragment); 157 158 initJsBasedOnPage(url); 159 160 window.scrollTo({ top: 0, behavior: "instant" }); 161 }, 162 }); 163}