// The code viewer (/code/, web-server-guide § The code viewer): the page's one script.//// Loaded in <head>, not deferred, so the theme is set before the first paint: the stored// choice, else the system's. The rest waits for the DOM — the tree filter, and on a// phone the Files drawer. Everything else on the page (file links, #L<n> line links, the// highlighted line) is plain HTML and CSS and works without it.(function(){'use strict';varroot=document.documentElement;root.classList.add('js');// ── theme ────────────────────────────────────────────────────────────────────────// The key is the main site's (site.js), so one choice holds on both. localStorage may// be absent or throw (a private window, blocked site data): then the toggle still// works, it just is not remembered.varKEY='euler-theme';functionstored(){try{returnwindow.localStorage.getItem(KEY);}catch(e){returnnull;}}functionstore(value){try{window.localStorage.setItem(KEY,value);}catch(e){/* not kept */}}vardark=window.matchMedia?window.matchMedia('(prefers-color-scheme: dark)'):null;functionapply(theme){root.setAttribute('data-theme',theme);}apply(stored()||(dark&&dark.matches?'dark':'light'));if(dark&&dark.addEventListener){dark.addEventListener('change',function(e){if(!stored()){apply(e.matches?'dark':'light');}});}document.addEventListener('DOMContentLoaded',function(){vartoggle=document.querySelector('.theme-toggle');functionlabel(){varisDark=root.getAttribute('data-theme')==='dark';toggle.setAttribute('aria-label',isDark?'Switch to light theme':'Switch to dark theme');}label();toggle.addEventListener('click',function(){varnext=root.getAttribute('data-theme')==='dark'?'light':'dark';apply(next);store(next);label();});varbody=document.body;varfiles=document.getElementById('files');vartree=files.querySelector('.tree');varfilter=document.getElementById('filter');varcount=files.querySelector('.filter-count');varopener=document.querySelector('.files-toggle');varcurrent=tree.querySelector('a[aria-current]');// The open file, centred in the tree's own scroll box (never the page's).functionreveal(){if(!current){return;}varbox=files.getBoundingClientRect();files.scrollTop+=current.getBoundingClientRect().top-box.top-files.clientHeight/2;}reveal();// ── the drawer (phone width only: CSS decides whether it is one) ────────────────functionsetOpen(open){body.classList.toggle('files-open',open);opener.setAttribute('aria-expanded',String(open));if(open){reveal();filter.focus();}else{opener.focus();}}opener.addEventListener('click',function(){setOpen(true);});files.querySelector('.files-close').addEventListener('click',function(){setOpen(false);});document.querySelector('.scrim').addEventListener('click',function(){setOpen(false);});files.addEventListener('keydown',function(e){if(e.key==='Escape'&&body.classList.contains('files-open')){setOpen(false);}});// ── the filter ───────────────────────────────────────────────────────────────────// A query matches a file's name or its summary; one with a `/` in it matches the// whole path. Matching files show with their folders opened; everything else hides.// Cleared, the tree goes back to how it was.varlinks=Array.prototype.slice.call(tree.querySelectorAll('a[data-path]'));varfolders=Array.prototype.slice.call(tree.querySelectorAll('details'));varwasOpen=folders.map(function(d){returnd.open;});varrows=Array.prototype.slice.call(tree.querySelectorAll('li'));functionname(a){varpath=a.getAttribute('data-path');returnpath.slice(path.lastIndexOf('/')+1);}functionmark(a,q){vartext=name(a);varat=q&&q.indexOf('/')<0?text.toLowerCase().indexOf(q):-1;a.textContent='';if(at<0){a.textContent=text;return;}// One span for the whole name: the row is a flex box, and loose pieces would each// be an item of their own, spaced apart by its gap.varlabel=document.createElement('span');varhit=document.createElement('mark');hit.textContent=text.slice(at,at+q.length);label.append(text.slice(0,at),hit,text.slice(at+q.length));a.append(label);}functionrun(){varq=filter.value.trim().toLowerCase();if(!q){rows.forEach(function(li){li.hidden=false;});folders.forEach(function(d,i){d.open=wasOpen[i];});links.forEach(function(a){mark(a,'');});count.hidden=true;return;}rows.forEach(function(li){li.hidden=true;});varmatches=0;links.forEach(function(a){varhay=q.indexOf('/')>=0?a.getAttribute('data-path'):name(a)+'\n'+(a.title||'');varhit=hay.toLowerCase().indexOf(q)>=0;mark(a,hit?q:'');if(!hit){return;}matches+=1;for(varel=a.parentElement;el&&el!==tree;el=el.parentElement){if(el.tagName==='LI'){el.hidden=false;}if(el.tagName==='DETAILS'){el.open=true;}}});count.textContent=matches===0?'No files match':matches+(matches===1?' file matches':' files match');count.hidden=false;}filter.addEventListener('input',run);// Enter opens the first match, so a name typed in full is one keystroke from its file.filter.addEventListener('keydown',function(e){if(e.key!=='Enter'||!filter.value.trim()){return;}varfirst=links.filter(function(a){return!a.closest('li[hidden]');})[0];if(first){e.preventDefault();window.location.href=first.href;}});});})();