Thread viewer for Bluesky
at master 156 lines 3.1 kB view raw
1<script lang="ts"> 2 import { type PostingStatsResult } from "../services/posting_stats"; 3 4 export interface TableOptions { 5 showReposts?: boolean, 6 showPercentages?: boolean, 7 showTotal?: boolean 8 }; 9 10 type Props = PostingStatsResult & TableOptions; 11 12 let { users, sums, daysBack, showReposts = true, showPercentages = true, showTotal = true }: Props = $props(); 13 14 function format(value: number): string { 15 return (value > 0) ? value.toFixed(1) : '–'; 16 } 17</script> 18 19<table class="scan-result"> 20 <thead> 21 <tr> 22 <th>#</th> 23 <th>Handle</th> 24 25 {#if showReposts} 26 <th>All posts /d</th> 27 <th>Own posts /d</th> 28 <th>Reposts /d</th> 29 {:else} 30 <th>Posts /d</th> 31 {/if} 32 33 {#if showPercentages} 34 <th>% of timeline</th> 35 {/if} 36 </tr> 37 </thead> 38 <tbody> 39 {#if showTotal} 40 <tr class="total"> 41 <td class="no"></td> 42 <td class="handle">Total:</td> 43 44 {#if showReposts} 45 <td>{format(sums.all / daysBack)}</td> 46 {/if} 47 48 <td>{format(sums.own / daysBack)}</td> 49 50 {#if showReposts} 51 <td>{format(sums.reposts / daysBack)}</td> 52 {/if} 53 54 {#if showPercentages} 55 <td class="percent"></td> 56 {/if} 57 </tr> 58 {/if} 59 60 {#each users as user, i} 61 <tr> 62 <td class="no">{i + 1}</td> 63 <td class="handle"> 64 <img class="avatar" alt="Avatar" src="{user.avatar}"> 65 <a href="https://bsky.app/profile/{user.handle}" target="_blank">{user.handle}</a> 66 </td> 67 68 {#if showReposts} 69 <td>{format(user.all / daysBack)}</td> 70 {/if} 71 72 <td>{format(user.own / daysBack)}</td> 73 74 {#if showReposts} 75 <td>{format(user.reposts / daysBack)}</td> 76 {/if} 77 78 {#if showPercentages} 79 <td class="percent">{format(user.all * 100 / sums.all)}%</td> 80 {/if} 81 </tr> 82 {/each} 83 </tbody> 84</table> 85 86<style> 87 .scan-result { 88 border: 1px solid #333; 89 border-collapse: collapse; 90 } 91 92 td, th { 93 border: 1px solid #333; 94 } 95 96 td { 97 text-align: right; 98 padding: 5px 8px; 99 } 100 101 th { 102 text-align: center; 103 background-color: hsl(207, 100%, 86%); 104 padding: 7px 10px; 105 } 106 107 td.handle { 108 text-align: left; 109 max-width: 450px; 110 overflow: hidden; 111 text-overflow: ellipsis; 112 white-space: nowrap; 113 } 114 115 tr.total td { 116 font-weight: bold; 117 font-size: 11pt; 118 background-color: hsla(207, 100%, 86%, 0.4); 119 } 120 121 tr.total td.handle { 122 text-align: left; 123 padding: 10px 12px; 124 } 125 126 .avatar { 127 width: 24px; 128 height: 24px; 129 border-radius: 14px; 130 vertical-align: middle; 131 margin-right: 2px; 132 padding: 2px; 133 } 134 135 td.no { 136 font-weight: bold; 137 } 138 139 td.percent { 140 min-width: 70px; 141 } 142 143 @media (prefers-color-scheme: dark) { 144 .scan-result, td, th { 145 border-color: #888; 146 } 147 148 th { 149 background-color: hsl(207, 90%, 25%); 150 } 151 152 tr.total td { 153 background-color: hsla(207, 90%, 25%, 0.4); 154 } 155 } 156</style>