Thread viewer for Bluesky
at master 101 lines 2.6 kB view raw
1<script lang="ts"> 2 import PostComponent from '../components/posts/PostComponent.svelte'; 3 import SearchPage from './SearchPage.svelte'; 4 import { Post } from '../models/posts'; 5 import { TimelineSearch } from '../services/timeline_search.js'; 6 import { numberOfDays } from '../utils.js'; 7 8 let timeRangeDays = $state(7); 9 let progressMax: number | undefined = $state(); 10 let progress: number | undefined = $state(); 11 let fetchInProgress = $derived(progress !== undefined); 12 let daysFetched: number | undefined = $state(); 13 14 let query = $state(''); 15 let results: Post[] = $state([]); 16 17 let timelineSearch = new TimelineSearch(); 18 19 async function startScan(e: Event) { 20 e.preventDefault(); 21 22 try { 23 if (!fetchInProgress) { 24 progressMax = timeRangeDays; 25 progress = 0; 26 27 await timelineSearch.fetchTimeline(timeRangeDays, (p) => { progress = p }); 28 29 daysFetched = progress; 30 progress = undefined; 31 } else { 32 progress = undefined; 33 timelineSearch.abortFetch(); 34 } 35 } catch (error) { 36 if (error.name !== 'AbortError') { 37 throw error; 38 } 39 } 40 } 41 42 function onKeyPress(e: KeyboardEvent) { 43 if (e.key == 'Enter') { 44 e.preventDefault(); 45 46 let q = query.trim().toLowerCase(); 47 results = timelineSearch.searchPosts(q); 48 } 49 } 50</script> 51 52<SearchPage> 53 <h2>Timeline search</h2> 54 55 <div class="timeline-search"> 56 <form onsubmit={startScan}> 57 <p> 58 Fetch timeline posts: <input id="timeline_search_range" type="range" min="1" max="60" bind:value={timeRangeDays}> 59 <label for="timeline_search_range">{numberOfDays(timeRangeDays)}</label> 60 </p> 61 62 <p> 63 <input type="submit" value="{fetchInProgress ? 'Cancel' : 'Fetch timeline'}"> 64 65 {#if fetchInProgress} 66 <progress max={progressMax} value={progress}></progress> 67 {/if} 68 </p> 69 </form> 70 71 {#if daysFetched} 72 <p class="archive-status"> 73 Timeline archive fetched: {numberOfDays(Math.round(daysFetched))} 74 </p> 75 {/if} 76 77 <hr> 78 </div> 79 80 {#if daysFetched} 81 <form class="search-form"> 82 <p class="search"> 83 Search: 84 <input type="text" class="search-query" autocomplete="off" onkeydown={onKeyPress} bind:value={query}> 85 </p> 86 </form> 87 88 <div class="results"> 89 {#each results as post (post.uri)} 90 <PostComponent {post} placement="feed" /> 91 {/each} 92 </div> 93 {/if} 94</SearchPage> 95 96<style> 97 input[type="range"] { 98 width: 250px; 99 vertical-align: middle; 100 } 101</style>