1/* Blog sidebar. Collapsible lists, Archive relabelled, counts made pills. */ 2(function () { 3 const BLOG_CATEGORIES_URL = "/doortablet/dtcms.nsf/base.xsp?open&cmd=blogcategs&return=json"; 4 const BLOG_CATEGORIES_CACHE_KEY = "blogCategories"; 5 const BLOG_CATEGORIES_CACHE_TIME_KEY = "blogCategoriesTime"; 6 const BLOG_CATEGORIES_MAX_AGE = 4 * 60 * 60 * 1000; // 4 hours 7 8 function addCount(link, digits) { 9 var pill = document.createElement("span"); 10 pill.className = "dt-count"; 11 pill.appendChild(document.createTextNode(digits)); 12 link.appendChild(pill); 13 } 14 15 function hasClass(el, name) { 16 return (" " + el.className + " ").indexOf(" " + name + " ") !== -1; 17 } 18 19 function toggle(box, head) { 20 var open = hasClass(box, "dt-open"); 21 if (open) { 22 box.className = (" " + box.className + " ").replace(" dt-open ", " ").replace(/^\s+|\s+$/g, ""); 23 } else { 24 box.className = (box.className + " dt-open").replace(/^\s+/, ""); 25 } 26 head.setAttribute("aria-expanded", open ? "false" : "true"); 27 } 28 29 /* Cap the list at MAX rows. Anything past it is hidden rather than removed */ 30 /* so the links stay in the markup and remain crawlable. */ 31 function capList(list, max) { 32 var extra = [].slice.call(list.getElementsByTagName("a"), max); 33 var i = extra.length; 34 while (i--) extra[i].className = extra[i].className + " dt-hide"; 35 } 36 37 function build(box, label, max) { 38 if (!box || box.getAttribute("data-dt")) return; 39 box.setAttribute("data-dt", "1"); 40 41 var head = box.getElementsByTagName("span")[0]; 42 if (!head) return; 43 44 /* The CMS puts the same id on both headings, so the heading is reached */ 45 /* through its parent rather than by id. */ 46 if (label) { 47 while (head.firstChild) head.removeChild(head.firstChild); 48 head.appendChild(document.createTextNode(label)); 49 } 50 head.className = "dt-head"; 51 head.setAttribute("role", "button"); 52 head.setAttribute("tabindex", "0"); 53 head.setAttribute("aria-expanded", "false"); 54 55 var list = document.createElement("div"); 56 list.className = "dt-list"; 57 58 var node = head.nextSibling; 59 var next, 60 lastLink = null, 61 digits; 62 while (node) { 63 next = node.nextSibling; 64 digits = null; 65 if (node.nodeType === 3 && /\(\s*\d+\s*\)/.test(node.nodeValue)) { 66 digits = node.nodeValue.match(/\d+/)[0]; 67 } 68 if (digits !== null && lastLink) { 69 addCount(lastLink, digits); 70 box.removeChild(node); 71 } else { 72 if (node.nodeType === 1 && node.tagName === "A") lastLink = node; 73 list.appendChild(node); 74 } 75 node = next; 76 } 77 box.appendChild(list); 78 capList(list, max); 79 80 head.onclick = function () { 81 toggle(box, head); 82 }; 83 head.onkeydown = function (ev) { 84 var e = ev || window.event; 85 if (e.keyCode === 13 || e.keyCode === 32) { 86 if (e.preventDefault) e.preventDefault(); 87 toggle(box, head); 88 } 89 }; 90 } 91 92 function init() { 93 build(document.getElementById("recent-posts"), null, 12); 94 build(document.getElementById("archive-list"), "Previous Posts", 12); 95 initBlogCategories(); 96 } 97 98 if (document.readyState === "loading") { 99 document.addEventListener("DOMContentLoaded", init); 100 } else { 101 init(); 102 } 103 104 async function loadBlogCategories() { 105 const now = Date.now(); 106 const cachedData = localStorage.getItem(BLOG_CATEGORIES_CACHE_KEY); 107 const cachedTime = parseInt(localStorage.getItem(BLOG_CATEGORIES_CACHE_TIME_KEY) || "0", 10); 108 const cacheExpired = !cachedData || !cachedTime || now - cachedTime > BLOG_CATEGORIES_MAX_AGE; 109
110 const useServer = hasDominoWebSession() || cacheExpired; 111 if (!useServer) return JSON.parse(cachedData); 112 113 try { 114 const response = await fetch(BLOG_CATEGORIES_URL, { 115 cache: "no-store", 116 }); 117 118 if (!response.ok) throw new Error("HTTP " + response.status); 119 const data = await response.json(); 120 121 localStorage.setItem(BLOG_CATEGORIES_CACHE_KEY, JSON.stringify(data)); 122 123 localStorage.setItem(BLOG_CATEGORIES_CACHE_TIME_KEY, now.toString()); 124 return data; 125 } catch (err) { 126 console.error("Unable to load blog categories:", err); 127 if (cachedData) return JSON.parse(cachedData); 128 throw err; 129 } 130 } 131 132 function hasDominoWebSession() { 133 return document.cookie.split(";").some((cookie) => cookie.trim().startsWith("DomAuthSessId=")); 134 } 135 136 function renderBlogCategories(data) { 137 const container = document.getElementById("blogCategories"); 138 if (!container || !data || !data.categories) return; 139 container.innerHTML = ""; 140 data.categories.forEach((item) => { 141 const link = document.createElement("a"); 142 143 link.className = "blog-category"; 144 link.href = "/doortablet/dtcms.nsf/blog?open&type=categ&key=" + encodeURIComponent(item.category); 145 146 const name = document.createElement("span"); 147 name.className = "blog-category-name"; 148 name.textContent = item.category; 149 150 const badge = document.createElement("span"); 151 badge.className = "blog-category-badge"; 152 badge.textContent = item.count; 153 154 link.appendChild(name); 155 link.appendChild(badge); 156 157 container.appendChild(link); 158 }); 159 } 160 161 async function initBlogCategories() { 162 try { 163 const data = await loadBlogCategories(); 164 renderBlogCategories(data); 165 renderBlogDocumentCategories(data); 166 } catch (err) { 167 console.error("Blog categories unavailable:", err); 168 } 169 } 170 171 function renderBlogDocumentCategories(data) { 172 if (!data || !data.categories) return; 173 174 // Create a lookup table for the category counts 175 const categoryMap = new Map(); 176 177 data.categories.forEach((item) => { 178 categoryMap.set(item.category.trim().toLowerCase(), item); 179 }); 180 181 document.querySelectorAll(".blogCategs").forEach((container) => { 182 const raw = container.dataset.categ; 183 if (!raw) return; 184 const wantedCategories = raw 185 .split(",") 186 .map((c) => c.trim()) 187 .filter(Boolean); 188 189 container.innerHTML = ""; 190 191 wantedCategories.forEach((categoryName) => { 192 const item = categoryMap.get(categoryName.toLowerCase()); 193 const link = document.createElement("a"); 194 link.className = "blog-category"; 195 link.href = "/doortablet/dtcms.nsf/blog?open&type=categ&key=" + encodeURIComponent(categoryName); 196 const name = document.createElement("span"); 197 name.className = "blog-category-name"; 198 name.textContent = categoryName; 199 200 link.appendChild(name); 201 202 // Add badge if we found this category in the server data 203 if (item) { 204 const badge = document.createElement("span"); 205 badge.className = "blog-category-badge"; 206 badge.textContent = item.count; 207 208 link.appendChild(badge); 209 } 210 211 container.appendChild(link); 212 }); 213 }); 214 } 215})();
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.