1// command-palette.js â Cmd/Ctrl+K quick navigation 2(function () { 3 'use strict'; 4 5 var dialog = document.getElementById('cmdk-dialog'); 6 if (!dialog) return; 7 8 var langUrl = dialog.getAttribute('data-lang-url') || ''; 9 var currentLang = dialog.getAttribute('data-lang') || 'en'; 10 var LOCALES = ['ja', 'zh', 'ko', 'fr', 'es', 'de', 'it', 'lzh', 'yue', 'bo']; 11 var pathPrefix = LOCALES.indexOf(currentLang) !== -1 ? '/' + currentLang : ''; 12 13 var input = dialog.querySelector('.cmdk-input'); 14 var listEl = dialog.querySelector('.cmdk-list'); 15 var emptyEl = dialog.querySelector('.cmdk-empty'); 16 17 var EMAIL = '[email protected]'; 18 19 var COMMANDS = [ 20 { label: 'Home', hint: 'Overview & featured work', url: pathPrefix + '/', keywords: 'index landing' }, 21 { label: 'Work', hint: 'Portfolio projects', url: pathPrefix + '/work.html', keywords: 'projects portfolio' }, 22 { label: 'Publications & Awards', hint: 'Recognition', url: pathPrefix + '/#publications', keywords: 'awards papers transparency report' }, 23 { label: 'Blog', hint: 'Writing & thoughts', url: pathPrefix + '/blog.html', keywords: 'writing posts articles' }, 24 { label: 'Transparency Report dashboard', hint: 'Google gov removals â interactive', url: pathPrefix + '/transparency.html', keywords: 'google government removals data analysis dashboard charts' }, 25 { label: 'Filter projects: AI / LLM', hint: 'RAG, Gemini, prompt engineering', url: '/work.html?filter=ai-llm', keywords: 'rag gemini llm prompt' }, 26 { label: 'Filter projects: Compliance', hint: 'CMA, EU DSA, transparency', url: '/work.html?filter=compliance', keywords: 'cma dsa regulatory' }, 27 { label: 'Filter projects: Data & SQL', hint: 'SQL, ETL, pipelines', url: '/work.html?filter=data', keywords: 'sql etl bigquery' }, 28 { label: 'Filter projects: Automation', hint: 'Chrome ext, AppsScript', url: '/work.html?filter=automation', keywords: 'chrome browser appsscript' }, 29 { label: 'Copy email address', hint: EMAIL, action: 'copy-email', keywords: 'contact mail' }, 30 { label: 'Email Kieran', hint: 'Compose new message', url: 'mailto:' + EMAIL, keywords: 'contact mail' }, 31 { label: 'LinkedIn', hint: 'Open profile', url: 'https://linkedin.com/in/KieranMaynard', external: true, keywords: 'social' }, 32 { label: 'GitHub', hint: '@krmaynard', url: 'https://github.com/krmaynard', external: true, keywords: 'code repos' } 33 ]; 34 35 if (langUrl) { 36 COMMANDS.push({ 37 label: currentLang === 'en' ? 'æ¥æ¬èªã§èªã' : 'Switch to English', 38 hint: 'Language switch', 39 url: langUrl, 40 keywords: 'language japanese english nihongo' 41 }); 42 } 43 44 var filtered = COMMANDS.slice(); 45 var cursor = 0; 46 47 function escapeHtml(s) { 48 return String(s).replace(/[&<>"']/g, function (c) { 49 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; 50 }); 51 } 52 53 function render() { 54 listEl.innerHTML = ''; 55 if (filtered.length === 0) { 56 emptyEl.hidden = false; 57 return; 58 } 59 emptyEl.hidden = true; 60 var frag = document.createDocumentFragment(); 61 filtered.forEach(function (cmd, i) { 62 var li = document.createElement('li'); 63 li.className = 'cmdk-item' + (i === cursor ? ' is-active' : ''); 64 li.setAttribute('role', 'option'); 65 li.setAttribute('aria-selected', i === cursor ? 'true' : 'false'); 66 li.setAttribute('data-index', String(i)); 67 li.innerHTML = 68 '<span class="cmdk-label">' + escapeHtml(cmd.label) + '</span>' + 69 '<span class="cmdk-hint">' + escapeHtml(cmd.hint || '') + '</span>'; 70 frag.appendChild(li); 71 }); 72 listEl.appendChild(frag); 73 } 74 75 function applyFilter(q) { 76 q = (q || '').trim().toLowerCase(); 77 if (!q) { 78 filtered = COMMANDS.slice(); 79 } else { 80 var terms = q.split(/\s+/); 81 filtered = COMMANDS.filter(function (cmd) { 82 var hay = (cmd.label + ' ' + (cmd.hint || '') + ' ' + (cmd.keywords || '')).toLowerCase(); 83 return terms.every(function (t) { return hay.indexOf(t) !== -1; }); 84 }); 85 } 86 cursor = 0; 87 render(); 88 } 89 90 function toast(msg) { 91 var t = document.createElement('div'); 92 t.className = 'cmdk-toast'; 93 t.textContent = msg; 94 document.body.appendChild(t); 95 setTimeout(function () { t.classList.add('is-out'); }, 1600); 96 setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 2200); 97 } 98 99 function activate(cmd) { 100 if (!cmd) return; 101 closeDialog(); 102 if (cmd.action === 'copy-email') { 103 if (navigator.clipboard && navigator.clipboard.writeText) { 104 navigator.clipboard.writeText(EMAIL).then( 105 function () { toast('Email copied to clipboard'); }, 106 function () { window.location.href = 'mailto:' + EMAIL; } 107 ); 108 } else { 109 window.location.href = 'mailto:' + EMAIL; 110 } 111 return; 112 } 113 if (cmd.url) { 114 if (cmd.external) window.open(cmd.url, '_blank', 'noopener'); 115 else window.location.href = cmd.url; 116 } 117 } 118 119 function openDialog() { 120 if (dialog.open) return; 121 input.value = ''; 122 applyFilter(''); 123 if (typeof dialog.showModal === 'function') dialog.showModal(); 124 else dialog.setAttribute('open', ''); 125 setTimeout(function () { input.focus(); }, 0); 126 } 127 128 function closeDialog() { 129 if (typeof dialog.close === 'function' && dialog.open) dialog.close(); 130 else dialog.removeAttribute('open'); 131 } 132 133 function scrollCursorIntoView() { 134 var el = listEl.querySelector('.cmdk-item.is-active'); 135 if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); 136 } 137 138 document.addEventListener('keydown', function (e) { 139 if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) { 140 e.preventDefault(); 141 if (dialog.open) closeDialog(); else openDialog(); 142 } 143 }); 144 145 input.addEventListener('keydown', function (e) { 146 if (filtered.length === 0) return; 147 if (e.key === 'ArrowDown') { 148 e.preventDefault(); 149 cursor = (cursor + 1) % filtered.length; 150 render(); 151 scrollCursorIntoView(); 152 } else if (e.key === 'ArrowUp') { 153 e.preventDefault(); 154 cursor = (cursor - 1 + filtered.length) % filtered.length; 155 render(); 156 scrollCursorIntoView(); 157 } else if (e.key === 'Enter') { 158 e.preventDefault(); 159 activate(filtered[cursor]); 160 } 161 }); 162 163 input.addEventListener('input', function () { applyFilter(input.value); }); 164 165 listEl.addEventListener('click', function (e) { 166 var li = e.target.closest('.cmdk-item'); 167 if (!li) return; 168 activate(filtered[+li.getAttribute('data-index')]); 169 }); 170 171 listEl.addEventListener('mousemove', function (e) { 172 var li = e.target.closest('.cmdk-item'); 173 if (!li) return; 174 var i = +li.getAttribute('data-index'); 175 if (i !== cursor) { 176 cursor = i; 177 listEl.querySelectorAll('.cmdk-item').forEach(function (n, idx) { 178 var active = idx === cursor; 179 n.classList.toggle('is-active', active); 180 n.setAttribute('aria-selected', active ? 'true' : 'false'); 181 }); 182 } 183 }); 184 185 dialog.addEventListener('click', function (e) { 186 if (e.target === dialog) closeDialog(); 187 }); 188 189 Array.prototype.forEach.call(document.querySelectorAll('[data-open-cmdk]'), function (btn) { 190 btn.addEventListener('click', openDialog); 191 }); 192 193 var isMac = /Mac|iPhone|iPad/.test(navigator.platform || ''); 194 Array.prototype.forEach.call(document.querySelectorAll('.cmdk-shortcut'), function (el) { 195 el.textContent = isMac ? 'âK' : 'Ctrl K'; 196 }); 197 198 render(); 199})();
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.