this repo has no description
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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
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">“' + esc(a.target.selector.exact) + '”</blockquote>';
64 }
65 html += '<div class="annotation-body">' + esc(a.body.value) + '</div>';
66 html += '<div class="card-meta">' + esc(a.target.title || domain) + ' · ' + 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>