Thread viewer for Bluesky
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>