A community-maintained directory of Bluesky Personal Data Servers (PDS).
pdslist.wisp.place
1// Load PDS data and populate table
2document.addEventListener('DOMContentLoaded', function() {
3 // Check if mobile and show popup
4 if (window.innerWidth < 768) {
5 document.getElementById('mobile-popup').style.display = 'flex';
6 }
7
8 // Close mobile popup button
9 document.getElementById('close-mobile-popup').addEventListener('click', function() {
10 document.getElementById('mobile-popup').style.display = 'none';
11 });
12
13 // Search input
14 document.getElementById('search-input').addEventListener('input', filterData);
15
16 // Invite filter radios
17 document.querySelectorAll('input[name="invite-filter"]').forEach(radio => {
18 radio.addEventListener('change', filterData);
19 });
20
21 loadPDSData();
22});
23
24// Store all PDS data globally for filtering
25let allPDSData = [];
26
27async function loadPDSData() {
28 const loading = document.getElementById('loading');
29 const content = document.getElementById('content');
30 const tableBody = document.getElementById('table-body');
31 const noData = document.getElementById('no-data');
32 const serverCount = document.getElementById('server-count');
33
34 try {
35 const response = await fetch('./pdslist.json');
36 const pdsData = await response.json();
37 allPDSData = pdsData; // Store for filtering
38
39 // Hide loading
40 loading.style.display = 'none';
41 content.style.display = 'block';
42
43 if (pdsData.length === 0) {
44 noData.style.display = 'block';
45 return;
46 }
47
48 // Update server count
49 serverCount.textContent = pdsData.length;
50
51 // Populate table
52 pdsData.forEach(pds => {
53 const row = createTableRow(pds);
54 tableBody.appendChild(row);
55 });
56
57 // Set initial filtered count to total
58 document.getElementById('filtered-count').textContent = pdsData.length;
59
60 } catch (error) {
61 console.error('Error loading PDS data:', error);
62 loading.innerHTML = '<p style="color: red;">Error loading database. Please check console.</p>';
63 }
64}
65
66function filterData() {
67 const searchTerm = document.getElementById('search-input').value.toLowerCase();
68 const inviteFilter = document.querySelector('input[name="invite-filter"]:checked').value;
69
70 const tableBody = document.getElementById('table-body');
71 const rows = tableBody.querySelectorAll('tr');
72 let visibleCount = 0;
73
74 rows.forEach((row, index) => {
75 const pds = allPDSData[index];
76 if (shouldShowRow(pds, searchTerm, inviteFilter)) {
77 row.style.display = '';
78 visibleCount++;
79 } else {
80 row.style.display = 'none';
81 }
82 });
83
84 // Update the filtered count
85 document.getElementById('filtered-count').textContent = visibleCount;
86}
87
88function shouldShowRow(pds, searchTerm, inviteFilter) {
89 // Apply search filter
90 const searchMatch = !searchTerm ||
91 pds.url.toLowerCase().includes(searchTerm) ||
92 (pds.supportedHandles && pds.supportedHandles.some(h => h.toLowerCase().includes(searchTerm))) ||
93 (pds.maintainer && pds.maintainer.toLowerCase().includes(searchTerm)) ||
94 (pds.contactEmail && pds.contactEmail.toLowerCase().includes(searchTerm));
95
96 // Apply invite filter
97 const inviteMatch = inviteFilter === 'all' ||
98 (inviteFilter === 'yes' && pds.inviteCodeRequired) ||
99 (inviteFilter === 'no' && !pds.inviteCodeRequired);
100
101 return searchMatch && inviteMatch;
102}
103
104function createTableRow(pds) {
105 const row = document.createElement('tr');
106
107 // URL
108 const urlCell = document.createElement('td');
109 const urlLink = document.createElement('p');
110 // sanitize URL by removing any '@' characters
111 const safeUrl = pds.url ? pds.url.replace(/@/g, '') : '';
112 urlLink.href = safeUrl;
113 urlLink.target = '_blank';
114 urlLink.textContent = safeUrl;
115 urlCell.appendChild(urlLink);
116 row.appendChild(urlCell);
117
118 // Handles
119 const handlesCell = document.createElement('td');
120 if (pds.supportedHandles && pds.supportedHandles.length > 0) {
121 handlesCell.textContent = pds.supportedHandles.join(', ');
122 } else {
123 handlesCell.textContent = 'N/A';
124 }
125 row.appendChild(handlesCell);
126
127 // Maintainer
128 const maintainerCell = document.createElement('td');
129 if (pds.maintainer) {
130 const link = document.createElement('a');
131 // sanitize maintainer by removing any '@' characters before using in URL and display
132 const safeMaintainer = pds.maintainer.replace(/@/g, '');
133 link.href = `https://madebydanny.uk/followonbsky.html?did=${encodeURIComponent(safeMaintainer)}`;
134 link.target = '_blank';
135 link.textContent = safeMaintainer;
136 maintainerCell.appendChild(link);
137 } else {
138 maintainerCell.textContent = '—';
139 }
140 row.appendChild(maintainerCell);
141
142 // Server Location
143 const locationCell = document.createElement('td');
144 if (pds.serverLocation) {
145 locationCell.textContent = pds.serverLocation;
146 } else {
147 locationCell.textContent = '—';
148 }
149 row.appendChild(locationCell);
150
151 // Contact Email
152 const emailCell = document.createElement('td');
153 if (pds.contactEmail) {
154 const link = document.createElement('a');
155 link.href = `mailto:${pds.contactEmail}`;
156 link.textContent = pds.contactEmail;
157 emailCell.appendChild(link);
158 } else {
159 emailCell.textContent = '—';
160 }
161 row.appendChild(emailCell);
162
163 // Invite Code Required
164 const inviteCell = document.createElement('td');
165 inviteCell.textContent = pds.inviteCodeRequired ? 'Yes' : 'No';
166 row.appendChild(inviteCell);
167
168 // Terms of Service
169 const tosCell = document.createElement('td');
170 if (pds.tosUrl) {
171 const link = document.createElement('a');
172 link.href = pds.tosUrl;
173 link.target = '_blank';
174 link.rel = 'noopener noreferrer';
175 link.textContent = 'Link';
176 tosCell.appendChild(link);
177 } else {
178 tosCell.textContent = '—';
179 }
180 row.appendChild(tosCell);
181
182 // Privacy Policy
183 const privacyCell = document.createElement('td');
184 if (pds.privacyUrl) {
185 const link = document.createElement('a');
186 link.href = pds.privacyUrl;
187 link.target = '_blank';
188 link.rel = 'noopener noreferrer';
189 link.textContent = 'Link';
190 privacyCell.appendChild(link);
191 } else {
192 privacyCell.textContent = '—';
193 }
194 row.appendChild(privacyCell);
195
196 return row;
197}