@nbtca/prompt 1.4.2 → 1.5.0
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/README.md +27 -58
- package/SECURITY.md +16 -45
- package/dist/app/app.js +53 -55
- package/dist/app/chrome.js +67 -50
- package/dist/app/fields/list-field.js +12 -25
- package/dist/app/fields/text-field.js +3 -8
- package/dist/app/frame.js +2 -21
- package/dist/app/keys.js +10 -2
- package/dist/app/views/docs-render.js +31 -24
- package/dist/app/views/docs.js +211 -60
- package/dist/app/views/events-render.js +19 -26
- package/dist/app/views/events.js +44 -31
- package/dist/app/views/home.js +28 -30
- package/dist/app/views/schedule-grid-cursor.js +9 -18
- package/dist/app/views/schedule-render.js +47 -71
- package/dist/app/views/schedule.js +158 -81
- package/dist/app/views/settings-render.js +8 -19
- package/dist/app/views/settings.js +92 -17
- package/dist/auth/cookie-transport.js +31 -32
- package/dist/auth/errors.js +3 -1
- package/dist/auth/nbt-auth.js +42 -25
- package/dist/auth/session-store.js +17 -9
- package/dist/config/data.js +9 -11
- package/dist/config/preferences.js +14 -7
- package/dist/core/calendar-day.js +37 -0
- package/dist/core/capabilities.js +6 -3
- package/dist/core/components/confirm.js +9 -8
- package/dist/core/components/menu.js +41 -16
- package/dist/core/components/messages.js +12 -4
- package/dist/core/components/painter.js +3 -1
- package/dist/core/components/spinner.js +17 -6
- package/dist/core/components/text-input.js +24 -18
- package/dist/core/icons.js +2 -2
- package/dist/core/logo.js +23 -5
- package/dist/core/motion.js +25 -19
- package/dist/core/text.js +182 -69
- package/dist/core/theme.js +0 -28
- package/dist/core/transitions.js +2 -2
- package/dist/core/ui.js +15 -13
- package/dist/core/vim-keys.js +9 -15
- package/dist/features/about.js +23 -0
- package/dist/features/calendar-heatmap.js +16 -40
- package/dist/features/calendar-query.js +1 -2
- package/dist/features/calendar.js +12 -185
- package/dist/features/docs.js +436 -275
- package/dist/features/schedule-render.js +65 -101
- package/dist/features/schedule-store.js +51 -9
- package/dist/features/schedule-view.js +46 -213
- package/dist/features/status.js +44 -56
- package/dist/features/student-timetable.js +73 -95
- package/dist/features/theme.js +6 -2
- package/dist/features/timetable-sanitize.js +40 -0
- package/dist/features/update.js +9 -27
- package/dist/i18n/index.js +83 -19
- package/dist/i18n/locales/en.json +1 -1
- package/dist/i18n/locales/zh.json +1 -1
- package/dist/index.js +83 -58
- package/dist/logo/ca-dotmatrix.txt +16 -18
- package/dist/main.js +7 -48
- package/package.json +27 -18
- package/bin/nbtca-welcome.js +0 -2
- package/dist/core/components/screen.js +0 -18
- package/dist/core/menu.js +0 -68
- package/dist/features/links.js +0 -36
- package/dist/features/schedule-query.js +0 -47
- package/dist/features/settings.js +0 -127
- package/dist/logo/ca-logo.png +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import chalk from 'chalk';
|
|
2
3
|
import open from 'open';
|
|
3
4
|
import { main } from './main.js';
|
|
4
|
-
import { fetchEvents, fetchHeatmapBuckets, renderEventsTable, serializeEvents } from './features/calendar.js';
|
|
5
|
+
import { fetchEvents, fetchHeatmapBuckets, renderEventsTable, serializeEvents, } from './features/calendar.js';
|
|
5
6
|
import { renderHeatmap } from './features/calendar-heatmap.js';
|
|
6
|
-
import { checkServices, countServiceHealth, hasServiceFailures, renderServiceStatusTable, serializeServiceStatus } from './features/status.js';
|
|
7
|
+
import { checkServices, countServiceHealth, hasServiceFailures, renderServiceStatusTable, serializeServiceStatus, } from './features/status.js';
|
|
7
8
|
import { pickIcon } from './core/icons.js';
|
|
8
9
|
import { applyColorModePreference } from './config/preferences.js';
|
|
9
10
|
import { openDocsInBrowser } from './features/docs.js';
|
|
@@ -13,6 +14,7 @@ import { clearScreen, handleGracefulExit } from './core/ui.js';
|
|
|
13
14
|
import { APP_INFO, URLS } from './config/data.js';
|
|
14
15
|
import { runUpdateCheck } from './features/update.js';
|
|
15
16
|
import { runStudentTimetableCommand } from './features/student-timetable.js';
|
|
17
|
+
import { showAbout } from './features/about.js';
|
|
16
18
|
const ACTION_ALIASES = {
|
|
17
19
|
events: 'events',
|
|
18
20
|
event: 'events',
|
|
@@ -32,13 +34,33 @@ const URL_ACTIONS = {
|
|
|
32
34
|
repair: URLS.repair,
|
|
33
35
|
website: URLS.homepage,
|
|
34
36
|
github: URLS.github,
|
|
35
|
-
roadmap: URLS.roadmap
|
|
37
|
+
roadmap: URLS.roadmap,
|
|
36
38
|
};
|
|
37
39
|
const KNOWN_FLAGS = new Set([
|
|
38
|
-
'--help',
|
|
39
|
-
'--
|
|
40
|
+
'--help',
|
|
41
|
+
'--version',
|
|
42
|
+
'--open',
|
|
43
|
+
'--json',
|
|
44
|
+
'--plain',
|
|
45
|
+
'--no-logo',
|
|
46
|
+
'--watch',
|
|
47
|
+
'--today',
|
|
48
|
+
'--heatmap',
|
|
49
|
+
'--week',
|
|
50
|
+
'--month',
|
|
51
|
+
'--one-shot',
|
|
52
|
+
'--no-save',
|
|
40
53
|
]);
|
|
41
|
-
const KNOWN_FLAG_PREFIXES = [
|
|
54
|
+
const KNOWN_FLAG_PREFIXES = [
|
|
55
|
+
'--interval=',
|
|
56
|
+
'--timeout=',
|
|
57
|
+
'--retries=',
|
|
58
|
+
'--next=',
|
|
59
|
+
'--search=',
|
|
60
|
+
'--term=',
|
|
61
|
+
'--output=',
|
|
62
|
+
'--week-one=',
|
|
63
|
+
];
|
|
42
64
|
const STATUS_WATCH_INTERVAL_MIN = 3;
|
|
43
65
|
const STATUS_WATCH_INTERVAL_MAX = 300;
|
|
44
66
|
const STATUS_TIMEOUT_MIN = 1000;
|
|
@@ -57,13 +79,16 @@ function parseArgs(argv) {
|
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
81
|
return {
|
|
60
|
-
command: positionals[0]
|
|
82
|
+
...(positionals[0] === undefined ? {} : { command: positionals[0].toLowerCase() }),
|
|
61
83
|
args: positionals.slice(1),
|
|
62
|
-
flags
|
|
84
|
+
flags,
|
|
63
85
|
};
|
|
64
86
|
}
|
|
87
|
+
function isTty(value) {
|
|
88
|
+
return value === true;
|
|
89
|
+
}
|
|
65
90
|
function hasInteractiveTerminal() {
|
|
66
|
-
return
|
|
91
|
+
return isTty(process.stdin.isTTY) && isTty(process.stdout.isTTY);
|
|
67
92
|
}
|
|
68
93
|
function getAllowedFlagsFor(command) {
|
|
69
94
|
const allowed = new Set(['--help', '--plain']);
|
|
@@ -122,27 +147,27 @@ function getAllowedFlagPrefixesFor(command) {
|
|
|
122
147
|
return [];
|
|
123
148
|
}
|
|
124
149
|
function validateFlags(command, flags) {
|
|
125
|
-
const
|
|
150
|
+
const unknownFlag = Array.from(flags).find((flag) => {
|
|
126
151
|
if (KNOWN_FLAGS.has(flag))
|
|
127
152
|
return false;
|
|
128
153
|
return !KNOWN_FLAG_PREFIXES.some((prefix) => flag.startsWith(prefix));
|
|
129
154
|
});
|
|
130
|
-
if (
|
|
155
|
+
if (unknownFlag) {
|
|
131
156
|
const trans0 = t();
|
|
132
|
-
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag:
|
|
157
|
+
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag: unknownFlag })));
|
|
133
158
|
console.error(chalk.dim(trans0.cli.unknownFlagHint));
|
|
134
159
|
process.exit(1);
|
|
135
160
|
}
|
|
136
161
|
const allowed = getAllowedFlagsFor(command);
|
|
137
162
|
const allowedPrefixes = getAllowedFlagPrefixesFor(command);
|
|
138
|
-
const
|
|
163
|
+
const disallowedFlag = Array.from(flags).find((flag) => {
|
|
139
164
|
if (allowed.has(flag))
|
|
140
165
|
return false;
|
|
141
166
|
return !allowedPrefixes.some((prefix) => flag.startsWith(prefix));
|
|
142
167
|
});
|
|
143
|
-
if (
|
|
168
|
+
if (disallowedFlag) {
|
|
144
169
|
const trans1 = t();
|
|
145
|
-
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag:
|
|
170
|
+
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag: disallowedFlag })));
|
|
146
171
|
console.error(chalk.dim(trans1.cli.invalidFlagHint));
|
|
147
172
|
process.exit(1);
|
|
148
173
|
}
|
|
@@ -200,7 +225,7 @@ async function runEventsCommand(flags) {
|
|
|
200
225
|
process.stdout.write(JSON.stringify(buckets, null, 2) + '\n');
|
|
201
226
|
}
|
|
202
227
|
else {
|
|
203
|
-
const useColor = !flags.has('--plain') &&
|
|
228
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
204
229
|
console.log(renderHeatmap(buckets, new Date(), { color: useColor }));
|
|
205
230
|
}
|
|
206
231
|
return;
|
|
@@ -220,23 +245,23 @@ async function runEventsCommand(flags) {
|
|
|
220
245
|
else {
|
|
221
246
|
events = await fetchEvents();
|
|
222
247
|
}
|
|
223
|
-
const searchFlag = Array.from(flags).find(f => f.startsWith('--search='));
|
|
248
|
+
const searchFlag = Array.from(flags).find((f) => f.startsWith('--search='));
|
|
224
249
|
if (searchFlag) {
|
|
225
250
|
const q = searchFlag.slice('--search='.length).toLowerCase();
|
|
226
|
-
events = events.filter(e => `${e.title} ${e.location}`.toLowerCase().includes(q));
|
|
251
|
+
events = events.filter((e) => `${e.title} ${e.location}`.toLowerCase().includes(q));
|
|
227
252
|
}
|
|
228
253
|
if (flags.has('--today')) {
|
|
229
254
|
const now = new Date();
|
|
230
|
-
events = events.filter(e => {
|
|
255
|
+
events = events.filter((e) => {
|
|
231
256
|
const d = e.startDate;
|
|
232
|
-
return d.getFullYear() === now.getFullYear() &&
|
|
257
|
+
return (d.getFullYear() === now.getFullYear() &&
|
|
233
258
|
d.getMonth() === now.getMonth() &&
|
|
234
|
-
d.getDate() === now.getDate();
|
|
259
|
+
d.getDate() === now.getDate());
|
|
235
260
|
});
|
|
236
261
|
}
|
|
237
|
-
const nextFlag = Array.from(flags).find(f => f.startsWith('--next='));
|
|
262
|
+
const nextFlag = Array.from(flags).find((f) => f.startsWith('--next='));
|
|
238
263
|
if (nextFlag) {
|
|
239
|
-
const n = Number.parseInt(nextFlag.
|
|
264
|
+
const n = Number.parseInt(nextFlag.slice('--next='.length), 10);
|
|
240
265
|
if (!Number.isInteger(n) || n < 1) {
|
|
241
266
|
console.error(chalk.red(t().cli.invalidNext));
|
|
242
267
|
process.exit(1);
|
|
@@ -247,7 +272,7 @@ async function runEventsCommand(flags) {
|
|
|
247
272
|
process.stdout.write(JSON.stringify(serializeEvents(events), null, 2) + '\n');
|
|
248
273
|
return;
|
|
249
274
|
}
|
|
250
|
-
const useColor = !flags.has('--plain') &&
|
|
275
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
251
276
|
console.log(renderEventsTable(events, { color: useColor }));
|
|
252
277
|
}
|
|
253
278
|
async function runStatusCommand(flags) {
|
|
@@ -256,14 +281,20 @@ async function runStatusCommand(flags) {
|
|
|
256
281
|
const intervalFlag = Array.from(flags).find((flag) => flag.startsWith('--interval='));
|
|
257
282
|
const timeoutFlag = Array.from(flags).find((flag) => flag.startsWith('--timeout='));
|
|
258
283
|
const retriesFlag = Array.from(flags).find((flag) => flag.startsWith('--retries='));
|
|
259
|
-
const intervalSeconds = intervalFlag
|
|
260
|
-
|
|
261
|
-
|
|
284
|
+
const intervalSeconds = intervalFlag
|
|
285
|
+
? Number.parseInt(intervalFlag.slice('--interval='.length), 10)
|
|
286
|
+
: 10;
|
|
287
|
+
const timeoutMs = timeoutFlag
|
|
288
|
+
? Number.parseInt(timeoutFlag.slice('--timeout='.length), 10)
|
|
289
|
+
: 6000;
|
|
290
|
+
const retries = retriesFlag ? Number.parseInt(retriesFlag.slice('--retries='.length), 10) : 1;
|
|
262
291
|
if (!watch && intervalFlag) {
|
|
263
292
|
console.error(chalk.red(trans.status.intervalNeedsWatch));
|
|
264
293
|
process.exit(1);
|
|
265
294
|
}
|
|
266
|
-
if (!Number.isInteger(timeoutMs) ||
|
|
295
|
+
if (!Number.isInteger(timeoutMs) ||
|
|
296
|
+
timeoutMs < STATUS_TIMEOUT_MIN ||
|
|
297
|
+
timeoutMs > STATUS_TIMEOUT_MAX) {
|
|
267
298
|
console.error(chalk.red(fmt(trans.status.invalidTimeout, { min: STATUS_TIMEOUT_MIN, max: STATUS_TIMEOUT_MAX })));
|
|
268
299
|
process.exit(1);
|
|
269
300
|
}
|
|
@@ -275,8 +306,14 @@ async function runStatusCommand(flags) {
|
|
|
275
306
|
console.error(chalk.red(trans.status.watchJsonConflict));
|
|
276
307
|
process.exit(1);
|
|
277
308
|
}
|
|
278
|
-
if (watch &&
|
|
279
|
-
|
|
309
|
+
if (watch &&
|
|
310
|
+
(!Number.isInteger(intervalSeconds) ||
|
|
311
|
+
intervalSeconds < STATUS_WATCH_INTERVAL_MIN ||
|
|
312
|
+
intervalSeconds > STATUS_WATCH_INTERVAL_MAX)) {
|
|
313
|
+
console.error(chalk.red(fmt(trans.status.invalidInterval, {
|
|
314
|
+
min: STATUS_WATCH_INTERVAL_MIN,
|
|
315
|
+
max: STATUS_WATCH_INTERVAL_MAX,
|
|
316
|
+
})));
|
|
280
317
|
process.exit(1);
|
|
281
318
|
}
|
|
282
319
|
if (watch && !hasInteractiveTerminal()) {
|
|
@@ -284,12 +321,14 @@ async function runStatusCommand(flags) {
|
|
|
284
321
|
process.exit(1);
|
|
285
322
|
}
|
|
286
323
|
if (watch) {
|
|
287
|
-
|
|
288
|
-
const onSigint = () => {
|
|
324
|
+
const stopController = new AbortController();
|
|
325
|
+
const onSigint = () => {
|
|
326
|
+
stopController.abort();
|
|
327
|
+
};
|
|
289
328
|
process.once('SIGINT', onSigint);
|
|
290
329
|
console.log(chalk.dim(`${fmt(trans.status.watchStarted, { seconds: intervalSeconds })} | ${trans.status.watchHint}`));
|
|
291
330
|
try {
|
|
292
|
-
while (!
|
|
331
|
+
while (!stopController.signal.aborted) {
|
|
293
332
|
const services = await checkServices({ timeoutMs, retries });
|
|
294
333
|
const hasFailures = hasServiceFailures(services);
|
|
295
334
|
const health = countServiceHealth(services);
|
|
@@ -297,7 +336,7 @@ async function runStatusCommand(flags) {
|
|
|
297
336
|
console.log(chalk.bold(`${trans.status.watchUpdated}: ${new Date().toLocaleString()}`));
|
|
298
337
|
console.log(chalk.dim(`${trans.status.up}: ${health.up} | ${trans.status.down}: ${health.down} | ${trans.status.watchHint}`));
|
|
299
338
|
console.log();
|
|
300
|
-
const useColor = !flags.has('--plain') &&
|
|
339
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
301
340
|
console.log(renderServiceStatusTable(services, { color: useColor }));
|
|
302
341
|
if (hasFailures) {
|
|
303
342
|
console.log(chalk.yellow(trans.status.summaryFail));
|
|
@@ -308,12 +347,12 @@ async function runStatusCommand(flags) {
|
|
|
308
347
|
await new Promise((resolve) => {
|
|
309
348
|
const stopWait = () => {
|
|
310
349
|
clearTimeout(timer);
|
|
311
|
-
|
|
350
|
+
stopController.signal.removeEventListener('abort', stopWait);
|
|
312
351
|
resolve();
|
|
313
352
|
};
|
|
314
353
|
const timer = setTimeout(stopWait, intervalSeconds * 1000);
|
|
315
|
-
|
|
316
|
-
if (
|
|
354
|
+
stopController.signal.addEventListener('abort', stopWait, { once: true });
|
|
355
|
+
if (stopController.signal.aborted)
|
|
317
356
|
stopWait();
|
|
318
357
|
});
|
|
319
358
|
}
|
|
@@ -331,7 +370,7 @@ async function runStatusCommand(flags) {
|
|
|
331
370
|
process.stdout.write(JSON.stringify(serializeServiceStatus(services), null, 2) + '\n');
|
|
332
371
|
}
|
|
333
372
|
else {
|
|
334
|
-
const useColor = !flags.has('--plain') &&
|
|
373
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
335
374
|
console.log(renderServiceStatusTable(services, { color: useColor }));
|
|
336
375
|
if (hasFailures) {
|
|
337
376
|
console.error(chalk.yellow(t().status.summaryFail));
|
|
@@ -348,7 +387,10 @@ function maybeDisableColor(flags) {
|
|
|
348
387
|
async function runCommandMode(argv) {
|
|
349
388
|
const { command, args, flags } = parseArgs(argv);
|
|
350
389
|
maybeDisableColor(flags);
|
|
351
|
-
if (flags.has('--version') ||
|
|
390
|
+
if (flags.has('--version') ||
|
|
391
|
+
command === '--version' ||
|
|
392
|
+
command === '-v' ||
|
|
393
|
+
command === 'version') {
|
|
352
394
|
console.log(APP_INFO.version);
|
|
353
395
|
return;
|
|
354
396
|
}
|
|
@@ -368,7 +410,7 @@ async function runCommandMode(argv) {
|
|
|
368
410
|
return;
|
|
369
411
|
}
|
|
370
412
|
if (command === 'lang' || command === 'language') {
|
|
371
|
-
const language = (args[0]
|
|
413
|
+
const language = (args[0] ?? '').toLowerCase();
|
|
372
414
|
if (language !== 'zh' && language !== 'en') {
|
|
373
415
|
console.error(chalk.red(t().cli.invalidLang));
|
|
374
416
|
process.exit(1);
|
|
@@ -441,24 +483,7 @@ async function runCommandMode(argv) {
|
|
|
441
483
|
return;
|
|
442
484
|
}
|
|
443
485
|
if (action === 'about') {
|
|
444
|
-
|
|
445
|
-
const { padEndV } = await import('./core/text.js');
|
|
446
|
-
const pad = 12;
|
|
447
|
-
const row = (label, value) => `${chalk.dim(padEndV(label, pad))}${value}`;
|
|
448
|
-
const link = (label, url) => row(label, chalk.cyan(url));
|
|
449
|
-
const trans = t();
|
|
450
|
-
const content = [
|
|
451
|
-
row(trans.about.project, APP_INFO.name),
|
|
452
|
-
row(trans.about.version, `v${APP_INFO.version}`),
|
|
453
|
-
row(trans.about.description, trans.about.descriptionText),
|
|
454
|
-
'',
|
|
455
|
-
link(trans.about.github, APP_INFO.repository),
|
|
456
|
-
link(trans.about.website, URLS.homepage),
|
|
457
|
-
link(trans.about.email, URLS.email),
|
|
458
|
-
'',
|
|
459
|
-
row(trans.about.license, `MIT | ${trans.about.author}: m1ngsama`),
|
|
460
|
-
].join('\n');
|
|
461
|
-
note(content, trans.about.title);
|
|
486
|
+
showAbout();
|
|
462
487
|
return;
|
|
463
488
|
}
|
|
464
489
|
const mappedUrl = URL_ACTIONS[action];
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
⠀⠀⠀⠀⠀⠀⠀⠙⠳⢤⣀⠂⠱⠄⢄⠈⣍⢍⣭⢩⢁⣤⠢⠞⠀⣉⡤⠞⠋⠀⠀⠀⠀⠀⠀⠀
|
|
18
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠶⠤⣤⣤⣥⣬⣤⣤⠤⠶⠒⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
1
|
+
⢀⣠⡤⠶⠚⢚⣋⢋⣙⠛⡛⠓⠦⢤⣄⡀
|
|
2
|
+
⢀⣤⠞⡋⠁⠘⢤⣑⣩⡥⡭⠭⢥⣙⣑⠁⠴⣊⡙⠳⣤⡀
|
|
3
|
+
⢀⡴⢋⣡⡓⣙⡴⠚⠉⠁ ⠈⠉⠓⠮⣙⢓⣄⡙⢦⡀
|
|
4
|
+
⣠⠞⣰⡄⣡⠚⠁ ⠈⠓⣍⢪⢌⠻⣄
|
|
5
|
+
⢰⠋⢬⢊⣼⠥⠴⠒⠒⠢⠤⡀ ⢀⣀⠤⠤⠒⠢⠤⣄⡈⢳⡱⣡⠙⡆
|
|
6
|
+
⢠⡏⣸⣧⣾⣅ ⢀⣀⣀⣀⡀⠈⠱⡄ ⣰⣮⣄ ⢀⢀⣀⡀ ⠉⢢⣳⡨⠃⢹⡄
|
|
7
|
+
⣼⢰⡤⢿⣿⣿⠞⠉⠁ ⠈⠉⠲⣀⡹⠐⣿⣿⡿⠚⠉⠁⠈⠈⠑⠦⣀⡏⣇⠳⠌⣧
|
|
8
|
+
⡟⢘⡅⡟⠈ ⠁ ⠈ ⢻⠰⡖⢻
|
|
9
|
+
⣧⠨⣁⣧ ⣼⠸⢛⣼
|
|
10
|
+
⢻⠘⢶⢻⡀⢇ ⢸⠁⢀⡏⠈⠉⡟
|
|
11
|
+
⠘⣇⠵⢇⢧⠘⣄ ⢠⠇⢀⡼⢩⡕⣹⠃
|
|
12
|
+
⠸⣆⠹⢌⢣⡈⢢⡀ ⢀⡰⠋⢀⡼⣑⠦⣠⠇
|
|
13
|
+
⠙⢦⡓⢴⡙⢤⡉⠲⣄⡀ ⢀⣀⠴⠋⢀⡤⢫⡌⠋⡴⠋
|
|
14
|
+
⠈⠳⣌⡙⢁⡍⡲⢤⣉⡙⠓⠒⠲⠖⠒⠋⢉⣁⡤⢖⡩⣠⠙⣡⠞⠁
|
|
15
|
+
⠈⠛⢦⣌⡀⠲⢜⠭⡝⢓⣒⣚⢚⣍⡭⡰⠆⠙⣨⡴⠛⠁
|
|
16
|
+
⠈⠙⠓⠲⢥⣥⣅⣉⣩⣤⣤⡬⠖⠚⠋⠁
|
package/dist/main.js
CHANGED
|
@@ -1,52 +1,11 @@
|
|
|
1
1
|
import { runStartup } from './core/logo.js';
|
|
2
|
-
import { clearScreen
|
|
3
|
-
import { showMainMenu } from './core/menu.js';
|
|
4
|
-
import { c } from './core/theme.js';
|
|
2
|
+
import { clearScreen } from './core/ui.js';
|
|
5
3
|
import { enableVimKeys } from './core/vim-keys.js';
|
|
6
|
-
import { checkForUpdate } from './features/update.js';
|
|
7
|
-
import { showEventsPreview } from './features/calendar.js';
|
|
8
4
|
export async function main(options = {}) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
await runStartup();
|
|
16
|
-
}
|
|
17
|
-
// Fire update check in background; events fetch provides natural wait time.
|
|
18
|
-
// Its own AbortController is tied to whichever path finishes first, so a
|
|
19
|
-
// still-in-flight request never keeps the process alive after we're done
|
|
20
|
-
// with (or have stopped caring about) its result.
|
|
21
|
-
const updateAbort = new AbortController();
|
|
22
|
-
const updatePromise = checkForUpdate(updateAbort.signal);
|
|
23
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
24
|
-
// Launch app shell for interactive terminals
|
|
25
|
-
const { runApp } = await import('./app/app.js');
|
|
26
|
-
await runApp();
|
|
27
|
-
updateAbort.abort();
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
// Classic path for non-TTY (CI, pipes, redirects)
|
|
31
|
-
await showEventsPreview();
|
|
32
|
-
try {
|
|
33
|
-
const line = (await import('./features/schedule-view.js')).peekNextClassLine();
|
|
34
|
-
if (line)
|
|
35
|
-
console.log(line);
|
|
36
|
-
}
|
|
37
|
-
catch { /* best effort */ }
|
|
38
|
-
// Update check is very likely done by now; give it a short window if not
|
|
39
|
-
const updateMsg = await Promise.race([
|
|
40
|
-
updatePromise,
|
|
41
|
-
new Promise(r => setTimeout(r, 100, null)),
|
|
42
|
-
]);
|
|
43
|
-
updateAbort.abort();
|
|
44
|
-
if (updateMsg)
|
|
45
|
-
console.log(c.warn(updateMsg));
|
|
46
|
-
await showMainMenu();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
handleGracefulExit(err);
|
|
51
|
-
}
|
|
5
|
+
enableVimKeys();
|
|
6
|
+
clearScreen();
|
|
7
|
+
if (!options.skipLogo)
|
|
8
|
+
await runStartup();
|
|
9
|
+
const { runApp } = await import('./app/app.js');
|
|
10
|
+
await runApp();
|
|
52
11
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbtca/prompt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"nbtca": "
|
|
11
|
-
"nbtca-welcome": "
|
|
10
|
+
"nbtca": "dist/index.js",
|
|
11
|
+
"nbtca-welcome": "dist/index.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
-
"bin/nbtca-welcome.js",
|
|
16
15
|
"README.md",
|
|
17
16
|
"SECURITY.md",
|
|
18
17
|
"LICENSE"
|
|
@@ -23,10 +22,17 @@
|
|
|
23
22
|
"dev:watch": "tsx watch src/index.ts",
|
|
24
23
|
"build": "tsc",
|
|
25
24
|
"postbuild": "node scripts/copy-assets.js",
|
|
26
|
-
"clean": "
|
|
25
|
+
"clean": "node scripts/clean.js",
|
|
27
26
|
"prebuild": "npm run clean",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"format": "prettier --write .",
|
|
28
|
+
"format:check": "prettier --check .",
|
|
29
|
+
"lint": "eslint . --max-warnings=0",
|
|
30
|
+
"typecheck": "tsc --project tsconfig.typecheck.json",
|
|
31
|
+
"test": "vitest run && npm run test:cli",
|
|
32
|
+
"test:cli": "npm run build && bash scripts/test-cli.sh",
|
|
33
|
+
"check:package": "node scripts/check-package.mjs",
|
|
34
|
+
"check": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run check:package && npm audit --audit-level=moderate",
|
|
35
|
+
"prepublishOnly": "npm run check"
|
|
30
36
|
},
|
|
31
37
|
"keywords": [
|
|
32
38
|
"cli",
|
|
@@ -39,8 +45,8 @@
|
|
|
39
45
|
"interactive"
|
|
40
46
|
],
|
|
41
47
|
"dependencies": {
|
|
42
|
-
"@nbtca/docs": "^0.
|
|
43
|
-
"@nbtca/nbtcal": "^0.
|
|
48
|
+
"@nbtca/docs": "^0.3.0",
|
|
49
|
+
"@nbtca/nbtcal": "^0.4.0",
|
|
44
50
|
"chalk": "^5.6.2",
|
|
45
51
|
"cheerio": "1.0.0",
|
|
46
52
|
"fetch-cookie": "^3.2.0",
|
|
@@ -51,10 +57,16 @@
|
|
|
51
57
|
"tough-cookie": "^6.0.2"
|
|
52
58
|
},
|
|
53
59
|
"devDependencies": {
|
|
54
|
-
"@
|
|
55
|
-
"@types/node": "
|
|
60
|
+
"@eslint/js": "^9.39.5",
|
|
61
|
+
"@types/node": "20.12.12",
|
|
62
|
+
"eslint": "^9.39.5",
|
|
63
|
+
"eslint-config-prettier": "^10.1.8",
|
|
64
|
+
"globals": "^17.9.0",
|
|
65
|
+
"prettier": "^3.9.6",
|
|
56
66
|
"tsx": "^4.23.5",
|
|
57
67
|
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.67.0",
|
|
69
|
+
"vite": "^6.4.3",
|
|
58
70
|
"vitest": "^3.2.7"
|
|
59
71
|
},
|
|
60
72
|
"engines": {
|
|
@@ -67,14 +79,11 @@
|
|
|
67
79
|
},
|
|
68
80
|
"repository": {
|
|
69
81
|
"type": "git",
|
|
70
|
-
"url": "git+https://github.com/nbtca/
|
|
82
|
+
"url": "git+https://github.com/nbtca/Prompt.git"
|
|
71
83
|
},
|
|
72
84
|
"bugs": {
|
|
73
|
-
"url": "https://github.com/nbtca/
|
|
85
|
+
"url": "https://github.com/nbtca/Prompt/issues"
|
|
74
86
|
},
|
|
75
|
-
"homepage": "https://github.com/nbtca/
|
|
76
|
-
"description": "Terminal information system for the NBTCA community"
|
|
77
|
-
"directories": {
|
|
78
|
-
"doc": "docs"
|
|
79
|
-
}
|
|
87
|
+
"homepage": "https://github.com/nbtca/Prompt#readme",
|
|
88
|
+
"description": "Terminal information system for the NBTCA community"
|
|
80
89
|
}
|
package/bin/nbtca-welcome.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { glyph, type, space } from '../theme.js';
|
|
2
|
-
export function screenWidth() {
|
|
3
|
-
return Math.min(process.stdout.columns || 80, 64);
|
|
4
|
-
}
|
|
5
|
-
export function renderScreen(opts) {
|
|
6
|
-
const width = opts.width ?? screenWidth();
|
|
7
|
-
const lines = [];
|
|
8
|
-
if (opts.title) {
|
|
9
|
-
lines.push(space.indent + type.heading(opts.title));
|
|
10
|
-
lines.push(space.indent + type.hint(glyph.rule().repeat(width)));
|
|
11
|
-
}
|
|
12
|
-
lines.push(opts.body);
|
|
13
|
-
if (opts.footer) {
|
|
14
|
-
lines.push('');
|
|
15
|
-
lines.push(space.indent + type.hint(opts.footer));
|
|
16
|
-
}
|
|
17
|
-
return lines.join('\n');
|
|
18
|
-
}
|
package/dist/core/menu.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { runMenu } from './components/menu.js';
|
|
2
|
-
import { type, space, glyph } from './theme.js';
|
|
3
|
-
import { clearScreen } from './ui.js';
|
|
4
|
-
import { showCalendar } from '../features/calendar.js';
|
|
5
|
-
import { showDocsMenu } from '../features/docs.js';
|
|
6
|
-
import { showServiceStatus } from '../features/status.js';
|
|
7
|
-
import { showLinksMenu } from '../features/links.js';
|
|
8
|
-
import { showSettingsMenu } from '../features/settings.js';
|
|
9
|
-
import { showStudentTimetableMenu } from '../features/student-timetable.js';
|
|
10
|
-
import { showSchedule } from '../features/schedule-view.js';
|
|
11
|
-
import { t } from '../i18n/index.js';
|
|
12
|
-
function getMainMenuOptions() {
|
|
13
|
-
const trans = t();
|
|
14
|
-
return [
|
|
15
|
-
{ value: 'events', label: trans.menu.events, hint: trans.menu.eventsDesc || undefined },
|
|
16
|
-
{ value: 'schedule', label: t().timetable.menuEntry },
|
|
17
|
-
{ value: 'timetable', label: trans.menu.timetable, hint: trans.menu.timetableDesc || undefined },
|
|
18
|
-
{ value: 'docs', label: trans.menu.docs, hint: trans.menu.docsDesc || undefined },
|
|
19
|
-
{ value: 'status', label: trans.menu.status, hint: trans.menu.statusDesc || undefined },
|
|
20
|
-
{ value: 'links', label: trans.menu.links, hint: trans.menu.linksDesc || undefined },
|
|
21
|
-
{ value: 'settings', label: trans.menu.settings, hint: trans.menu.settingsDesc || undefined },
|
|
22
|
-
];
|
|
23
|
-
}
|
|
24
|
-
export async function showMainMenu() {
|
|
25
|
-
let first = true;
|
|
26
|
-
while (true) {
|
|
27
|
-
if (!first)
|
|
28
|
-
clearScreen();
|
|
29
|
-
first = false;
|
|
30
|
-
const trans = t();
|
|
31
|
-
const footer = `${glyph.updown()} ${trans.menu.hintMove} ${glyph.enter()} ${trans.menu.hintOpen} q ${trans.menu.hintQuit}`;
|
|
32
|
-
const action = await runMenu({
|
|
33
|
-
title: trans.menu.chooseAction,
|
|
34
|
-
options: getMainMenuOptions(),
|
|
35
|
-
footer,
|
|
36
|
-
});
|
|
37
|
-
if (action === null) {
|
|
38
|
-
console.log(space.indent + type.hint(t().common.goodbye));
|
|
39
|
-
process.exit(0);
|
|
40
|
-
}
|
|
41
|
-
await runMenuAction(action);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
export async function runMenuAction(action) {
|
|
45
|
-
switch (action) {
|
|
46
|
-
case 'events':
|
|
47
|
-
await showCalendar();
|
|
48
|
-
break;
|
|
49
|
-
case 'schedule':
|
|
50
|
-
await showSchedule();
|
|
51
|
-
break;
|
|
52
|
-
case 'timetable':
|
|
53
|
-
await showStudentTimetableMenu();
|
|
54
|
-
break;
|
|
55
|
-
case 'docs':
|
|
56
|
-
await showDocsMenu();
|
|
57
|
-
break;
|
|
58
|
-
case 'status':
|
|
59
|
-
await showServiceStatus();
|
|
60
|
-
break;
|
|
61
|
-
case 'links':
|
|
62
|
-
await showLinksMenu();
|
|
63
|
-
break;
|
|
64
|
-
case 'settings':
|
|
65
|
-
await showSettingsMenu();
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
package/dist/features/links.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import open from 'open';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { runMenu, menuFooter } from '../core/components/menu.js';
|
|
4
|
-
import { createSpinner } from '../core/ui.js';
|
|
5
|
-
import { URLS } from '../config/data.js';
|
|
6
|
-
import { t } from '../i18n/index.js';
|
|
7
|
-
import { enterScreen, breadcrumb } from '../core/transitions.js';
|
|
8
|
-
async function openUrl(url) {
|
|
9
|
-
const trans = t();
|
|
10
|
-
const s = createSpinner(trans.links.opening);
|
|
11
|
-
try {
|
|
12
|
-
await open(url);
|
|
13
|
-
s.stop(trans.links.opened);
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
s.error(trans.links.error);
|
|
17
|
-
console.log(chalk.dim(` ${url}`));
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export async function showLinksMenu() {
|
|
21
|
-
const trans = t();
|
|
22
|
-
await enterScreen(breadcrumb(trans.menu.links));
|
|
23
|
-
const selected = await runMenu({
|
|
24
|
-
title: trans.links.choose,
|
|
25
|
-
options: [
|
|
26
|
-
{ value: URLS.homepage, label: trans.links.website },
|
|
27
|
-
{ value: URLS.github, label: trans.links.github },
|
|
28
|
-
{ value: URLS.roadmap, label: trans.links.roadmap },
|
|
29
|
-
{ value: URLS.repair, label: trans.links.repair },
|
|
30
|
-
],
|
|
31
|
-
footer: menuFooter(),
|
|
32
|
-
});
|
|
33
|
-
if (selected === null)
|
|
34
|
-
return;
|
|
35
|
-
await openUrl(selected);
|
|
36
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const DAY_MS = 86400000;
|
|
2
|
-
export function currentWeekNumber(weekOneMonday, now) {
|
|
3
|
-
const base = new Date(`${weekOneMonday}T00:00:00`);
|
|
4
|
-
const days = Math.floor((now.getTime() - base.getTime()) / DAY_MS);
|
|
5
|
-
return Math.floor(days / 7) + 1;
|
|
6
|
-
}
|
|
7
|
-
export function campusWeekday(now) {
|
|
8
|
-
return ((now.getDay() + 6) % 7) + 1;
|
|
9
|
-
}
|
|
10
|
-
export function meetingsInWeek(meetings, week) {
|
|
11
|
-
return meetings.filter((mtg) => mtg.weeks.includes(week));
|
|
12
|
-
}
|
|
13
|
-
export function meetingsOnDay(meetings, weekday, week) {
|
|
14
|
-
return meetings
|
|
15
|
-
.filter((mtg) => mtg.weekday === weekday && mtg.weeks.includes(week))
|
|
16
|
-
.sort((a, b) => a.startPeriod - b.startPeriod);
|
|
17
|
-
}
|
|
18
|
-
export function periodStartDate(weekOneMonday, week, weekday, period, periods) {
|
|
19
|
-
const p = periods.find((x) => x.period === period);
|
|
20
|
-
if (!p)
|
|
21
|
-
return null;
|
|
22
|
-
const base = new Date(`${weekOneMonday}T00:00:00`);
|
|
23
|
-
const date = new Date(base.getTime() + ((week - 1) * 7 + (weekday - 1)) * DAY_MS);
|
|
24
|
-
const parts = p.start.split(':');
|
|
25
|
-
date.setHours(Number.parseInt(parts[0] ?? '0', 10), Number.parseInt(parts[1] ?? '0', 10), 0, 0);
|
|
26
|
-
return date;
|
|
27
|
-
}
|
|
28
|
-
export function nextMeeting(meetings, periods, weekOneMonday, now) {
|
|
29
|
-
let best = null;
|
|
30
|
-
for (const meeting of meetings) {
|
|
31
|
-
for (const week of meeting.weeks) {
|
|
32
|
-
const start = periodStartDate(weekOneMonday, week, meeting.weekday, meeting.startPeriod, periods);
|
|
33
|
-
if (start && start.getTime() > now.getTime() && (!best || start.getTime() < best.start.getTime())) {
|
|
34
|
-
best = { meeting, start };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return best;
|
|
39
|
-
}
|
|
40
|
-
/** The meeting occupying a grid cell, whether it starts there or is a later
|
|
41
|
-
* period of a meeting that started earlier the same day -- one condition
|
|
42
|
-
* (`startPeriod <= period <= endPeriod`) covers both cases, matching
|
|
43
|
-
* renderWeekGrid's own starting/continuing lookup so "does this cell have a
|
|
44
|
-
* meeting" and "what does the grid actually draw there" never disagree. */
|
|
45
|
-
export function meetingAtCursor(meetings, week, cursor) {
|
|
46
|
-
return meetingsInWeek(meetings, week).find((m) => m.weekday === cursor.weekday && m.startPeriod <= cursor.period && cursor.period <= m.endPeriod) ?? null;
|
|
47
|
-
}
|