+24
index.html
+24
index.html
···
155
155
</table>
156
156
</div>
157
157
158
+
<div id="private_search_page">
159
+
<h2>Archive search *Beta*</h2>
160
+
161
+
<form>
162
+
<p>
163
+
Fetch timeline posts: <input type="range" min="1" max="60" value="7"> <label>7 days</label>
164
+
</p>
165
+
166
+
<p>
167
+
<input type="submit" value="Fetch timeline"> <progress></progress>
168
+
</p>
169
+
</form>
170
+
171
+
<p class="archive-status"></p>
172
+
173
+
<hr>
174
+
175
+
<p class="search">Search: <input type="text" class="search-query"></p>
176
+
177
+
<div class="results">
178
+
</div>
179
+
</div>
180
+
158
181
<script src="lib/purify.min.js"></script>
159
182
<script src="minisky.js"></script>
160
183
<script src="api.js"></script>
···
166
189
<script src="posting_stats_page.js"></script>
167
190
<script src="like_stats_page.js"></script>
168
191
<script src="notifications_page.js"></script>
192
+
<script src="private_search_page.js"></script>
169
193
<script src="embed_component.js"></script>
170
194
<script src="post_component.js"></script>
171
195
<script src="skythread.js"></script>
+9
models.js
+9
models.js
···
342
342
return this.record.text;
343
343
}
344
344
345
+
/** @returns {string} */
346
+
get lowercaseText() {
347
+
if (!this._lowercaseText) {
348
+
this._lowercaseText = this.record.text.toLowerCase();
349
+
}
350
+
351
+
return this._lowercaseText;
352
+
}
353
+
345
354
/** @returns {json} */
346
355
get facets() {
347
356
return this.record.facets;
+143
private_search_page.js
+143
private_search_page.js
···
1
+
class PrivateSearchPage {
2
+
3
+
/** @type {number | undefined} */
4
+
fetchStartTime;
5
+
6
+
constructor() {
7
+
this.pageElement = $id('private_search_page');
8
+
9
+
this.rangeInput = $(this.pageElement.querySelector('input[type="range"]'), HTMLInputElement);
10
+
this.submitButton = $(this.pageElement.querySelector('input[type="submit"]'), HTMLInputElement);
11
+
this.progressBar = $(this.pageElement.querySelector('input[type="submit"] + progress'), HTMLProgressElement);
12
+
this.archiveStatus = $(this.pageElement.querySelector('.archive-status'));
13
+
14
+
this.searchLine = $(this.pageElement.querySelector('.search'));
15
+
this.searchField = $(this.pageElement.querySelector('.search-query'), HTMLInputElement);
16
+
this.results = $(this.pageElement.querySelector('.results'));
17
+
18
+
this.timelinePosts = [];
19
+
20
+
this.setupEvents();
21
+
}
22
+
23
+
setupEvents() {
24
+
$(this.pageElement.querySelector('form')).addEventListener('submit', (e) => {
25
+
e.preventDefault();
26
+
27
+
if (!this.fetchStartTime) {
28
+
this.fetchTimeline();
29
+
} else {
30
+
this.stopFetch();
31
+
}
32
+
});
33
+
34
+
this.rangeInput.addEventListener('input', (e) => {
35
+
let days = parseInt(this.rangeInput.value, 10);
36
+
let label = $(this.pageElement.querySelector('input[type=range] + label'));
37
+
label.innerText = (days == 1) ? '1 day' : `${days} days`;
38
+
});
39
+
40
+
this.searchField.addEventListener('input', (e) => {
41
+
let query = this.searchField.value.trim().toLowerCase();
42
+
43
+
if (this.searchTimer) {
44
+
clearTimeout(this.searchTimer);
45
+
}
46
+
47
+
this.searchTimer = setTimeout(() => this.searchInTimeline(query), 100);
48
+
});
49
+
}
50
+
51
+
/** @returns {number} */
52
+
53
+
selectedDaysRange() {
54
+
return parseInt(this.rangeInput.value, 10);
55
+
}
56
+
57
+
show() {
58
+
this.pageElement.style.display = 'block';
59
+
}
60
+
61
+
/** @returns {Promise<void>} */
62
+
63
+
async fetchTimeline() {
64
+
this.submitButton.value = 'Cancel';
65
+
66
+
let requestedDays = this.selectedDaysRange();
67
+
68
+
this.progressBar.max = requestedDays;
69
+
this.progressBar.value = 0;
70
+
this.progressBar.style.display = 'inline';
71
+
72
+
let startTime = new Date().getTime();
73
+
this.fetchStartTime = startTime;
74
+
75
+
let timeline = await accountAPI.loadTimeline(requestedDays, {
76
+
onPageLoad: (data) => {
77
+
if (this.fetchStartTime != startTime) {
78
+
return { cancel: true };
79
+
}
80
+
81
+
this.updateProgress(data, startTime);
82
+
}
83
+
});
84
+
85
+
if (this.fetchStartTime != startTime) {
86
+
return;
87
+
}
88
+
89
+
let last = timeline.at(-1);
90
+
let daysBack;
91
+
92
+
if (last) {
93
+
let lastTimestamp = last.reason ? last.reason.indexedAt : last.post.record.createdAt;
94
+
let lastDate = Date.parse(lastTimestamp);
95
+
daysBack = Math.round((startTime - lastDate) / 86400 / 1000);
96
+
} else {
97
+
daysBack = 0;
98
+
}
99
+
100
+
this.timelinePosts = timeline.map(x => Post.parseFeedPost(x));
101
+
102
+
this.archiveStatus.innerText = "Timeline archive fetched: " + ((daysBack == 1) ? '1 day' : `${daysBack} days`);
103
+
this.searchLine.style.display = 'block';
104
+
105
+
this.submitButton.value = 'Fetch timeline';
106
+
this.progressBar.style.display = 'none';
107
+
this.fetchStartTime = undefined;
108
+
}
109
+
110
+
searchInTimeline(query) {
111
+
this.results.innerHTML = '';
112
+
113
+
if (query.length == 0) {
114
+
return;
115
+
}
116
+
117
+
let matching = this.timelinePosts.filter(x => x.lowercaseText.includes(query));
118
+
119
+
for (let post of matching) {
120
+
let postView = new PostComponent(post, 'feed').buildElement();
121
+
this.results.appendChild(postView);
122
+
}
123
+
}
124
+
125
+
/** @param {json[]} dataPage, @param {number} startTime */
126
+
127
+
updateProgress(dataPage, startTime) {
128
+
if (dataPage.length == 0) { return }
129
+
130
+
let last = dataPage[dataPage.length - 1];
131
+
let lastTimestamp = last.reason ? last.reason.indexedAt : last.post.record.createdAt;
132
+
let lastDate = Date.parse(lastTimestamp);
133
+
134
+
let daysBack = (startTime - lastDate) / 86400 / 1000;
135
+
this.progressBar.value = daysBack;
136
+
}
137
+
138
+
stopFetch() {
139
+
this.submitButton.value = 'Fetch timeline';
140
+
this.progressBar.style.display = 'none';
141
+
this.fetchStartTime = undefined;
142
+
}
143
+
}
+3
skythread.js
+3
skythread.js
···
12
12
window.postingStatsPage = new PostingStatsPage();
13
13
window.likeStatsPage = new LikeStatsPage();
14
14
window.notificationsPage = new NotificationsPage();
15
+
window.privateSearchPage = new PrivateSearchPage();
15
16
16
17
$(document.querySelector('#search form')).addEventListener('submit', (e) => {
17
18
e.preventDefault();
···
316
317
window.postingStatsPage.show();
317
318
} else if (page == 'like_stats') {
318
319
window.likeStatsPage.show();
320
+
} else if (page == 'search') {
321
+
window.privateSearchPage.show();
319
322
}
320
323
}
321
324
+38
style.css
+38
style.css
···
898
898
padding: 2px;
899
899
}
900
900
901
+
#private_search_page {
902
+
display: none;
903
+
}
904
+
905
+
#private_search_page input[type="range"] {
906
+
width: 250px;
907
+
vertical-align: middle;
908
+
}
909
+
910
+
#private_search_page input[type="submit"] {
911
+
font-size: 12pt;
912
+
margin: 5px 0px;
913
+
padding: 5px 10px;
914
+
}
915
+
916
+
#private_search_page progress {
917
+
width: 300px;
918
+
margin-left: 10px;
919
+
vertical-align: middle;
920
+
display: none;
921
+
}
922
+
923
+
#private_search_page .search {
924
+
display: none;
925
+
}
926
+
927
+
#private_search_page .search-query {
928
+
font-size: 12pt;
929
+
border: 1px solid #ccc;
930
+
border-radius: 6px;
931
+
padding: 5px 6px;
932
+
margin-left: 8px;
933
+
}
934
+
901
935
@media (prefers-color-scheme: dark) {
902
936
body {
903
937
background-color: rgb(39, 39, 37);
···
1088
1122
1089
1123
#like_stats_page .scan-result th {
1090
1124
background-color: hsl(207, 90%, 25%);
1125
+
}
1126
+
1127
+
#private_search_page .search-query {
1128
+
border: 1px solid #666;
1091
1129
}
1092
1130
}