PageSourceSearch

https://shareplane.malott.ai/native/v3/assets/discovery.js

js malott.ai collected 2026-10-05 12:57:29 UTC 15,666 bytes, 63 lines download raw bytes

1(()=>{'use strict';
2 const works=window.SP_DATA||[],edges=window.SP_EDGES||[],byId=new Map(works.map(w=>[w.id,w])),bySlug=new Map(works.map(w=>[w.slug,w]));
3 const root=document.body.dataset.rootPrefix||'',one=s=>document.querySelector(s),all=s=>[...document.querySelectorAll(s)];
4 const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
5 const norm=s=>String(s||'').normalize('NFKD').replace(/[\u0300-\u036f]/g,'').toLowerCase();
6 const human=s=>String(s||'').replace(/[-_]/g,' ');
7 const route=w=>root+w.href;
8 function score(w,query){const terms=norm(query).split(/\s+/).filter(Boolean);if(!terms.length)return 1;const title=norm(w.title),meta=norm([w.thesis,w.abstract,...w.topics].join(' ')),body=norm(w.text);let total=0;for(const t of terms){if(title.includes(t))total+=12+(title.startsWith(t)?8:0);else if(meta.includes(t))total+=5;else if(body.includes(t))total+=1;else return 0;}return total+(title.includes(norm(query))?20:0);}
9 function results(q,items=works){return items.map(w=>({w,score:score(w,q)})).filter(x=>x.score>0).sort((a,b)=>b.score-a.score||b.w.date.localeCompare(a.w.date)).map(x=>x.w);}
10 function highlight(text,q){const terms=norm(q).split(/\s+/).filter(Boolean);if(!terms.length)return esc(text);let out='',i=0;const n=norm(text);while(i<text.length){const t=terms.find(t=>n.slice(i,i+t.length)===t);if(t){out+='<mark>'+esc(text.slice(i,i+t.length))+'</mark>';i+=t.length;}else out+=esc(text[i++]);}return out;}
11 function card(w,q=''){return '<a class="discovery-card" href="'+esc(route(w))+'"><div class="work-meta"><span>'+esc(w.kind)+'</span><span>'+esc(w.dateLabel)+' '+esc(w.date)+'</span></div><h2>'+highlight(w.title,q)+'</h2><p>'+highlight(w.thesis||w.abstract,q)+'</p><div class="work-topics">'+w.topics.slice(0,3).map(x=>esc(human(x))).join(' · ')+'</div></a>';}
12 const dialog=one('#sp-search-dialog'),input=one('#sp-search-input'),quick=one('#sp-search-results');let selected=0,lastFocus=null;
13 function quickRender(){const list=results(input.value).slice(0,9);selected=0;quick.innerHTML=list.length?list.map((w,i)=>'<a role="option" aria-selected="'+(i===0)+'" id="sp-result-'+i+'" href="'+esc(route(w))+'"><span>'+highlight(w.title,input.value)+'</span><small>'+esc(w.kind)+' · '+esc(w.date)+'</small></a>').join(''):'<p class="spx-search-empty">No Work matches that query. Try fewer words or a topic such as memory, authority, or design.</p>';input.setAttribute('aria-activedescendant',list.length?'sp-result-0':'');one('#sp-search-count').textContent=list.length?('Showing '+list.length+' of '+results(input.value).length+' matching Works'):'No results';}
14 function showSearch(){if(!dialog)return;lastFocus=document.activeElement;dialog.showModal();input.value='';quickRender();input.focus();}
15 all('[data-search-open]').forEach(b=>b.addEventListener('click',showSearch));
16 if(dialog){one('#sp-search-close').addEventListener('click',()=>dialog.close());dialog.addEventListener('close',()=>lastFocus?.focus());input.addEventListener('input',quickRender);input.addEventListener('keydown',e=>{const opts=[...quick.querySelectorAll('a')];if(!opts.length)return;if(e.key==='ArrowDown'||e.key==='ArrowUp'){e.preventDefault();selected=(selected+(e.key==='ArrowDown'?1:-1)+opts.length)%opts.length;opts.forEach((o,i)=>o.setAttribute('aria-selected',String(i===selected)));input.setAttribute('aria-activedescendant',opts[selected].id);opts[selected].scrollIntoView({block:'nearest'});}if(e.key==='Enter'){e.preventDefault();location.href=opts[selected].href;}});dialog.addEventListener('click',e=>{if(e.target===dialog)dialog.close();});}
17 document.addEventListener('keydown',e=>{const typing=/^(INPUT|TEXTAREA|SELECT)$/.test(document.activeElement?.tagName)||document.activeElement?.isContentEditable;if((e.key.toLowerCase()==='k'&&(e.metaKey||e.ctrlKey))||(e.key==='/'&&!typing)){e.preventDefault();showSearch();}});
18 all('[data-offline-full]').forEach(a=>a.addEventListener('click',e=>{if(document.documentElement.dataset.offlinePackage){e.preventDefault();const note=one('#sp-package-notice');if(note){note.showModal();note.querySelector('button').focus();}}}));
19 one('#sp-package-close')?.addEventListener('click',()=>one('#sp-package-notice').close());
20 const search=one('#discover-query'),list=one('#discover-results');let topic='',sort='newest',view='cards';
21 if(search&&list){
22  const counts=new Map();works.forEach(w=>w.topics.forEach(t=>counts.set(t,(counts.get(t)||0)+1)));const top=[...counts].sort((a,b)=>b[1]-a[1]).slice(0,8);
23  one('#discover-facets').innerHTML='<button type="button" data-topic="" aria-pressed="true">All topics</button>'+top.map(([t,n])=>'<button type="button" data-topic="'+esc(t)+'" aria-pressed="false">'+esc(human(t))+' <span>'+n+'</span></button>').join('');
24  function render(){let found=results(search.value,works.filter(w=>!topic||w.topics.includes(topic)));if(sort==='title')found.sort((a,b)=>a.title.localeCompare(b.title));if(!search.value&&sort==='newest')found.sort((a,b)=>b.date.localeCompare(a.date));list.innerHTML=found.length?found.map(w=>card(w,search.value)).join(''):'<div class="discovery-empty"><h2>No matches in this view.</h2><p>Clear the topic or try a shorter query. Search includes the complete local text, as well as titles, theses, and topics.</p><button class="button" type="button" id="clear-all-search">Clear search and filters</button></div>';list.dataset.view=view;one('#discover-count').textContent=found.length+' '+(found.length===1?'Work':'Works');one('#clear-all-search')?.addEventListener('click',()=>{search.value='';topic='';all('[data-topic]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.topic==='')));render();});}
25  search.addEventListener('input',render);one('#discover-clear').addEventListener('click',()=>{search.value='';search.focus();render();});
26  all('[data-topic]').forEach(b=>b.addEventListener('click',()=>{topic=b.dataset.topic;all('[data-topic]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));render();}));
27  all('[data-sort]').forEach(b=>b.addEventListener('click',()=>{sort=b.dataset.sort;all('[data-sort]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));render();}));
28  all('[data-library-view]').forEach(b=>b.addEventListener('click',()=>{view=b.dataset.libraryView;
28all('[data-library-view]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));render();}));render();
29 }
30 const released=works.filter(w=>w.published).sort((a,b)=>b.date.localeCompare(a.date));
31 all('[data-latest-list]').forEach(el=>{el.innerHTML=released.slice(0,Number(el.dataset.latestList)||released.length).map(w=>card(w)).join('');});
32 all('[data-latest-count]').forEach(el=>el.textContent=released.length+' dated releases in this snapshot');
33 all('[data-work-link]').forEach(a=>{const w=bySlug.get(a.dataset.workLink);if(w)a.href=route(w);else a.removeAttribute('href');});
34 const picker=one('#atlas-query'),workList=one('#atlas-work-list');if(!picker||!workList)return;
35 const validEdges=edges.filter(e=>byId.has(e.source)&&byId.has(e.target));const degree=new Map(works.map(w=>[w.id,validEdges.filter(e=>e.source===w.id||e.target===w.id).length]));
36 let chosen=works.find(w=>w.slug==='taste-is-infrastructure')||[...works].sort((a,b)=>degree.get(b.id)-degree.get(a.id))[0],mode='constellation';
37 const canvas=one('#atlas-map'),ctx=canvas.getContext('2d');let scale=1,panX=0,panY=0,drag=null,drawWidth=0,drawHeight=510;
38 const ordered=[...works].sort((a,b)=>degree.get(b.id)-degree.get(a.id)||a.title.localeCompare(b.title));
39 const positions=new Map();ordered.forEach((w,i)=>{const connected=degree.get(w.id)>0;const angle=i*2.399963229728653;const radius=connected?70+Math.sqrt(i)*32:305+(i%3)*16;positions.set(w.id,{x:Math.cos(angle)*radius,y:Math.sin(angle)*radius*.67});});
40 // Deterministic relaxation follows declared edges only. Unconnected Works remain visible.
41 for(let step=0;step<95;step++){for(const edge of validEdges){const a=positions.get(edge.source),b=positions.get(edge.target),dx=b.x-a.x,dy=b.y-a.y,d=Math.max(1,Math.hypot(dx,dy)),f=(d-125)*.007;a.x+=dx/d*f;a.y+=dy/d*f;b.x-=dx/d*f;b.y-=dy/d*f;}for(let i=0;i<ordered.length;i++){const a=positions.get(ordered[i].id);for(let j=i+1;j<ordered.length;j++){const b=positions.get(ordered[j].id),dx=b.x-a.x,dy=b.y-a.y,d=Math.max(1,Math.hypot(dx,dy));if(d<53){const f=(53-d)*.06;a.x-=dx/d*f;a.y-=dy/d*f;b.x+=dx/d*f;b.y+=dy/d*f;}}}}
42 function relatives(){return validEdges.filter(e=>e.source===chosen.id||e.target===chosen.id);}
43 function edgeCard(e){const outgoing=e.source===chosen.id,target=byId.get(outgoing?e.target:e.source);return '<article class="connection-item"><div class="edge-direction">'+(outgoing?'Outgoing':'Incoming')+' · '+esc(human(e.group||e.type))+'</div><div><h3><a href="'+esc(route(target))+'">'+esc(target.title)+'</a></h3><button class="spx-small-action" type="button" data-graph-work="'+esc(target.slug)+'">Explore its connections</button></div><p>'+esc(e.explanation||e.label||'This relationship is declared in the source record; no fuller explanation was provided.')+'<span class="edge-type">'+esc(human(e.type))+'</span></p></article>';}
44 function pickerRender(){const found=results(picker.value);workList.innerHTML=found.map(w=>'<button type="button" data-graph-work="'+esc(w.slug)+'" aria-pressed="'+(w.id===chosen.id)+'">'+esc(w.title)+'<small>'+degree.get(w.id)+' declared '+(degree.get(w.id)===1?'connection':'connections')+'</small></button>').join('');}
45 function graphRender(){const rel=relatives();one('#atlas-detail').innerHTML='<div><p class="eyebrow">Selected Work</p><h2>'+esc(chosen.title)+'</h2><p>'+esc(chosen.thesis||chosen.abstract)+'</p><div class="actions"><a class="button primary" href="'+esc(route(chosen))+'">Read the Work</a><a class="text-link" href="'+esc(root+chosen.contextHref)+'">Full text</a></div></div><div><p>'+rel.length+' declared '+(rel.length===1?'relationship':'relationships')+' touch this Work.</p><p class="source-note">'+esc(chosen.kind)+' · '+esc(chosen.dateLabel)+' '+esc(chosen.date)+'. Connections retain the direction and meaning in their source records. Topic membership does not create an edge.</p></div>';
46  const empty='<p class="source-note">No Work-to-Work edge is declared for this Work in the included records. It remains discoverable through search and topics.</p>';
47  one('#atlas-connections').innerHTML=rel.length?rel.map(edgeCard).join(''):empty;
48  one('#atlas-pathways').innerHTML='<div class="pathway-center"><p class="eyebrow">Current argument</p><h2>'+esc(chosen.title)+'</h2><p>'+esc(chosen.thesis||chosen.abstract)+'</p></div><div class="connection-list">'+(rel.length?rel.map(edgeCard).join(''):empty)+'</div>';
49  one('#atlas-ledger').innerHTML='<p class="source-note">Source and target direction are preserved. Select another Work to inspect the same relationship from its other end.</p><div class="connection-ledger">'+(rel.length?rel.map(edgeCard).join(''):empty)+'</div>';
50  pickerRender();draw();
51 }
52 function choose(slug){const w=bySlug.get(slug);if(!w)return;chosen=w;graphRender();history.replaceState(null,'','#graph?work='+encodeURIComponent(slug));} function fromHash(){const q=new URLSearchParams(location.hash.split('?')[1]||'');if(q.get('work')&&bySlug.has(q.get('work'))){chosen=bySlug.get(q.get('work'));graphRender();}} window.addEventListener('hashchange',fromHash);
53 document.addEventListener('click',e=>{const b=e.target.closest('[data-graph-work]');if(b)choose(b.dataset.graphWork);});picker.addEventListener('input',pickerRender);
54 all('[data-graph-mode]').forEach(b=>b.addEventListener('click',()=>{mode=b.dataset.graphMode;all('[data-graph-mode]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));all('.atlas-panel').forEach(p=>p.hidden=p.dataset.graphPanel!==mode);one('#atlas-detail').hidden=mode!=='constellation';if(mode==='constellation')resize();}));
55 function point(id){const p=positions.get(id);return {x:drawWidth/2+p.x*scale+panX,y:drawHeight/2+p.y*scale+panY};}
56 function draw(){if(!drawWidth)return;ctx.clearRect(0,0,drawWidth,drawHeight);ctx.fillStyle='#0f1b19';ctx.fillRect(0,0,drawWidth,drawHeight);const rel=relatives(),near=new Set([chosen.id,...rel.flatMap(e=>[e.source,e.target])]);for(const edge of validEdges){const a=point(edge.source),b=point(edge.target),active=edge.source===ch
56osen.id||edge.target===chosen.id;ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.quadraticCurveTo((a.x+b.x)/2+12,(a.y+b.y)/2-15,b.x,b.y);ctx.strokeStyle=active?(edge.source===chosen.id?'#dca27e':'#8dbfa9'):'#304941';ctx.lineWidth=active?1.6:0.7;ctx.stroke();if(active){const angle=Math.atan2(b.y-a.y,b.x-a.x);ctx.beginPath();ctx.moveTo(b.x-Math.cos(angle)*10,b.y-Math.sin(angle)*10);ctx.lineTo(b.x-Math.cos(angle)*16+Math.sin(angle)*3,b.y-Math.sin(angle)*16-Math.cos(angle)*3);ctx.lineTo(b.x-Math.cos(angle)*16-Math.sin(angle)*3,b.y-Math.sin(angle)*16+Math.cos(angle)*3);ctx.closePath();ctx.fillStyle=ctx.strokeStyle;ctx.fill();}}
57  const labels=[];for(const w of [...ordered].sort((a,b)=>(b.id===chosen.id)-(a.id===chosen.id))){const p=point(w.id),active=w.id===chosen.id,neighbor=near.has(w.id);ctx.beginPath();ctx.arc(p.x,p.y,active?9:neighbor?6:3.5,0,Math.PI*2);ctx.fillStyle=active?'#edbd8f':neighbor?'#a0c7b5':'#65877a';ctx.fill();if(active){ctx.beginPath();ctx.arc(p.x,p.y,15,0,Math.PI*2);ctx.strokeStyle='#9d7c56';ctx.lineWidth=1;ctx.stroke();}if(active||neighbor){ctx.font=(active?'600 ':'')+'12px Arial';const words=w.title.split(' ');const title=words.slice(0,6).join(' ')+(words.length>6?'…':'');const tw=ctx.measureText(title).width;let spot=null;for(const [dx,dy]of[[13,-12],[13,22],[-tw-13,-12],[-tw-13,22],[13,-35],[13,44]]){const x=Math.max(10,Math.min(drawWidth-tw-10,p.x+dx)),y=Math.max(65,Math.min(drawHeight-12,p.y+dy));const box={x:x-4,y:y-13,w:tw+8,h:20};if(!labels.some(r=>box.x<r.x+r.w&&box.x+box.w>r.x&&box.y<r.y+r.h&&box.y+box.h>r.y)){spot={x,y,box};break;}}if(spot){labels.push(spot.box);ctx.fillStyle='#0f1b19dc';ctx.fillRect(spot.box.x,spot.box.y,spot.box.w,spot.box.h);ctx.fillStyle='#e6ece3';ctx.fillText(title,spot.x,spot.y);}}}
58 }
59 function resize(){const rect=canvas.getBoundingClientRect();if(!rect.width)return;drawWidth=rect.width;drawHeight=rect.height;const ratio=window.devicePixelRatio||1;canvas.width=Math.round(drawWidth*ratio);canvas.height=Math.round(drawHeight*ratio);ctx.setTransform(ratio,0,0,ratio,0,0);if(drawWidth<550&&scale===1)scale=.7;draw();}
60 canvas.addEventListener('pointerdown',e=>{drag={x:e.clientX,y:e.clientY,px:panX,py:panY,moved:false};canvas.setPointerCapture(e.pointerId);});canvas.addEventListener('pointermove',e=>
60{if(!drag)return;const dx=e.clientX-drag.x,dy=e.clientY-drag.y;if(Math.hypot(dx,dy)>4)drag.moved=true;panX=drag.px+dx;panY=drag.py+dy;draw();});canvas.addEventListener('pointerup',e=>{if(drag&&!drag.moved){const r=canvas.getBoundingClientRect(),x=e.clientX-r.left,y=e.clientY-r.top;const hit=ordered.map(w=>({w,p:point(w.id)})).sort((a,b)=>Math.hypot(a.p.x-x,a.p.y-y)-Math.hypot(b.p.x-x,b.p.y-y))[0];if(hit&&Math.hypot(hit.p.x-x,hit.p.y-y)<18)choose(hit.w.slug);}drag=null;});canvas.addEventListener('pointercancel',()=>drag=null);
61 one('#atlas-zoom-in').addEventListener('click',()=>{scale=Math.min(2.5,scale*1.2);draw();});one('#atlas-zoom-out').addEventListener('click',()=>{scale=Math.max(.35,scale/1.2);draw();});one('#atlas-fit').addEventListener('click',()=>{scale=drawWidth<550?.65:1;panX=panY=0;draw();});new ResizeObserver(resize).observe(canvas);
62 one('#atlas-totals').textContent=works.length+' Works · '+validEdges.length+' declared directed edges · '+works.filter(w=>degree.get(w.id)===0).length+' without a recorded Work edge';graphRender();fromHash();resize();
63})();

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.