@nbtca/prompt 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/app.js +142 -13
- package/dist/app/chrome.js +13 -2
- package/dist/app/frame.js +14 -2
- package/dist/app/keys.js +109 -1
- package/dist/app/views/docs-render.js +3 -2
- package/dist/app/views/docs.js +90 -25
- package/dist/app/views/events-render.js +2 -1
- package/dist/app/views/events.js +13 -2
- package/dist/app/views/home.js +46 -24
- package/dist/app/views/schedule-render.js +4 -3
- package/dist/app/views/schedule.js +101 -33
- package/dist/cli.js +570 -0
- package/dist/config/preferences.js +7 -0
- package/dist/core/canvas.js +1 -0
- package/dist/core/components/menu.js +23 -22
- package/dist/core/components/spinner.js +17 -1
- package/dist/core/text.js +7 -3
- package/dist/core/vim-keys.js +149 -6
- package/dist/features/calendar-store.js +27 -0
- package/dist/features/calendar.js +55 -6
- package/dist/features/docs-client.js +225 -0
- package/dist/features/docs.js +224 -68
- package/dist/features/links.js +51 -0
- package/dist/features/status.js +83 -14
- package/dist/features/student-timetable.js +1 -2
- package/dist/features/theme.js +3 -3
- package/dist/features/update.js +3 -2
- package/dist/i18n/locales/en.json +7 -2
- package/dist/i18n/locales/zh.json +7 -2
- package/dist/index.js +5 -498
- package/package.json +2 -1
|
@@ -21,6 +21,21 @@ let session = null;
|
|
|
21
21
|
let client = null;
|
|
22
22
|
let catalog = [];
|
|
23
23
|
let pendingId = '';
|
|
24
|
+
let lifecycleGeneration = 0;
|
|
25
|
+
const closingSessions = new WeakMap();
|
|
26
|
+
function isLifecycleActive(ctx, generation) {
|
|
27
|
+
return generation === lifecycleGeneration && ctx.signal?.aborted !== true;
|
|
28
|
+
}
|
|
29
|
+
async function closeSession(target) {
|
|
30
|
+
let closing = closingSessions.get(target);
|
|
31
|
+
if (!closing) {
|
|
32
|
+
closing = Promise.resolve()
|
|
33
|
+
.then(() => target.close())
|
|
34
|
+
.catch(() => undefined);
|
|
35
|
+
closingSessions.set(target, closing);
|
|
36
|
+
}
|
|
37
|
+
await closing;
|
|
38
|
+
}
|
|
24
39
|
async function releaseSession(target = session) {
|
|
25
40
|
if (!target)
|
|
26
41
|
return;
|
|
@@ -28,10 +43,7 @@ async function releaseSession(target = session) {
|
|
|
28
43
|
session = null;
|
|
29
44
|
client = null;
|
|
30
45
|
}
|
|
31
|
-
|
|
32
|
-
await target.close();
|
|
33
|
-
}
|
|
34
|
-
catch { }
|
|
46
|
+
await closeSession(target);
|
|
35
47
|
}
|
|
36
48
|
function isTimetableLike(value) {
|
|
37
49
|
return (!!value &&
|
|
@@ -88,12 +100,16 @@ function buildPublicField() {
|
|
|
88
100
|
});
|
|
89
101
|
}
|
|
90
102
|
const PUBLIC_UPCOMING_FETCH_CAP = 15;
|
|
91
|
-
async function goToPublic(ctx) {
|
|
103
|
+
async function goToPublic(ctx, generation = lifecycleGeneration) {
|
|
104
|
+
if (!isLifecycleActive(ctx, generation))
|
|
105
|
+
return;
|
|
92
106
|
setVimKeysActive(true);
|
|
93
107
|
state = { mode: 'public', publicField: buildPublicField() };
|
|
94
108
|
ctx.rerender();
|
|
95
109
|
try {
|
|
96
|
-
const cal = await loadCalendarOrThrow();
|
|
110
|
+
const cal = await loadCalendarOrThrow(ctx.signal);
|
|
111
|
+
if (!isLifecycleActive(ctx, generation))
|
|
112
|
+
return;
|
|
97
113
|
const now = new Date();
|
|
98
114
|
const windowEvents = cal.inRange(addLocalDays(now, -400), addLocalDays(now, 400));
|
|
99
115
|
const publicWindow = currentAcademicWindow(windowEvents, now);
|
|
@@ -105,13 +121,18 @@ async function goToPublic(ctx) {
|
|
|
105
121
|
state = { ...state, publicWindow, publicUpcoming };
|
|
106
122
|
}
|
|
107
123
|
catch {
|
|
124
|
+
if (!isLifecycleActive(ctx, generation))
|
|
125
|
+
return;
|
|
108
126
|
state = { ...state, publicWindow: null };
|
|
109
127
|
}
|
|
110
|
-
ctx
|
|
128
|
+
if (isLifecycleActive(ctx, generation))
|
|
129
|
+
ctx.rerender();
|
|
111
130
|
}
|
|
112
|
-
async function tryInferWeekOne() {
|
|
131
|
+
async function tryInferWeekOne(ctx, generation) {
|
|
113
132
|
try {
|
|
114
|
-
const cal = await loadCalendarOrThrow();
|
|
133
|
+
const cal = await loadCalendarOrThrow(ctx.signal);
|
|
134
|
+
if (!isLifecycleActive(ctx, generation))
|
|
135
|
+
return null;
|
|
115
136
|
const now = new Date();
|
|
116
137
|
const events = cal.inRange(addLocalDays(now, -400), addLocalDays(now, 400));
|
|
117
138
|
return inferWeekOneMonday(events, now);
|
|
@@ -120,19 +141,32 @@ async function tryInferWeekOne() {
|
|
|
120
141
|
return null;
|
|
121
142
|
}
|
|
122
143
|
}
|
|
123
|
-
async function afterAuthenticated(ctx, s) {
|
|
144
|
+
async function afterAuthenticated(ctx, s, generation) {
|
|
145
|
+
if (!isLifecycleActive(ctx, generation)) {
|
|
146
|
+
await closeSession(s);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
124
149
|
const hadCache = state.mode === 'hub';
|
|
125
150
|
if (session && session !== s)
|
|
126
151
|
await releaseSession(session);
|
|
152
|
+
if (!isLifecycleActive(ctx, generation)) {
|
|
153
|
+
await closeSession(s);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
127
156
|
session = s;
|
|
128
157
|
client = createNbtTimetableClient(s.timetableTransport, { baseUrl: JWXT_ORIGIN });
|
|
129
158
|
try {
|
|
130
|
-
|
|
159
|
+
const nextCatalog = (await client.listTerms(ctx.signal === undefined ? {} : { signal: ctx.signal })).map(sanitizeAcademicTerm);
|
|
160
|
+
if (!isLifecycleActive(ctx, generation))
|
|
161
|
+
return;
|
|
162
|
+
catalog = nextCatalog;
|
|
131
163
|
const term = resolveTerm(catalog);
|
|
132
164
|
const key = termKey(term);
|
|
133
165
|
let weekOne = loadWeekOne(key);
|
|
134
166
|
if (!weekOne) {
|
|
135
|
-
weekOne = await tryInferWeekOne();
|
|
167
|
+
weekOne = await tryInferWeekOne(ctx, generation);
|
|
168
|
+
if (!isLifecycleActive(ctx, generation))
|
|
169
|
+
return;
|
|
136
170
|
if (weekOne)
|
|
137
171
|
saveWeekOne(key, weekOne);
|
|
138
172
|
}
|
|
@@ -151,9 +185,11 @@ async function afterAuthenticated(ctx, s) {
|
|
|
151
185
|
ctx.rerender();
|
|
152
186
|
return;
|
|
153
187
|
}
|
|
154
|
-
await fetchAndShowHub(ctx, term, key, weekOne);
|
|
188
|
+
await fetchAndShowHub(ctx, term, key, weekOne, generation);
|
|
155
189
|
}
|
|
156
190
|
catch (err) {
|
|
191
|
+
if (!isLifecycleActive(ctx, generation))
|
|
192
|
+
return;
|
|
157
193
|
if (isSessionExpired(err)) {
|
|
158
194
|
createSessionStore().clear();
|
|
159
195
|
await releaseSession(s);
|
|
@@ -168,13 +204,15 @@ async function afterAuthenticated(ctx, s) {
|
|
|
168
204
|
ctx.rerender();
|
|
169
205
|
}
|
|
170
206
|
}
|
|
171
|
-
async function fetchAndShowHub(ctx, term, key, weekOne) {
|
|
172
|
-
if (!client)
|
|
207
|
+
async function fetchAndShowHub(ctx, term, key, weekOne, generation = lifecycleGeneration) {
|
|
208
|
+
if (!client || !isLifecycleActive(ctx, generation))
|
|
173
209
|
return;
|
|
174
210
|
state = { mode: 'loading', statusMessage: t().calendar.loading };
|
|
175
211
|
ctx.rerender();
|
|
176
212
|
try {
|
|
177
|
-
const timetable = sanitizeTimetable(await client.fetchTerm(term));
|
|
213
|
+
const timetable = sanitizeTimetable(await client.fetchTerm(term, ctx.signal === undefined ? {} : { signal: ctx.signal }));
|
|
214
|
+
if (!isLifecycleActive(ctx, generation))
|
|
215
|
+
return;
|
|
178
216
|
const schedule = createTimetableSchedule(timetable, { weekOneMonday: weekOne });
|
|
179
217
|
saveTimetableCache(key, timetable);
|
|
180
218
|
saveCurrentPointer(key, weekOne);
|
|
@@ -188,6 +226,8 @@ async function fetchAndShowHub(ctx, term, key, weekOne) {
|
|
|
188
226
|
};
|
|
189
227
|
}
|
|
190
228
|
catch (err) {
|
|
229
|
+
if (!isLifecycleActive(ctx, generation))
|
|
230
|
+
return;
|
|
191
231
|
if (isSessionExpired(err)) {
|
|
192
232
|
createSessionStore().clear();
|
|
193
233
|
await releaseSession();
|
|
@@ -197,27 +237,36 @@ async function fetchAndShowHub(ctx, term, key, weekOne) {
|
|
|
197
237
|
state = { mode: 'error', errorMessage: safeMessage(err) };
|
|
198
238
|
}
|
|
199
239
|
}
|
|
200
|
-
ctx
|
|
240
|
+
if (isLifecycleActive(ctx, generation))
|
|
241
|
+
ctx.rerender();
|
|
201
242
|
}
|
|
202
|
-
async function refreshFromNetwork(ctx) {
|
|
243
|
+
async function refreshFromNetwork(ctx, generation) {
|
|
244
|
+
if (!isLifecycleActive(ctx, generation))
|
|
245
|
+
return;
|
|
203
246
|
const hadCache = state.mode === 'hub';
|
|
204
247
|
try {
|
|
205
248
|
const store = createSessionStore();
|
|
206
249
|
const persisted = store.load();
|
|
207
250
|
if (!persisted) {
|
|
208
251
|
if (!hadCache)
|
|
209
|
-
await goToPublic(ctx);
|
|
252
|
+
await goToPublic(ctx, generation);
|
|
210
253
|
return;
|
|
211
254
|
}
|
|
212
255
|
const restored = await restoreNbtSession(persisted);
|
|
213
|
-
|
|
256
|
+
if (!isLifecycleActive(ctx, generation)) {
|
|
257
|
+
await closeSession(restored);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await afterAuthenticated(ctx, restored, generation);
|
|
214
261
|
}
|
|
215
262
|
catch (err) {
|
|
263
|
+
if (!isLifecycleActive(ctx, generation))
|
|
264
|
+
return;
|
|
216
265
|
if (!hadCache) {
|
|
217
266
|
if (err instanceof AuthError && isSessionExpired(err)) {
|
|
218
267
|
createSessionStore().clear();
|
|
219
268
|
}
|
|
220
|
-
await goToPublic(ctx);
|
|
269
|
+
await goToPublic(ctx, generation);
|
|
221
270
|
}
|
|
222
271
|
}
|
|
223
272
|
}
|
|
@@ -225,6 +274,9 @@ export const scheduleView = {
|
|
|
225
274
|
id: 'schedule',
|
|
226
275
|
title: t().timetable.menuEntry,
|
|
227
276
|
async load(ctx) {
|
|
277
|
+
const generation = ++lifecycleGeneration;
|
|
278
|
+
if (!isLifecycleActive(ctx, generation))
|
|
279
|
+
return;
|
|
228
280
|
const ptr = loadCurrentPointer();
|
|
229
281
|
const cached = readCachedTimetable(ptr ? loadTimetableCache(ptr.termKey) : null);
|
|
230
282
|
if (ptr && cached) {
|
|
@@ -241,15 +293,20 @@ export const scheduleView = {
|
|
|
241
293
|
state = { mode: 'loading' };
|
|
242
294
|
}
|
|
243
295
|
ctx.rerender();
|
|
244
|
-
await refreshFromNetwork(ctx);
|
|
296
|
+
await refreshFromNetwork(ctx, generation);
|
|
245
297
|
},
|
|
246
298
|
async dispose() {
|
|
299
|
+
lifecycleGeneration += 1;
|
|
300
|
+
setVimKeysActive(true);
|
|
247
301
|
await releaseSession();
|
|
248
302
|
},
|
|
249
303
|
render(ctx) {
|
|
250
304
|
state.termField?.setMaxVisible(computeMaxVisible(ctx.bodyRows));
|
|
251
305
|
return renderSchedule(state, new Date(), ctx.bodyRows, ctx.size.cols);
|
|
252
306
|
},
|
|
307
|
+
isBusy() {
|
|
308
|
+
return state.mode === 'loading';
|
|
309
|
+
},
|
|
253
310
|
capturesInput() {
|
|
254
311
|
return (state.mode === 'needsLoginId' ||
|
|
255
312
|
state.mode === 'needsLoginPassword' ||
|
|
@@ -331,23 +388,33 @@ export const scheduleView = {
|
|
|
331
388
|
}
|
|
332
389
|
if (result?.submitted !== undefined) {
|
|
333
390
|
const password = result.submitted;
|
|
391
|
+
const generation = lifecycleGeneration;
|
|
334
392
|
setVimKeysActive(true);
|
|
335
393
|
state = { mode: 'authenticating', statusMessage: t().timetable.loginWillSave };
|
|
336
394
|
ctx.rerender();
|
|
337
|
-
void loginWithStudentPassword(pendingId, password)
|
|
395
|
+
void loginWithStudentPassword(pendingId, password, ctx.signal === undefined ? {} : { signal: ctx.signal })
|
|
338
396
|
.then(async (s) => {
|
|
339
|
-
let handedOff = false;
|
|
340
397
|
try {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
398
|
+
if (!isLifecycleActive(ctx, generation)) {
|
|
399
|
+
await closeSession(s);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const snapshot = await s.snapshot();
|
|
403
|
+
if (!isLifecycleActive(ctx, generation)) {
|
|
404
|
+
await closeSession(s);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
createSessionStore().save(snapshot);
|
|
408
|
+
await afterAuthenticated(ctx, s, generation);
|
|
344
409
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
410
|
+
catch (error) {
|
|
411
|
+
await closeSession(s);
|
|
412
|
+
throw error;
|
|
348
413
|
}
|
|
349
414
|
})
|
|
350
415
|
.catch((err) => {
|
|
416
|
+
if (!isLifecycleActive(ctx, generation))
|
|
417
|
+
return;
|
|
351
418
|
goToLoginId(safeMessage(err));
|
|
352
419
|
ctx.rerender();
|
|
353
420
|
});
|
|
@@ -377,7 +444,7 @@ export const scheduleView = {
|
|
|
377
444
|
}
|
|
378
445
|
saveWeekOne(targetKey, trimmed);
|
|
379
446
|
setVimKeysActive(true);
|
|
380
|
-
void fetchAndShowHub(ctx, targetTerm, targetKey, trimmed);
|
|
447
|
+
void fetchAndShowHub(ctx, targetTerm, targetKey, trimmed, lifecycleGeneration);
|
|
381
448
|
}
|
|
382
449
|
return;
|
|
383
450
|
}
|
|
@@ -456,10 +523,11 @@ export const scheduleView = {
|
|
|
456
523
|
return;
|
|
457
524
|
}
|
|
458
525
|
if (shortcut.key === 'x') {
|
|
526
|
+
const generation = ++lifecycleGeneration;
|
|
459
527
|
createSessionStore().clear();
|
|
460
528
|
clearScheduleCache();
|
|
461
529
|
void releaseSession();
|
|
462
|
-
void goToPublic(ctx);
|
|
530
|
+
void goToPublic(ctx, generation);
|
|
463
531
|
}
|
|
464
532
|
return;
|
|
465
533
|
}
|
|
@@ -528,7 +596,7 @@ export const scheduleView = {
|
|
|
528
596
|
};
|
|
529
597
|
return;
|
|
530
598
|
}
|
|
531
|
-
void fetchAndShowHub(ctx, term, newTermKey, weekOne);
|
|
599
|
+
void fetchAndShowHub(ctx, term, newTermKey, weekOne, lifecycleGeneration);
|
|
532
600
|
return;
|
|
533
601
|
}
|
|
534
602
|
case 'loading':
|