+21
index.html
+21
index.html
···
193
193
</div>
194
194
</form>
195
195
196
+
<div class="lycan-import">
197
+
<form>
198
+
<p>
199
+
In order to search within your likes and bookmarks, the posts you've liked or saved need to be imported into a database.
200
+
This is a one-time process, but it can take several minutes or more, depending on the age of your account.
201
+
</p>
202
+
<p>
203
+
To start the import, press the button below. You can then wait until it finishes, or close this tab and come back a bit later.
204
+
After the import is complete, the database will be kept up to date automatically going forward.
205
+
</p>
206
+
<p>
207
+
<input type="submit" value="Start import">
208
+
</p>
209
+
</form>
210
+
211
+
<div class="import-progress">
212
+
<p class="import-status"></p>
213
+
<p><progress></progress> <output></output></p>
214
+
</div>
215
+
</div>
216
+
196
217
<div class="results">
197
218
</div>
198
219
</div>
+171
-6
private_search_page.js
+171
-6
private_search_page.js
···
3
3
/** @type {number | undefined} */
4
4
fetchStartTime;
5
5
6
+
/** @type {number | undefined} */
7
+
importTimer;
8
+
9
+
/** @type {string | undefined} */
10
+
lycanImportStatus;
11
+
6
12
constructor() {
7
13
this.pageElement = $id('private_search_page');
8
14
···
17
23
this.results = $(this.pageElement.querySelector('.results'));
18
24
19
25
this.timelineSearch = $(this.pageElement.querySelector('.timeline-search'));
26
+
this.timelineSearchForm = $(this.pageElement.querySelector('.timeline-search form'), HTMLFormElement);
20
27
this.searchCollections = $(this.pageElement.querySelector('.search-collections'));
21
28
29
+
this.lycanImportSection = $(this.pageElement.querySelector('.lycan-import'));
30
+
this.lycanImportForm = $(this.pageElement.querySelector('.lycan-import form'), HTMLFormElement);
31
+
this.importProgress = $(this.pageElement.querySelector('.import-progress'));
32
+
this.importProgressBar = $(this.pageElement.querySelector('.import-progress progress'), HTMLProgressElement);
33
+
this.importStatusLabel = $(this.pageElement.querySelector('.import-status'));
34
+
this.importStatusPosition = $(this.pageElement.querySelector('.import-progress output'));
35
+
36
+
this.isCheckingStatus = false;
22
37
this.timelinePosts = [];
23
38
24
39
this.setupEvents();
···
33
48
}
34
49
35
50
setupEvents() {
36
-
$(this.pageElement.querySelector('form')).addEventListener('submit', (e) => {
51
+
this.timelineSearchForm.addEventListener('submit', (e) => {
37
52
e.preventDefault();
38
53
39
54
if (!this.fetchStartTime) {
···
62
77
}
63
78
}
64
79
});
80
+
81
+
this.lycanImportForm.addEventListener('submit', (e) => {
82
+
e.preventDefault();
83
+
this.startLycanImport();
84
+
});
65
85
}
66
86
67
87
/** @returns {number} */
···
77
97
this.timelineSearch.style.display = 'none';
78
98
this.searchCollections.style.display = 'block';
79
99
this.searchLine.style.display = 'block';
100
+
this.lycanImportSection.style.display = 'none';
101
+
this.checkLycanImportStatus();
80
102
} else {
81
103
this.timelineSearch.style.display = 'block';
82
104
this.searchCollections.style.display = 'none';
105
+
this.lycanImportSection.style.display = 'none';
106
+
}
107
+
}
108
+
109
+
/** @returns {Promise<void>} */
110
+
111
+
async checkLycanImportStatus() {
112
+
if (this.isCheckingStatus) {
113
+
return;
114
+
}
115
+
116
+
this.isCheckingStatus = true;
117
+
118
+
try {
119
+
let response = await this.getImportStatus();
120
+
this.showImportStatus(response);
121
+
} catch (error) {
122
+
this.showImportError(`Couldn't check import status: ${error}`);
123
+
} finally {
124
+
this.isCheckingStatus = false;
125
+
}
126
+
}
127
+
128
+
/** @returns {Promise<json>} */
129
+
130
+
async getImportStatus() {
131
+
if (this.localLycan) {
132
+
return await this.localLycan.getRequest('blue.feeds.lycan.getImportStatus', { user: accountAPI.user.did });
133
+
} else {
134
+
return await accountAPI.getRequest('blue.feeds.lycan.getImportStatus', null, {
135
+
headers: { 'atproto-proxy': 'did:web:lycan.feeds.blue#lycan' }
136
+
});
137
+
}
138
+
}
139
+
140
+
/** @param {json} info */
141
+
142
+
showImportStatus(info) {
143
+
console.log(info);
144
+
145
+
if (!info.status) {
146
+
this.showImportError("Error checking import status");
147
+
return;
148
+
}
149
+
150
+
this.lycanImportStatus = info.status;
151
+
152
+
if (info.status == 'not_started') {
153
+
this.lycanImportSection.style.display = 'block';
154
+
this.lycanImportForm.style.display = 'block';
155
+
this.importProgress.style.display = 'none';
156
+
157
+
this.stopImportTimer();
158
+
} else if (info.status == 'in_progress' || info.status == 'scheduled' || info.status == 'requested') {
159
+
this.lycanImportSection.style.display = 'block';
160
+
this.lycanImportForm.style.display = 'none';
161
+
this.importProgress.style.display = 'block';
162
+
163
+
this.showImportProgress(info);
164
+
this.startImportTimer();
165
+
} else if (info.status == 'finished') {
166
+
this.lycanImportForm.style.display = 'none';
167
+
this.importProgress.style.display = 'block';
168
+
169
+
this.showImportProgress({ status: 'finished', progress: 1.0 });
170
+
this.stopImportTimer();
171
+
} else {
172
+
this.showImportError("Error checking import status");
173
+
this.stopImportTimer();
174
+
}
175
+
}
176
+
177
+
/** @param {json} info */
178
+
179
+
showImportProgress(info) {
180
+
let progress = Math.max(0, Math.min(info.progress || 0));
181
+
this.importProgressBar.value = progress;
182
+
this.importProgressBar.style.display = 'inline';
183
+
184
+
let percent = Math.round(progress * 100);
185
+
this.importStatusPosition.innerText = `${percent}%`;
186
+
187
+
if (info.progress == 1.0) {
188
+
this.importStatusLabel.innerText = `Import complete ✓`;
189
+
} else if (info.position) {
190
+
let date = new Date(info.position).toLocaleString(window.dateLocale, { day: 'numeric', month: 'short', year: 'numeric' });
191
+
this.importStatusLabel.innerText = `Imported data until: ${date}`;
192
+
} else if (info.status == 'requested') {
193
+
this.importStatusLabel.innerText = 'Requesting import…';
194
+
} else {
195
+
this.importStatusLabel.innerText = 'Import started…';
196
+
}
197
+
}
198
+
199
+
/** @param {string} message */
200
+
201
+
showImportError(message) {
202
+
this.lycanImportSection.style.display = 'block';
203
+
this.lycanImportForm.style.display = 'none';
204
+
this.importProgress.style.display = 'block';
205
+
206
+
this.importStatusLabel.innerText = message;
207
+
this.stopImportTimer();
208
+
}
209
+
210
+
startImportTimer() {
211
+
if (this.importTimer) {
212
+
return;
213
+
}
214
+
215
+
this.importTimer = setInterval(() => {
216
+
this.checkLycanImportStatus();
217
+
}, 3000);
218
+
}
219
+
220
+
stopImportTimer() {
221
+
if (this.importTimer) {
222
+
clearInterval(this.importTimer);
223
+
this.importTimer = undefined;
224
+
}
225
+
}
226
+
227
+
/** @returns {Promise<void>} */
228
+
229
+
async startLycanImport() {
230
+
this.showImportStatus({ status: 'requested' });
231
+
232
+
try {
233
+
if (this.localLycan) {
234
+
await this.localLycan.postRequest('blue.feeds.lycan.startImport', {
235
+
user: accountAPI.user.did
236
+
});
237
+
} else {
238
+
await accountAPI.postRequest('blue.feeds.lycan.startImport', null, {
239
+
headers: { 'atproto-proxy': 'did:web:lycan.feeds.blue#lycan' }
240
+
});
241
+
}
242
+
243
+
this.startImportTimer();
244
+
} catch (err) {
245
+
console.error('Failed to start Lycan import', err);
246
+
this.showImportError(`Import failed: ${err}`);
83
247
}
84
248
}
85
249
···
151
315
/** @param {string} query */
152
316
153
317
searchInLycan(query) {
154
-
this.results.innerHTML = '';
155
-
156
-
if (query.length == 0) {
318
+
if (query.length == 0 || this.lycanImportStatus != 'finished') {
157
319
return;
158
320
}
159
321
322
+
this.results.innerHTML = '';
323
+
this.lycanImportSection.style.display = 'none';
324
+
160
325
let collection = this.searchForm.elements['collection'].value;
161
326
162
327
let loading = $tag('p', { text: "..." });
···
174
339
let response;
175
340
176
341
if (this.localLycan) {
177
-
let params = { collection, query, user: window.accountAPI.user.did };
342
+
let params = { collection, query, user: accountAPI.user.did };
178
343
if (cursor) params.cursor = cursor;
179
344
180
345
response = await this.localLycan.getRequest('blue.feeds.lycan.searchPosts', params);
···
197
362
return;
198
363
}
199
364
200
-
let records = await window.accountAPI.loadPosts(response.posts);
365
+
let records = await accountAPI.loadPosts(response.posts);
201
366
let posts = records.map(x => new Post(x));
202
367
203
368
if (!firstPageLoaded) {
+25
style.css
+25
style.css
···
1074
1074
vertical-align: middle;
1075
1075
}
1076
1076
1077
+
#private_search_page .lycan-import {
1078
+
display: none;
1079
+
1080
+
margin-top: 30px;
1081
+
border-top: 1px solid #ccc;
1082
+
padding-top: 5px;
1083
+
}
1084
+
1085
+
#private_search_page .lycan-import form p {
1086
+
line-height: 135%;
1087
+
}
1088
+
1089
+
#private_search_page .lycan-import .import-progress progress {
1090
+
margin-left: 0;
1091
+
margin-right: 6px;
1092
+
}
1093
+
1094
+
#private_search_page .lycan-import .import-progress progress + output {
1095
+
font-size: 11pt;
1096
+
}
1097
+
1077
1098
#private_search_page .results {
1078
1099
margin-top: 30px;
1079
1100
}
···
1363
1384
1364
1385
#private_search_page .search-query {
1365
1386
border: 1px solid #666;
1387
+
}
1388
+
1389
+
#private_search_page .lycan-import {
1390
+
border-top-color: #888;
1366
1391
}
1367
1392
1368
1393
#private_search_page .results-end {