Thread viewer for Bluesky

Compare changes

Choose any two refs to compare.

+11 -1
index.html
··· 49 49 50 50 <li><a href="#" data-action="login">Log in</a></li> 51 51 <li><a href="#" data-action="logout">Log out</a></li> 52 + 53 + <li class="link"><a href="?">Home</a></li> 54 + <li class="link"><a href="?page=posting_stats">Posting stats</a></li> 55 + <li class="link"><a href="?page=like_stats">Like stats</a></li> 56 + <li class="link"><a href="?page=search">Timeline search</a></li> 57 + <li class="link"><a href="?page=search&mode=likes">Archive search</a></li> 52 58 </ul> 53 59 </div> 54 60 ··· 164 170 </div> 165 171 166 172 <div id="private_search_page"> 167 - <h2>Archive search *Beta*</h2> 173 + <h2>Archive search</h2> 168 174 169 175 <div class="timeline-search"> 170 176 <form> ··· 195 201 196 202 <div class="lycan-import"> 197 203 <form> 204 + <h4>Data not imported yet</h4> 205 + 198 206 <p> 199 207 In order to search within your likes and bookmarks, the posts you've liked or saved need to be imported into a database. 200 208 This is a one-time process, but it can take several minutes or more, depending on the age of your account. ··· 209 217 </form> 210 218 211 219 <div class="import-progress"> 220 + <h4>Import in progress</h4> 221 + 212 222 <p class="import-status"></p> 213 223 <p><progress></progress> <output></output></p> 214 224 </div>
+8 -1
menu.js
··· 11 11 12 12 html.addEventListener('click', (e) => { 13 13 this.menuElement.style.visibility = 'hidden'; 14 + this.icon.classList.remove('active'); 14 15 }); 16 + 17 + let homeLink = $(this.menuElement.querySelector('a[href="?"]'), HTMLLinkElement); 18 + homeLink.href = location.origin + location.pathname; 15 19 16 20 this.icon.addEventListener('click', (e) => { 17 21 e.stopPropagation(); ··· 66 70 } 67 71 68 72 toggleAccountMenu() { 69 - this.menuElement.style.visibility = (this.menuElement.style.visibility == 'visible') ? 'hidden' : 'visible'; 73 + let isVisible = (this.menuElement.style.visibility == 'visible'); 74 + 75 + this.menuElement.style.visibility = isVisible ? 'hidden' : 'visible'; 76 + this.icon.classList.toggle('active', !isVisible); 70 77 } 71 78 72 79 /** @param {string} buttonName */
+22 -8
private_search_page.js
··· 12 12 constructor() { 13 13 this.pageElement = $id('private_search_page'); 14 14 15 + this.header = $(this.pageElement.querySelector('h2')); 16 + 15 17 this.rangeInput = $(this.pageElement.querySelector('input[type="range"]'), HTMLInputElement); 16 18 this.submitButton = $(this.pageElement.querySelector('input[type="submit"]'), HTMLInputElement); 17 19 this.progressBar = $(this.pageElement.querySelector('input[type="submit"] + progress'), HTMLProgressElement); ··· 40 42 41 43 let params = new URLSearchParams(location.search); 42 44 this.mode = params.get('mode'); 43 - this.lycanMode = params.get('lycan'); 45 + let lycan = params.get('lycan'); 44 46 45 - if (this.lycanMode == 'local') { 47 + if (lycan == 'local') { 46 48 this.localLycan = new BlueskyAPI('http://localhost:3000', false); 49 + } else if (lycan) { 50 + this.lycanAddress = `did:web:${lycan}#lycan`; 51 + } else { 52 + this.lycanAddress = 'did:web:lycan.feeds.blue#lycan'; 47 53 } 48 54 } 49 55 ··· 94 100 this.pageElement.style.display = 'block'; 95 101 96 102 if (this.mode == 'likes') { 103 + this.header.innerText = 'Archive search'; 97 104 this.timelineSearch.style.display = 'none'; 98 105 this.searchCollections.style.display = 'block'; 99 106 this.searchLine.style.display = 'block'; 100 107 this.lycanImportSection.style.display = 'none'; 101 108 this.checkLycanImportStatus(); 102 109 } else { 110 + this.header.innerText = 'Timeline search'; 103 111 this.timelineSearch.style.display = 'block'; 104 112 this.searchCollections.style.display = 'none'; 105 113 this.lycanImportSection.style.display = 'none'; ··· 132 140 return await this.localLycan.getRequest('blue.feeds.lycan.getImportStatus', { user: accountAPI.user.did }); 133 141 } else { 134 142 return await accountAPI.getRequest('blue.feeds.lycan.getImportStatus', null, { 135 - headers: { 'atproto-proxy': 'did:web:lycan.feeds.blue#lycan' } 143 + headers: { 'atproto-proxy': this.lycanAddress } 136 144 }); 137 145 } 138 146 } ··· 153 161 this.lycanImportSection.style.display = 'block'; 154 162 this.lycanImportForm.style.display = 'block'; 155 163 this.importProgress.style.display = 'none'; 164 + this.searchField.disabled = true; 156 165 157 166 this.stopImportTimer(); 158 167 } else if (info.status == 'in_progress' || info.status == 'scheduled' || info.status == 'requested') { 159 168 this.lycanImportSection.style.display = 'block'; 160 169 this.lycanImportForm.style.display = 'none'; 161 170 this.importProgress.style.display = 'block'; 171 + this.searchField.disabled = true; 162 172 163 173 this.showImportProgress(info); 164 174 this.startImportTimer(); 165 175 } else if (info.status == 'finished') { 166 176 this.lycanImportForm.style.display = 'none'; 167 177 this.importProgress.style.display = 'block'; 178 + this.searchField.disabled = false; 168 179 169 180 this.showImportProgress({ status: 'finished', progress: 1.0 }); 170 181 this.stopImportTimer(); ··· 188 199 this.importStatusLabel.innerText = `Import complete โœ“`; 189 200 } else if (info.position) { 190 201 let date = new Date(info.position).toLocaleString(window.dateLocale, { day: 'numeric', month: 'short', year: 'numeric' }); 191 - this.importStatusLabel.innerText = `Imported data until: ${date}`; 202 + this.importStatusLabel.innerText = `Downloaded data until: ${date}`; 192 203 } else if (info.status == 'requested') { 193 204 this.importStatusLabel.innerText = 'Requesting importโ€ฆ'; 194 205 } else { ··· 202 213 this.lycanImportSection.style.display = 'block'; 203 214 this.lycanImportForm.style.display = 'none'; 204 215 this.importProgress.style.display = 'block'; 216 + this.searchField.disabled = true; 205 217 206 218 this.importStatusLabel.innerText = message; 207 219 this.stopImportTimer(); ··· 236 248 }); 237 249 } else { 238 250 await accountAPI.postRequest('blue.feeds.lycan.startImport', null, { 239 - headers: { 'atproto-proxy': 'did:web:lycan.feeds.blue#lycan' } 251 + headers: { 'atproto-proxy': this.lycanAddress } 240 252 }); 241 253 } 242 254 ··· 285 297 daysBack = 0; 286 298 } 287 299 288 - this.timelinePosts = timeline.map(x => Post.parseFeedPost(x)); 300 + this.timelinePosts = timeline; 289 301 290 302 this.archiveStatus.innerText = "Timeline archive fetched: " + ((daysBack == 1) ? '1 day' : `${daysBack} days`); 291 303 this.searchLine.style.display = 'block'; ··· 304 316 return; 305 317 } 306 318 307 - let matching = this.timelinePosts.filter(x => x.lowercaseText.includes(query)); 319 + let matching = this.timelinePosts 320 + .filter(x => x.post.record.text.toLowerCase().includes(query)) 321 + .map(x => Post.parseFeedPost(x)); 308 322 309 323 for (let post of matching) { 310 324 let postView = new PostComponent(post, 'feed').buildElement(); ··· 348 362 if (cursor) params.cursor = cursor; 349 363 350 364 response = await accountAPI.getRequest('blue.feeds.lycan.searchPosts', params, { 351 - headers: { 'atproto-proxy': 'did:web:lycan.feeds.blue#lycan' } 365 + headers: { 'atproto-proxy': this.lycanAddress } 352 366 }); 353 367 } 354 368
+24 -4
style.css
··· 127 127 padding: 6px 11px; 128 128 } 129 129 130 - #account_menu li a { 130 + #account_menu li a[data-action] { 131 131 display: inline-block; 132 132 color: #333; 133 133 font-size: 11pt; ··· 138 138 background-color: hsla(210, 100%, 4%, 0.12); 139 139 } 140 140 141 - #account_menu li a:hover { 141 + #account_menu li a[data-action]:hover { 142 142 background-color: hsla(210, 100%, 4%, 0.2); 143 143 text-decoration: none; 144 + } 145 + 146 + #account_menu li:not(.link) + li.link { 147 + margin-top: 16px; 148 + padding-top: 10px; 149 + border-top: 1px solid #ccc; 150 + } 151 + 152 + #account_menu li.link { 153 + margin-top: 8px; 154 + margin-left: 2px; 155 + } 156 + 157 + #account_menu li.link a { 158 + font-size: 11pt; 159 + color: #333; 144 160 } 145 161 146 162 #account_menu li .check { ··· 1142 1158 background-color: transparent; 1143 1159 } 1144 1160 1161 + #account.active { 1162 + color: #333; 1163 + } 1164 + 1145 1165 #account_menu { 1146 1166 background: hsl(210, 33.33%, 94.0%); 1147 1167 border-color: #ccc; 1148 1168 } 1149 1169 1150 - #account_menu li a { 1170 + #account_menu li a[data-action] { 1151 1171 color: #333; 1152 1172 border-color: #bbb; 1153 1173 background-color: hsla(210, 100%, 4%, 0.12); 1154 1174 } 1155 1175 1156 - #account_menu li a:hover { 1176 + #account_menu li a[data-action]:hover { 1157 1177 background-color: hsla(210, 100%, 4%, 0.2); 1158 1178 } 1159 1179