@nbtca/prompt 1.4.2 → 1.5.1

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.
Files changed (71) hide show
  1. package/README.md +27 -58
  2. package/SECURITY.md +16 -45
  3. package/dist/app/app.js +167 -64
  4. package/dist/app/chrome.js +67 -50
  5. package/dist/app/fields/list-field.js +12 -25
  6. package/dist/app/fields/text-field.js +3 -8
  7. package/dist/app/frame.js +16 -23
  8. package/dist/app/keys.js +110 -2
  9. package/dist/app/views/docs-render.js +34 -26
  10. package/dist/app/views/docs.js +280 -68
  11. package/dist/app/views/events-render.js +21 -27
  12. package/dist/app/views/events.js +57 -33
  13. package/dist/app/views/home.js +67 -47
  14. package/dist/app/views/schedule-grid-cursor.js +9 -18
  15. package/dist/app/views/schedule-render.js +51 -74
  16. package/dist/app/views/schedule.js +246 -101
  17. package/dist/app/views/settings-render.js +8 -19
  18. package/dist/app/views/settings.js +92 -17
  19. package/dist/auth/cookie-transport.js +31 -32
  20. package/dist/auth/errors.js +3 -1
  21. package/dist/auth/nbt-auth.js +42 -25
  22. package/dist/auth/session-store.js +17 -9
  23. package/dist/cli.js +570 -0
  24. package/dist/config/data.js +9 -11
  25. package/dist/config/preferences.js +21 -7
  26. package/dist/core/calendar-day.js +37 -0
  27. package/dist/core/canvas.js +1 -0
  28. package/dist/core/capabilities.js +6 -3
  29. package/dist/core/components/confirm.js +9 -8
  30. package/dist/core/components/menu.js +64 -38
  31. package/dist/core/components/messages.js +12 -4
  32. package/dist/core/components/painter.js +3 -1
  33. package/dist/core/components/spinner.js +34 -7
  34. package/dist/core/components/text-input.js +24 -18
  35. package/dist/core/icons.js +2 -2
  36. package/dist/core/logo.js +23 -5
  37. package/dist/core/motion.js +25 -19
  38. package/dist/core/text.js +186 -69
  39. package/dist/core/theme.js +0 -28
  40. package/dist/core/transitions.js +2 -2
  41. package/dist/core/ui.js +15 -13
  42. package/dist/core/vim-keys.js +156 -19
  43. package/dist/features/about.js +23 -0
  44. package/dist/features/calendar-heatmap.js +16 -40
  45. package/dist/features/calendar-query.js +1 -2
  46. package/dist/features/calendar-store.js +27 -0
  47. package/dist/features/calendar.js +66 -190
  48. package/dist/features/docs-client.js +225 -0
  49. package/dist/features/docs.js +615 -298
  50. package/dist/features/links.js +44 -29
  51. package/dist/features/schedule-render.js +65 -101
  52. package/dist/features/schedule-store.js +51 -9
  53. package/dist/features/schedule-view.js +46 -213
  54. package/dist/features/status.js +117 -60
  55. package/dist/features/student-timetable.js +74 -97
  56. package/dist/features/theme.js +9 -5
  57. package/dist/features/timetable-sanitize.js +40 -0
  58. package/dist/features/update.js +12 -29
  59. package/dist/i18n/index.js +83 -19
  60. package/dist/i18n/locales/en.json +8 -3
  61. package/dist/i18n/locales/zh.json +8 -3
  62. package/dist/index.js +6 -474
  63. package/dist/logo/ca-dotmatrix.txt +16 -18
  64. package/dist/main.js +7 -48
  65. package/package.json +28 -18
  66. package/bin/nbtca-welcome.js +0 -2
  67. package/dist/core/components/screen.js +0 -18
  68. package/dist/core/menu.js +0 -68
  69. package/dist/features/schedule-query.js +0 -47
  70. package/dist/features/settings.js +0 -127
  71. package/dist/logo/ca-logo.png +0 -0
