Thread viewer for Bluesky

initial support for Lycan like search

+14 -12
index.html
··· 10 10 font-src 'self'; 11 11 script-src-attr 'none'; 12 12 style-src-attr 'none'; 13 - connect-src https:; 13 + connect-src https: http://localhost:3000; 14 14 base-uri 'none'; 15 15 form-action 'none';"> 16 16 ··· 166 166 <div id="private_search_page"> 167 167 <h2>Archive search *Beta*</h2> 168 168 169 - <form> 170 - <p> 171 - Fetch timeline posts: <input type="range" min="1" max="60" value="7"> <label>7 days</label> 172 - </p> 169 + <div class="timeline-search"> 170 + <form> 171 + <p> 172 + Fetch timeline posts: <input type="range" min="1" max="60" value="7"> <label>7 days</label> 173 + </p> 173 174 174 - <p> 175 - <input type="submit" value="Fetch timeline"> <progress></progress> 176 - </p> 177 - </form> 175 + <p> 176 + <input type="submit" value="Fetch timeline"> <progress></progress> 177 + </p> 178 + </form> 178 179 179 - <p class="archive-status"></p> 180 + <p class="archive-status"></p> 180 181 181 - <hr> 182 + <hr> 183 + </div> 182 184 183 - <p class="search">Search: <input type="text" class="search-query"></p> 185 + <p class="search">Search: <input type="text" class="search-query" autocomplete="off"></p> 184 186 185 187 <div class="results"> 186 188 </div>
+55 -6
private_search_page.js
··· 18 18 this.timelinePosts = []; 19 19 20 20 this.setupEvents(); 21 + 22 + let params = new URLSearchParams(location.search); 23 + this.mode = params.get('mode'); 24 + this.lycanMode = params.get('lycan'); 25 + 26 + if (this.lycanMode == 'local') { 27 + this.lycan = new BlueskyAPI('http://localhost:3000', false); 28 + } 21 29 } 22 30 23 31 setupEvents() { ··· 37 45 label.innerText = (days == 1) ? '1 day' : `${days} days`; 38 46 }); 39 47 40 - this.searchField.addEventListener('input', (e) => { 41 - let query = this.searchField.value.trim().toLowerCase(); 48 + this.searchField.addEventListener('keydown', (e) => { 49 + if (e.key == 'Enter') { 50 + e.preventDefault(); 42 51 43 - if (this.searchTimer) { 44 - clearTimeout(this.searchTimer); 52 + let query = this.searchField.value.trim().toLowerCase(); 53 + 54 + if (this.mode == 'likes') { 55 + this.searchInLycan(query); 56 + } else { 57 + this.searchInTimeline(query); 58 + } 45 59 } 46 - 47 - this.searchTimer = setTimeout(() => this.searchInTimeline(query), 100); 48 60 }); 49 61 } 50 62 ··· 56 68 57 69 show() { 58 70 this.pageElement.style.display = 'block'; 71 + 72 + if (this.mode == 'likes') { 73 + this.pageElement.querySelector('.timeline-search').style.display = 'none'; 74 + this.searchLine.style.display = 'block'; 75 + } else { 76 + this.pageElement.querySelector('.timeline-search').style.display = 'block'; 77 + } 59 78 } 60 79 61 80 /** @returns {Promise<void>} */ ··· 120 139 for (let post of matching) { 121 140 let postView = new PostComponent(post, 'feed').buildElement(); 122 141 this.results.appendChild(postView); 142 + } 143 + } 144 + 145 + /** @param {string} query */ 146 + 147 + async searchInLycan(query) { 148 + if (query.length == 0) { 149 + this.results.innerHTML = ''; 150 + return; 151 + } 152 + 153 + this.results.innerHTML = '...'; 154 + 155 + if (this.lycanMode == 'local') { 156 + let response = await this.lycan.getRequest('blue.feeds.lycan.searchPosts', { query: query, user: window.accountAPI.user.did }); 157 + 158 + if (response.posts.length == 0) { 159 + this.results.innerHTML = "No results."; 160 + return; 161 + } 162 + 163 + let records = await window.accountAPI.loadPosts(response.posts); 164 + let posts = records.map(x => new Post(x)); 165 + 166 + this.results.innerHTML = ''; 167 + 168 + for (let post of posts) { 169 + let postView = new PostComponent(post, 'feed').buildElement(); 170 + this.results.appendChild(postView); 171 + } 123 172 } 124 173 } 125 174
+4
style.css
··· 1036 1036 margin-left: 8px; 1037 1037 } 1038 1038 1039 + #private_search_page .results > .post { 1040 + padding-left: 0; 1041 + } 1042 + 1039 1043 @media (prefers-color-scheme: dark) { 1040 1044 body { 1041 1045 background-color: rgb(39, 39, 37);