this repo has no description
at main 100 lines 3.5 kB view raw
1--- 2import { DID } from '../../lib/atproto'; 3--- 4 5<div id="annotationsPanel" class="tab-panel"> 6 <div class="loading"> 7 <span class="loading-dot"></span> 8 <span class="loading-dot"></span> 9 <span class="loading-dot"></span> 10 </div> 11</div> 12 13<script is:inline define:vars={{ DID }}> 14(function() { 15 var panel = document.getElementById('annotationsPanel'); 16 17 function esc(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); } 18 19 function formatDate(d) { 20 return new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); 21 } 22 23 async function resolvePDS() { 24 var res = await fetch('https://plc.directory/' + DID); 25 var doc = await res.json(); 26 var svc = doc.service.find(function(s) { return s.type === 'AtprotoPersonalDataServer'; }); 27 return svc.serviceEndpoint; 28 } 29 30 async function listAllRecords(pds, collection) { 31 var records = [], cursor = null; 32 do { 33 var url = new URL(pds + '/xrpc/com.atproto.repo.listRecords'); 34 url.searchParams.set('repo', DID); 35 url.searchParams.set('collection', collection); 36 url.searchParams.set('limit', '100'); 37 if (cursor) url.searchParams.set('cursor', cursor); 38 var res = await fetch(url); 39 if (!res.ok) throw new Error('PDS error ' + res.status); 40 var data = await res.json(); 41 records = records.concat(data.records); 42 cursor = data.cursor || null; 43 } while (cursor); 44 return records; 45 } 46 47 async function load() { 48 try { 49 var pds = await resolvePDS(); 50 var records = await listAllRecords(pds, 'at.margin.annotation'); 51 var annotations = records.map(function(r) { return r.value; }); 52 annotations.sort(function(a, b) { return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); }); 53 54 if (!annotations.length) { panel.innerHTML = '<div class="empty-state">No annotations yet</div>'; return; } 55 56 panel.innerHTML = ''; 57 annotations.forEach(function(a) { 58 var domain = ''; 59 try { domain = new URL(a.target.source).hostname.replace('www.',''); } catch(e) {} 60 61 var html = ''; 62 if (a.target.selector && a.target.selector.exact) { 63 html += '<blockquote class="annotation-quote">&ldquo;' + esc(a.target.selector.exact) + '&rdquo;</blockquote>'; 64 } 65 html += '<div class="annotation-body">' + esc(a.body.value) + '</div>'; 66 html += '<div class="card-meta">' + esc(a.target.title || domain) + ' &middot; ' + formatDate(a.createdAt) + '</div>'; 67 68 var el = document.createElement('a'); 69 el.className = 'card annotation-card'; 70 el.href = a.target.source; 71 el.target = '_blank'; 72 el.rel = 'noopener'; 73 el.innerHTML = html; 74 panel.appendChild(el); 75 }); 76 } catch(e) { 77 panel.innerHTML = '<div class="error-state">Could not load annotations</div>'; 78 } 79 } 80 81 window.addEventListener('tab-switch', function(e) { 82 if (e.detail === 'annotations' && !panel.dataset.loaded) { 83 panel.dataset.loaded = '1'; 84 load(); 85 } 86 }); 87})(); 88</script> 89 90<style is:global> 91 .annotation-card { display: block; text-decoration: none; color: var(--text); } 92 .annotation-quote { 93 font-style: normal; color: var(--text-light); 94 border-left: 6px solid var(--green); 95 padding-left: 12px; margin-bottom: 8px; 96 font-size: 0.88rem; 97 font-weight: 700; 98 } 99 .annotation-body { font-size: 0.9rem; margin-bottom: 8px; } 100</style>