package/package.json CHANGED
@@ -1,18 +1,17 @@
1
1
  {
2
2
  "name": "@nbtca/prompt",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
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": "bin/nbtca-welcome.js",
11
- "nbtca-welcome": "bin/nbtca-welcome.js"
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,18 @@
23
22
  "dev:watch": "tsx watch src/index.ts",
24
23
  "build": "tsc",
25
24
  "postbuild": "node scripts/copy-assets.js",
26
- "clean": "rm -rf dist",
25
+ "clean": "node scripts/clean.js",
27
26
  "prebuild": "npm run clean",
28
- "prepublishOnly": "npm run build",
29
- "test": "npm run build && vitest run && bash scripts/test-cli.sh"
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
+ "precheck:package": "npm run build",
34
+ "check:package": "node scripts/check-package.mjs",
35
+ "check": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run check:package && npm audit --audit-level=moderate",
36
+ "prepublishOnly": "npm run check"
30
37
  },
31
38
  "keywords": [
32
39
  "cli",
@@ -39,8 +46,8 @@
39
46
  "interactive"
40
47
  ],
41
48
  "dependencies": {
42
- "@nbtca/docs": "^0.2.3",
43
- "@nbtca/nbtcal": "^0.3.7",
49
+ "@nbtca/docs": "^0.3.0",
50
+ "@nbtca/nbtcal": "^0.4.0",
44
51
  "chalk": "^5.6.2",
45
52
  "cheerio": "1.0.0",
46
53
  "fetch-cookie": "^3.2.0",
@@ -51,10 +58,16 @@
51
58
  "tough-cookie": "^6.0.2"
52
59
  },
53
60
  "devDependencies": {
54
- "@types/gradient-string": "^1.1.6",
55
- "@types/node": "^22.20.1",
61
+ "@eslint/js": "^9.39.5",
62
+ "@types/node": "20.12.12",
63
+ "eslint": "^9.39.5",
64
+ "eslint-config-prettier": "^10.1.8",
65
+ "globals": "^17.9.0",
66
+ "prettier": "^3.9.6",
56
67
  "tsx": "^4.23.5",
57
68
  "typescript": "^5.9.3",
69
+ "typescript-eslint": "^8.67.0",
70
+ "vite": "^6.4.3",
58
71
  "vitest": "^3.2.7"
59
72
  },
60
73
  "engines": {
@@ -67,14 +80,11 @@
67
80
  },
68
81
  "repository": {
69
82
  "type": "git",
70
- "url": "git+https://github.com/nbtca/prompt.git"
83
+ "url": "git+https://github.com/nbtca/Prompt.git"
71
84
  },
72
85
  "bugs": {
73
- "url": "https://github.com/nbtca/prompt/issues"
86
+ "url": "https://github.com/nbtca/Prompt/issues"
74
87
  },
75
- "homepage": "https://github.com/nbtca/prompt#readme",
76
- "description": "Terminal information system for the NBTCA community",
77
- "directories": {
78
- "doc": "docs"
79
- }
88
+ "homepage": "https://github.com/nbtca/Prompt#readme",
89
+ "description": "Terminal information system for the NBTCA community"
80
90
  }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import "../dist/index.js";
