@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.
- package/README.md +27 -58
- package/SECURITY.md +16 -45
- package/dist/app/app.js +167 -64
- 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 +16 -23
- package/dist/app/keys.js +110 -2
- package/dist/app/views/docs-render.js +34 -26
- package/dist/app/views/docs.js +280 -68
- package/dist/app/views/events-render.js +21 -27
- package/dist/app/views/events.js +57 -33
- package/dist/app/views/home.js +67 -47
- package/dist/app/views/schedule-grid-cursor.js +9 -18
- package/dist/app/views/schedule-render.js +51 -74
- package/dist/app/views/schedule.js +246 -101
- 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/cli.js +570 -0
- package/dist/config/data.js +9 -11
- package/dist/config/preferences.js +21 -7
- package/dist/core/calendar-day.js +37 -0
- package/dist/core/canvas.js +1 -0
- package/dist/core/capabilities.js +6 -3
- package/dist/core/components/confirm.js +9 -8
- package/dist/core/components/menu.js +64 -38
- package/dist/core/components/messages.js +12 -4
- package/dist/core/components/painter.js +3 -1
- package/dist/core/components/spinner.js +34 -7
- 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 +186 -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 +156 -19
- 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-store.js +27 -0
- package/dist/features/calendar.js +66 -190
- package/dist/features/docs-client.js +225 -0
- package/dist/features/docs.js +615 -298
- package/dist/features/links.js +44 -29
- 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 +117 -60
- package/dist/features/student-timetable.js +74 -97
- package/dist/features/theme.js +9 -5
- package/dist/features/timetable-sanitize.js +40 -0
- package/dist/features/update.js +12 -29
- package/dist/i18n/index.js +83 -19
- package/dist/i18n/locales/en.json +8 -3
- package/dist/i18n/locales/zh.json +8 -3
- package/dist/index.js +6 -474
- package/dist/logo/ca-dotmatrix.txt +16 -18
- package/dist/main.js +7 -48
- package/package.json +28 -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/schedule-query.js +0 -47
- package/dist/features/settings.js +0 -127
- package/dist/logo/ca-logo.png +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { createTimetableSchedule, } from '@nbtca/nbtcal/timetable';
|
|
1
2
|
import { c, type, space, glyph } from '../../core/theme.js';
|
|
2
3
|
import { pickIcon } from '../../core/icons.js';
|
|
3
4
|
import { t, fmt } from '../../i18n/index.js';
|
|
4
5
|
import { renderListFieldWithContext } from '../fields/list-field.js';
|
|
5
|
-
import { currentWeekNumber, campusWeekday, meetingsOnDay, nextMeeting } from '../../features/schedule-query.js';
|
|
6
6
|
import { renderNextClassBanner, renderWeekGrid, renderUnresolvedItems, renderTodayTimeline, weekdayShortLabel, renderTermDensity, renderMeetingDetail, renderDayTimeline, renderDaySwitcher, } from '../../features/schedule-render.js';
|
|
7
7
|
import { renderEventBrief } from '../../features/calendar.js';
|
|
8
|
-
import { visualWidth,
|
|
8
|
+
import { sanitizeTerminalLine, visualWidth, wrapAnsiWithIndent } from '../../core/text.js';
|
|
9
|
+
import { localDayDifference, parseLocalDate, parseLocalMonday } from '../../core/calendar-day.js';
|
|
10
|
+
import { loadingLines } from '../../core/components/spinner.js';
|
|
9
11
|
function heading(label) {
|
|
10
12
|
return `${space.indent}${type.heading(label)}`;
|
|
11
13
|
}
|
|
@@ -13,10 +15,7 @@ function hint(label) {
|
|
|
13
15
|
return `${space.indent}${type.hint(label)}`;
|
|
14
16
|
}
|
|
15
17
|
function wrappedIndentedLines(label, cols, style) {
|
|
16
|
-
|
|
17
|
-
const indent = visualWidth(space.indent) < width ? space.indent : '';
|
|
18
|
-
const contentWidth = Math.max(1, width - visualWidth(indent));
|
|
19
|
-
return wrapAnsiToVisualWidth(style(label), contentWidth).map((part) => `${indent}${part}`);
|
|
18
|
+
return wrapAnsiWithIndent(style(label), cols, space.indent);
|
|
20
19
|
}
|
|
21
20
|
function headingLines(label, cols) {
|
|
22
21
|
return wrappedIndentedLines(label, cols, type.heading);
|
|
@@ -51,8 +50,12 @@ function renderShortcutLines(shortcuts, cols, compact = false) {
|
|
|
51
50
|
const available = Math.max(1, cols - visualWidth(space.indent));
|
|
52
51
|
const parts = shortcuts.map((shortcut) => {
|
|
53
52
|
const text = compact
|
|
54
|
-
? shortcut.showKey === false
|
|
55
|
-
|
|
53
|
+
? shortcut.showKey === false
|
|
54
|
+
? `[${shortcut.key}] ${shortcut.label}`
|
|
55
|
+
: `[${shortcut.key}]`
|
|
56
|
+
: shortcut.showKey === false
|
|
57
|
+
? `[${shortcut.label}]`
|
|
58
|
+
: `[${shortcut.key}] ${shortcut.label}`;
|
|
56
59
|
return shortcut.warn ? c.warn(text) : type.hint(text);
|
|
57
60
|
});
|
|
58
61
|
const lines = [];
|
|
@@ -76,20 +79,19 @@ function hubPreGridLines(state, now, cols) {
|
|
|
76
79
|
const tt = state.timetable;
|
|
77
80
|
if (!tt || !state.weekOne)
|
|
78
81
|
return null;
|
|
79
|
-
const
|
|
82
|
+
const schedule = createTimetableSchedule(tt, { weekOneMonday: state.weekOne });
|
|
83
|
+
const week = schedule.weekAt(now);
|
|
80
84
|
const lines = [];
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
let next = null;
|
|
86
|
+
try {
|
|
87
|
+
next = schedule.next(now);
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
const banner = renderNextClassBanner(next, now, cols);
|
|
91
|
+
lines.push(...(banner ? [banner] : hintLines(trans.timetable.noNextClass, cols)));
|
|
83
92
|
lines.push('');
|
|
84
|
-
const todayWd =
|
|
93
|
+
const todayWd = schedule.weekdayAt(now);
|
|
85
94
|
if (week < 1) {
|
|
86
|
-
// weekOne can be a *future* date -- auto-inferred while on break, it
|
|
87
|
-
// deliberately points at the upcoming term (see academic-calendar.ts)
|
|
88
|
-
// so it's ready the moment classes start. There is no "today" to show
|
|
89
|
-
// yet, but the timetable's real week-1 data is already fetched -- show
|
|
90
|
-
// it as an explicit preview rather than showing no grid at all
|
|
91
|
-
// regardless of terminal height. The "Week 1 preview" heading keeps it
|
|
92
|
-
// unambiguous that this isn't "happening right now".
|
|
93
95
|
lines.push(heading(trans.timetable.termNotStarted));
|
|
94
96
|
lines.push(hint(fmt(trans.timetable.termStartsIn, {
|
|
95
97
|
date: state.weekOne,
|
|
@@ -99,49 +101,32 @@ function hubPreGridLines(state, now, cols) {
|
|
|
99
101
|
lines.push(heading(trans.timetable.termPreviewWeek));
|
|
100
102
|
return { inlineLines: lines, fallbackLines: [...lines], week: 1, tt };
|
|
101
103
|
}
|
|
102
|
-
const today = meetingsOnDay(
|
|
104
|
+
const today = schedule.meetingsOnDay(week, todayWd);
|
|
103
105
|
const weekHeading = heading(trans.timetable.hubWeek);
|
|
104
106
|
const inlineLines = [
|
|
105
107
|
...lines,
|
|
106
|
-
heading(fmt(trans.timetable.todayHeading, {
|
|
108
|
+
heading(fmt(trans.timetable.todayHeading, {
|
|
109
|
+
weekday: weekdayShortLabel(todayWd),
|
|
110
|
+
week: String(week),
|
|
111
|
+
})),
|
|
107
112
|
...renderTodayTimeline(today, tt.periods, now, cols).split('\n'),
|
|
108
113
|
weekHeading,
|
|
109
114
|
];
|
|
110
115
|
return { inlineLines, fallbackLines: [...lines, weekHeading], week, tt };
|
|
111
116
|
}
|
|
112
|
-
// Below this width, even an all-empty grid's own per-column floor (3, plus
|
|
113
|
-
// row-head and separator overhead) leaves each of the 7 columns too cramped
|
|
114
|
-
// to show real content -- a technically-fitting but practically unreadable
|
|
115
|
-
// grid isn't better than the single-day view, so width gates the decision
|
|
116
|
-
// just as much as height does.
|
|
117
117
|
const MIN_GRID_COLS = 100;
|
|
118
|
-
/** The one place that decides "does the real week grid fit inline (plus a
|
|
119
|
-
* floor reserved for the shortcut bar), or does the hub fall back to the
|
|
120
|
-
* single-day view." Both branches represent the exact same gridCursor, just
|
|
121
|
-
* rendered differently, so unlike the old non-interactive strip fallback
|
|
122
|
-
* this decision no longer needs to be exposed to key handling -- arrow
|
|
123
|
-
* keys/Enter are always meaningful in hub mode regardless of which branch is
|
|
124
|
-
* currently on screen. */
|
|
125
118
|
function gridFitsInline(precedingLineCount, tt, week, now, bodyRows, cols, cursor, reservedRows) {
|
|
126
119
|
if (cols < MIN_GRID_COLS)
|
|
127
120
|
return false;
|
|
128
|
-
const gridLines = renderWeekGrid(tt
|
|
121
|
+
const gridLines = renderWeekGrid(tt, week, now, cols, cursor).split('\n');
|
|
129
122
|
return precedingLineCount + gridLines.length <= bodyRows - reservedRows;
|
|
130
123
|
}
|
|
131
|
-
/** Renders the full weekday x period grid if it (plus a floor reserved for
|
|
132
|
-
* the shortcut bar) fits within bodyRows and cols, otherwise a single day's
|
|
133
|
-
* detailed timeline -- the day the cursor's own weekday points at (today, by
|
|
134
|
-
* default). A large terminal genuinely has no excuse to not show the whole
|
|
135
|
-
* week; a small one is better served by one day shown properly than seven
|
|
136
|
-
* days crammed into unreadable slivers. Shared by the "this week" and "term
|
|
137
|
-
* hasn't started yet, preview week 1" branches of renderHubBody -- the same
|
|
138
|
-
* measure-and-fallback decision, just against a different week number. */
|
|
139
124
|
function renderAdaptiveWeekGrid(inlineLines, fallbackLines, tt, week, todayWd, now, bodyRows, cols, cursor, reservedRows) {
|
|
140
125
|
if (gridFitsInline(inlineLines.length, tt, week, now, bodyRows, cols, cursor, reservedRows)) {
|
|
141
|
-
return [...inlineLines, ...renderWeekGrid(tt
|
|
126
|
+
return [...inlineLines, ...renderWeekGrid(tt, week, now, cols, cursor).split('\n')];
|
|
142
127
|
}
|
|
143
128
|
const selectedWd = cursor?.weekday ?? todayWd;
|
|
144
|
-
const dayMeetings =
|
|
129
|
+
const dayMeetings = createTimetableSchedule(tt).meetingsOnDay(week, selectedWd);
|
|
145
130
|
return [
|
|
146
131
|
...fallbackLines,
|
|
147
132
|
renderDaySwitcher(selectedWd, todayWd, cols),
|
|
@@ -164,7 +149,7 @@ function renderHubBody(state, now, bodyRows, cols) {
|
|
|
164
149
|
}
|
|
165
150
|
tail.push(...shortcutLines);
|
|
166
151
|
const content = pre
|
|
167
|
-
? renderAdaptiveWeekGrid(pre.inlineLines, pre.fallbackLines, pre.tt, pre.week,
|
|
152
|
+
? renderAdaptiveWeekGrid(pre.inlineLines, pre.fallbackLines, pre.tt, pre.week, createTimetableSchedule(pre.tt).weekdayAt(now), now, rows, cols, state.gridCursor, tail.length)
|
|
168
153
|
: [];
|
|
169
154
|
return { content, tail };
|
|
170
155
|
};
|
|
@@ -174,19 +159,14 @@ function renderHubBody(state, now, bodyRows, cols) {
|
|
|
174
159
|
const compact = build(renderShortcutLines(shortcuts, cols, true));
|
|
175
160
|
if (compact.tail.length >= rows)
|
|
176
161
|
return rows > 0 ? compact.tail.slice(-rows) : [];
|
|
177
|
-
return [
|
|
178
|
-
...compact.content.slice(0, rows - compact.tail.length),
|
|
179
|
-
...compact.tail,
|
|
180
|
-
];
|
|
162
|
+
return [...compact.content.slice(0, rows - compact.tail.length), ...compact.tail];
|
|
181
163
|
}
|
|
182
164
|
const TERM_PROGRESS_WIDTH = 20;
|
|
183
|
-
function renderTermProgressBar(w,
|
|
165
|
+
function renderTermProgressBar(w, cols) {
|
|
184
166
|
if (!w.nextBreakStart)
|
|
185
167
|
return null;
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
const totalWeeks = Math.max(1, Math.round((nextBreakMs - weekOneMs) / (7 * 86400000)));
|
|
189
|
-
const currentWeek = currentWeekNumber(w.weekOneMonday, now);
|
|
168
|
+
const totalWeeks = Math.max(1, Math.round(localDayDifference(parseLocalMonday(w.weekOneMonday), parseLocalDate(w.nextBreakStart)) / 7));
|
|
169
|
+
const currentWeek = w.currentWeek;
|
|
190
170
|
const labelText = fmt(t().timetable.weekLabel2, { week: `${currentWeek}/${totalWeeks}` });
|
|
191
171
|
const label = type.hint(labelText);
|
|
192
172
|
const width = Number.isFinite(cols) ? Math.max(1, Math.floor(cols)) : Number.POSITIVE_INFINITY;
|
|
@@ -203,31 +183,31 @@ function renderTermProgressBar(w, now, cols) {
|
|
|
203
183
|
: [`${indent}${bar}`, ...hintLines(labelText, cols)];
|
|
204
184
|
}
|
|
205
185
|
function daysBetween(a, b) {
|
|
206
|
-
return Math.max(0,
|
|
186
|
+
return Math.max(0, localDayDifference(a, b));
|
|
207
187
|
}
|
|
208
188
|
function renderPublicBody(state, now, bodyRows, cols) {
|
|
209
189
|
const trans = t();
|
|
210
190
|
const lines = [];
|
|
211
191
|
const w = state.publicWindow;
|
|
212
192
|
if (w === undefined) {
|
|
213
|
-
lines.push(...
|
|
193
|
+
lines.push(...loadingLines(trans.common.loading, cols));
|
|
214
194
|
}
|
|
215
195
|
else if (w === null) {
|
|
216
196
|
lines.push(...hintLines(trans.timetable.publicUnavailable, cols));
|
|
217
197
|
}
|
|
218
198
|
else if (w.status === 'onBreak') {
|
|
219
|
-
lines.push(...headingLines(fmt(trans.timetable.onBreak, { title: w.breakTitle }), cols));
|
|
199
|
+
lines.push(...headingLines(fmt(trans.timetable.onBreak, { title: sanitizeTerminalLine(w.breakTitle) }), cols));
|
|
220
200
|
}
|
|
221
201
|
else {
|
|
222
202
|
const semesterLabel = w.semester === '1' ? trans.timetable.semester1 : trans.timetable.semester2;
|
|
223
|
-
lines.push(...headingLines(`${fmt(trans.timetable.academicYearSuffix, { year: w.academicYear })} · ${semesterLabel} · ${fmt(trans.timetable.weekLabel2, { week: String(w.currentWeek) })}`, cols));
|
|
224
|
-
const bar = renderTermProgressBar(w,
|
|
203
|
+
lines.push(...headingLines(`${fmt(trans.timetable.academicYearSuffix, { year: sanitizeTerminalLine(w.academicYear) })} · ${semesterLabel} · ${fmt(trans.timetable.weekLabel2, { week: String(w.currentWeek) })}`, cols));
|
|
204
|
+
const bar = renderTermProgressBar(w, cols);
|
|
225
205
|
if (bar)
|
|
226
206
|
lines.push(...bar);
|
|
227
207
|
if (w.nextBreakStart && w.nextBreakTitle) {
|
|
228
208
|
lines.push(...hintLines(fmt(trans.timetable.daysUntilBreak, {
|
|
229
|
-
title: w.nextBreakTitle,
|
|
230
|
-
days: String(daysBetween(now,
|
|
209
|
+
title: sanitizeTerminalLine(w.nextBreakTitle),
|
|
210
|
+
days: String(daysBetween(now, parseLocalDate(w.nextBreakStart))),
|
|
231
211
|
}), cols));
|
|
232
212
|
}
|
|
233
213
|
}
|
|
@@ -261,7 +241,7 @@ export function renderSchedule(state, now, bodyRows = 100, cols = 80) {
|
|
|
261
241
|
const trans = t();
|
|
262
242
|
switch (state.mode) {
|
|
263
243
|
case 'loading':
|
|
264
|
-
return
|
|
244
|
+
return loadingLines(trans.common.loading, cols);
|
|
265
245
|
case 'public':
|
|
266
246
|
return renderPublicBody(state, now, bodyRows, cols);
|
|
267
247
|
case 'needsLoginId':
|
|
@@ -272,7 +252,7 @@ export function renderSchedule(state, now, bodyRows = 100, cols = 80) {
|
|
|
272
252
|
case 'needsLoginPassword':
|
|
273
253
|
return state.passwordField?.render(cols) ?? [];
|
|
274
254
|
case 'authenticating':
|
|
275
|
-
return
|
|
255
|
+
return loadingLines(state.statusMessage ?? trans.common.loading, cols);
|
|
276
256
|
case 'needsWeekOne':
|
|
277
257
|
return [
|
|
278
258
|
...(state.errorMessage ? [...hintLines(state.errorMessage, cols), ''] : []),
|
|
@@ -283,20 +263,17 @@ export function renderSchedule(state, now, bodyRows = 100, cols = 80) {
|
|
|
283
263
|
case 'week': {
|
|
284
264
|
if (!state.timetable || !state.weekOne)
|
|
285
265
|
return [hint(trans.timetable.genericError)];
|
|
286
|
-
const
|
|
266
|
+
const schedule = createTimetableSchedule(state.timetable, {
|
|
267
|
+
weekOneMonday: state.weekOne,
|
|
268
|
+
});
|
|
269
|
+
const week = Math.max(1, schedule.weekAt(now));
|
|
287
270
|
const weekLines = [heading(trans.timetable.hubWeek), ''];
|
|
288
|
-
|
|
289
|
-
// banner/today-section eating into it first, unlike the hub's own
|
|
290
|
-
// inline area) -- reached via the `w` shortcut specifically so a
|
|
291
|
-
// marginal terminal that couldn't fit the grid inline the hub still
|
|
292
|
-
// gets a real shot at it here, falling back to the single-day view
|
|
293
|
-
// only if even the full screen isn't enough.
|
|
294
|
-
return renderAdaptiveWeekGrid(weekLines, weekLines, state.timetable, week, campusWeekday(now), now, bodyRows, cols, state.gridCursor, 2);
|
|
271
|
+
return renderAdaptiveWeekGrid(weekLines, weekLines, state.timetable, week, schedule.weekdayAt(now), now, bodyRows, cols, state.gridCursor, 2);
|
|
295
272
|
}
|
|
296
273
|
case 'termDensity':
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
274
|
+
if (!state.timetable || !state.weekOne)
|
|
275
|
+
return [hint(trans.timetable.genericError)];
|
|
276
|
+
return renderTermDensity(state.timetable.meetings, state.weekOne, createTimetableSchedule(state.timetable, { weekOneMonday: state.weekOne }).weekAt(now), cols).split('\n');
|
|
300
277
|
case 'termPicker':
|
|
301
278
|
return state.termField?.render(bodyRows, cols) ?? [];
|
|
302
279
|
case 'unresolved':
|