1const { ref } = Vue; 2 3// site management 4 5let myPath = window.location.pathname.slice(1) || "index.html"; 6if (!/\.html/.test(myPath)) { 7 myPath = `${myPath}.html`; // cloudflare likes to drop the .html 8} 9 10/** replace a/b/c.md with a/b */ 11function path2dir(p) { 12 const dir = p.split("/").slice(0, -1).join("/"); 13 return dir; 14} 15 16/** replace a/b/c.md with a/b/c.html */ 17function md2html(p) { 18 return p.replace(/\.md$/, ".html"); 19} 20 21/** replace a/b/c with to /a/b/c, also '' => '/' */ 22function path2url(p) { 23 if (p === "index") { 24 return "/"; 25 } 26 return `/${p}`; 27} 28 29/** replace a/b/c.html with a/b/c.md */ 30function html2md(p) { 31 return p.replace(/\.html$/, ".md"); 32} 33 34/** replace a/b/c.md with a/b/c */ 35function dropmd(p) { 36 return p.replace(/\.md$/, ""); 37} 38 39/** replace a/b/c.html with a/b/c */ 40function drophtml(p) { 41 return p.replace(/\.html$/, "").replace(/\/$/, ""); 42} 43 44/** load and cook the site data */ 45async function siteData() { 46 // load the json 47 const d = await fetch("/assets/json/tree.json"); 48 const j = await d.json(); 49 const { usermap } = j; 50 51 return j; 52} 53 54const AncestorPages = { 55 props: ["ancestorPages"], 56 setup() {}, 57 template: ` 58 <span class="ancestor" v-for="ancestor of ancestorPages" :key="ancestor.href"> 59 <a class="uplink" v-bind:href="ancestor.href">{{ ancestor.title }}</a><span class="crumb">❱</span> 60 </span> 61`, 62}; 63 64const SubPagesPopup = { 65 props: { 66 children: { 67 type: Object, 68 required: true, 69 }, 70 }, 71 emits: ["hide"], // when user clicks hide 72 setup() {}, 73 methods: { 74 hide() { 75 this.$emit("hide"); 76 }, 77 }, 78 template: ` 79 <div class="subpages"> 80 <span class="hamburger" @click="hide()">✕</span> 81 <ul class="subpages" > 82 <li v-for="subpage of children" :key="subpage.path"> 83 <a v-bind:href="subpage.href"> 84 {{ subpage.title }} 85 <span class="hamburger" v-if="subpage.children">❱</span> 86 </a> 87 </li> 88 </ul> 89 </div> 90`, 91}; 92 93const SubMap = { 94 name: "SubMap", 95 props: { 96 usermap: { 97 type: Object, 98 required: true, 99 }, 100 path: String, 101 }, 102 setup() {}, 103 computed: { 104 title() { 105 return this.usermap[this.path].title; 106 }, 107 children() { 108 return this.usermap[this.path].children || []; 109 }, 110 href() { 111 return path2url(this.path); 112 }, 113 }, 114 template: ` 115 <div class="submap"> 116 <a v-bind:href="href" v-bind:title="path">{{title}}</a> 117 <SubMap v-for="child in children" :key="child" :usermap="usermap" :path="child" /> 118 </div> 119 `, 120}; 121 122const SiteMap = { 123 props: { 124 tree: { 125 type: Object, 126 required: true, 127 }, 128 }, 129 components: { 130 SubMap, 131 }, 132 emits: ["hide"], 133 setup() {}, 134 methods: { 135 hide() { 136 this.$emit("hide"); 137 }, 138 }, 139 template: ` 140 <div class="sitemap"> 141 <span class="hamburger" @click="hide()">✕</span> 142 <b>Site Map</b><hr/> 143 <SubMap :usermap="tree.value.usermap" path="index"/> 144 </div> 145 `, 146}; 147 148const app = Vue.createApp( 149 { 150 components: { 151 AncestorPages, 152 SubPagesPopup, 153 SiteMap, 154 }, 155 setup(props) { 156 // the tree.json data 157 const tree = ref({}); 158 // loading status for tree.json 159 const status = ref(null); 160 // is the popup menu shown? 161 const popup = ref(false); 162 // is the site map shown? 163 const showmap = ref(false); 164 165 return { 166 tree, 167 status, 168 popup, 169 showmap, 170 }; 171 }, 172 mounted() { 173 const t = this; 174 siteData().then( 175 (d) => (t.tree.value = d), 176 (e) => (t.status = e) 177 ); 178 }, 179 props: { 180 path: String, 181 }, 182 computed: { 183 /** base path: 'index' or 'downloads/cldr-33' */ 184 base() { 185 if (this.path) { 186 return drophtml(this.path); 187 } else { 188 return "index"; // '' => 'index' 189 } 190 return null; 191 }, 192 ourTitle() { 193 if (this.tree?.value) { 194 if (this.path === "") return this.rootTitle; 195 return this?.tree?.value?.usermap[this.base]?.title; 196 } 197 }, 198 // title of root 199 rootTitle() { 200 const usermap = this?.tree?.value?.usermap ?? {}; 201 return usermap?.index?.title ?? "CLDR"; 202 }, 203 children() { 204 const usermap = this?.tree?.value?.usermap; 205 if (!usermap) return []; // no children 206 const entry = usermap[this.base]; 207 const children = entry?.children; 208 if (!children || !children.length) return []; 209 return children.map((path) => ({ 210 path, 211 href: path2url(path), 212 title: usermap[path]?.title || path, 213 children: (usermap[path].children ?? []).length > 0, 214 })); 215 }, 216 ancestorPages() { 217 const pages = []; 218 // if we are not loaded, or if we're at the root, then exit 219 const usermap = this?.tree?.value?.usermap; 220 if ( 221 !usermap || 222 !this.path || 223 this.path == "index.html" || 224 this.map == "index" 225 ) { 226 return []; 227 } 228 // traverse 229 let path = drophtml(this.path); // can't be null, empty, or index (see above). Map a/b/c.html to a/b/c 230 do { 231 // calculate the immediate ancestor 232 const nextParentPath = usermap[path]?.parent; 233 if (!nextParentPath) break; 234 if (nextParentPath == path) { 235 console.error("Loop detected!"); 236 break; 237 } 238 const nextParent = usermap[nextParentPath]; 239 if (!nextParent) break; 240 const href = path2url(nextParentPath); 241 const { title } = nextParent || nextParentPath; 242 // prepend 243 pages.push({ 244 href, 245 title, 246 path: nextParentPath, 247 }); 248 path = nextParentPath; 249 } while (path); // we iterate over 'path' until it returns null 250 pages.reverse(); 251 return pages; 252 }, 253 }, 254 template: ` 255 <div> 256 <div class='status' v-if="status">{{ status }}</div> 257 <div class='status' v-if="!tree">Loading…</div> 258 <a class="icon" href="http://www.unicode.org/"> <img border="0" 259 src="/assets/img/logo60s2.gif" alt="[Unicode]" width="34" 260 height="33"></a> 261 262 <AncestorPages :ancestorPages="ancestorPages"/> 263 264 <div v-if="!children || !children.length" class="title"> {{ ourTitle }} </div> 265 266 <div v-else class="title" @mouseover="popup = true"><span class="hamburger" @click="showmap=false,popup = !popup">≡</span> 267 268 {{ ourTitle }} 269 270 <SubPagesPopup v-if="popup && !showmap" @hide="popup = false" :children="children"/> 271 272 </div> 273 <div class="showmap" v-if="!showmap" @click="popup=false,showmap = true">Site Map</div> 274 <SiteMap v-if="tree && showmap" :tree="tree" @hide="showmap = popup = false" /> 275 276 </div>`, 277 }, 278 { 279 // path of / goes to /index.html 280 path: myPath, 281 } 282); 283 284app.mount("#nav"); 285 286// load anchor.js 287anchors.add("h1, h2, h3, h4, h5, h6, caption, dfn"); 288