@@ -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
- }
@@ -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
- }
@@ -1,127 +0,0 @@
1
- import chalk from 'chalk';
2
- import { applyColorModePreference, loadPreferences, resetPreferences, setColorMode, setIconMode, } from '../config/preferences.js';
3
- import { pickIcon } from '../core/icons.js';
4
- import { resetIconCache } from '../core/icons.js';
5
- import { padEndV } from '../core/text.js';
6
- import { success, warning } from '../core/ui.js';
7
- import { APP_INFO, URLS } from '../config/data.js';
8
- import { t, getCurrentLanguage, saveLanguagePreference, clearTranslationCache } from '../i18n/index.js';
9
- import { runMenu, menuFooter } from '../core/components/menu.js';
10
- import { note } from '../core/components/note.js';
11
- import { enterScreen, breadcrumb } from '../core/transitions.js';
12
- function notifyResult(saved, successMsg, warningMsg) {
13
- if (saved) {
14
- success(successMsg);
15
- }
16
- else {
17
- warning(warningMsg);
18
- }
19
- }
20
- function showAbout() {
21
- const trans = t();
22
- const pad = 12;
23
- const row = (label, value) => `${chalk.dim(padEndV(label, pad))}${value}`;
24
- const link = (label, url) => row(label, chalk.cyan(url));
25
- const content = [
26
- row(trans.about.project, APP_INFO.name),
27
- row(trans.about.version, `v${APP_INFO.version}`),
28
- row(trans.about.description, trans.about.descriptionText),
29
- '',
30
- link(trans.about.github, APP_INFO.repository),
31
- link(trans.about.website, URLS.homepage),
32
- link(trans.about.email, URLS.email),
33
- '',
34
- row(trans.about.license, `MIT ${pickIcon('·', '|')} ${trans.about.author}: m1ngsama`),
35
- ].join('\n');
36
- note(content, trans.about.title);
37
- }
38
- export async function showSettingsMenu() {
39
- await enterScreen(breadcrumb(t().menu.settings));
40
- while (true) {
41
- const trans = t();
42
- const prefs = loadPreferences();
43
- const currentLang = getCurrentLanguage();
44
- const footer = menuFooter();
45
- const action = await runMenu({
46
- title: trans.theme.chooseAction,
47
- options: [
48
- { value: 'language', label: trans.language.selectLanguage, hint: currentLang === 'zh' ? trans.language.zh : trans.language.en },
49
- { value: 'icon', label: trans.theme.iconMode, hint: prefs.iconMode },
50
- { value: 'color', label: trans.theme.colorMode, hint: prefs.colorMode },
51
- { value: 'reset', label: trans.theme.resetLabel },
52
- { value: 'about', label: trans.about.title },
53
- ],
54
- footer,
55
- });
56
- if (action === null)
57
- return;
58
- if (action === 'about') {
59
- showAbout();
60
- continue;
61
- }
62
- if (action === 'language') {
63
- const langOptions = [
64
- { value: 'zh', label: trans.language.zh, hint: currentLang === 'zh' ? trans.common.current : undefined },
65
- { value: 'en', label: trans.language.en, hint: currentLang === 'en' ? trans.common.current : undefined },
66
- ];
67
- const language = await runMenu({
68
- title: trans.language.selectLanguage,
69
- options: langOptions,
70
- footer,
71
- initialIndex: Math.max(0, langOptions.findIndex(o => o.value === currentLang)),
72
- });
73
- if (language === null)
74
- continue;
75
- if (language !== currentLang) {
76
- const saved = saveLanguagePreference(language);
77
- clearTranslationCache();
78
- notifyResult(saved, t().language.changed, t().language.changedSessionOnly);
79
- }
80
- continue;
81
- }
82
- if (action === 'icon') {
83
- const iconOptions = [
84
- { value: 'auto', label: trans.theme.modeAuto, hint: prefs.iconMode === 'auto' ? trans.common.current : undefined },
85
- { value: 'ascii', label: trans.theme.modeAscii, hint: prefs.iconMode === 'ascii' ? trans.common.current : undefined },
86
- { value: 'unicode', label: trans.theme.modeUnicode, hint: prefs.iconMode === 'unicode' ? trans.common.current : undefined },
87
- ];
88
- const mode = await runMenu({
89
- title: trans.theme.chooseIconMode,
90
- options: iconOptions,
91
- footer,
92
- initialIndex: Math.max(0, iconOptions.findIndex(o => o.value === prefs.iconMode)),
93
- });
94
- if (mode === null)
95
- continue;
96
- const saved = setIconMode(mode);
97
- resetIconCache();
98
- notifyResult(saved, trans.theme.updated, trans.theme.updatedSessionOnly);
99
- continue;
100
- }
101
- if (action === 'color') {
102
- const colorOptions = [
103
- { value: 'auto', label: trans.theme.modeAuto, hint: prefs.colorMode === 'auto' ? trans.common.current : undefined },
104
- { value: 'on', label: trans.theme.modeOn, hint: prefs.colorMode === 'on' ? trans.common.current : undefined },
105
- { value: 'off', label: trans.theme.modeOff, hint: prefs.colorMode === 'off' ? trans.common.current : undefined },
106
- ];
107
- const mode = await runMenu({
108
- title: trans.theme.chooseColorMode,
109
- options: colorOptions,
110
- footer,
111
- initialIndex: Math.max(0, colorOptions.findIndex(o => o.value === prefs.colorMode)),
112
- });
113
- if (mode === null)
114
- continue;
115
- const saved = setColorMode(mode);
116
- applyColorModePreference(false);
117
- notifyResult(saved, trans.theme.updated, trans.theme.updatedSessionOnly);
118
- continue;
119
- }
120
- if (action === 'reset') {
121
- const saved = resetPreferences();
122
- resetIconCache();
123
- applyColorModePreference(false);
124
- notifyResult(saved, trans.theme.reset, trans.theme.resetSessionOnly);
125
- }
126
- }
127
- }
Binary file