@nbtca/prompt 1.4.1 → 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/LICENSE +1 -1
- 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 -67
- package/dist/app/views/events-render.js +19 -26
- package/dist/app/views/events.js +44 -31
- package/dist/app/views/home.js +33 -76
- 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 -90
- package/dist/app/views/settings-render.js +8 -19
- package/dist/app/views/settings.js +93 -18
- 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 +10 -13
- 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 +25 -21
- package/dist/core/motion.js +25 -19
- package/dist/core/text.js +182 -75
- package/dist/core/theme.js +0 -28
- package/dist/core/transitions.js +2 -2
- package/dist/core/ui.js +15 -30
- 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 +439 -320
- package/dist/features/schedule-render.js +65 -102
- package/dist/features/schedule-store.js +51 -9
- package/dist/features/schedule-view.js +46 -220
- package/dist/features/status.js +44 -59
- package/dist/features/student-timetable.js +73 -95
- package/dist/features/theme.js +6 -5
- package/dist/features/timetable-sanitize.js +40 -0
- package/dist/features/update.js +9 -37
- package/dist/i18n/index.js +87 -65
- package/dist/i18n/locales/en.json +1 -1
- package/dist/i18n/locales/zh.json +1 -1
- package/dist/index.js +85 -64
- package/dist/logo/ca-dotmatrix.txt +16 -18
- package/dist/main.js +7 -48
- package/package.json +30 -18
- package/bin/nbtca-welcome.js +0 -2
- package/dist/core/components/screen.js +0 -18
- package/dist/core/menu.js +0 -71
- package/dist/features/links.js +0 -39
- package/dist/features/schedule-query.js +0 -47
- package/dist/features/settings.js +0 -130
- package/dist/logo/ca-logo.png +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
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';
|
|
9
10
|
function heading(label) {
|
|
10
11
|
return `${space.indent}${type.heading(label)}`;
|
|
11
12
|
}
|
|
@@ -13,10 +14,7 @@ function hint(label) {
|
|
|
13
14
|
return `${space.indent}${type.hint(label)}`;
|
|
14
15
|
}
|
|
15
16
|
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}`);
|
|
17
|
+
return wrapAnsiWithIndent(style(label), cols, space.indent);
|
|
20
18
|
}
|
|
21
19
|
function headingLines(label, cols) {
|
|
22
20
|
return wrappedIndentedLines(label, cols, type.heading);
|
|
@@ -51,8 +49,12 @@ function renderShortcutLines(shortcuts, cols, compact = false) {
|
|
|
51
49
|
const available = Math.max(1, cols - visualWidth(space.indent));
|
|
52
50
|
const parts = shortcuts.map((shortcut) => {
|
|
53
51
|
const text = compact
|
|
54
|
-
? shortcut.showKey === false
|
|
55
|
-
|
|
52
|
+
? shortcut.showKey === false
|
|
53
|
+
? `[${shortcut.key}] ${shortcut.label}`
|
|
54
|
+
: `[${shortcut.key}]`
|
|
55
|
+
: shortcut.showKey === false
|
|
56
|
+
? `[${shortcut.label}]`
|
|
57
|
+
: `[${shortcut.key}] ${shortcut.label}`;
|
|
56
58
|
return shortcut.warn ? c.warn(text) : type.hint(text);
|
|
57
59
|
});
|
|
58
60
|
const lines = [];
|
|
@@ -76,20 +78,19 @@ function hubPreGridLines(state, now, cols) {
|
|
|
76
78
|
const tt = state.timetable;
|
|
77
79
|
if (!tt || !state.weekOne)
|
|
78
80
|
return null;
|
|
79
|
-
const
|
|
81
|
+
const schedule = createTimetableSchedule(tt, { weekOneMonday: state.weekOne });
|
|
82
|
+
const week = schedule.weekAt(now);
|
|
80
83
|
const lines = [];
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
let next = null;
|
|
85
|
+
try {
|
|
86
|
+
next = schedule.next(now);
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
const banner = renderNextClassBanner(next, now, cols);
|
|
90
|
+
lines.push(...(banner ? [banner] : hintLines(trans.timetable.noNextClass, cols)));
|
|
83
91
|
lines.push('');
|
|
84
|
-
const todayWd =
|
|
92
|
+
const todayWd = schedule.weekdayAt(now);
|
|
85
93
|
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
94
|
lines.push(heading(trans.timetable.termNotStarted));
|
|
94
95
|
lines.push(hint(fmt(trans.timetable.termStartsIn, {
|
|
95
96
|
date: state.weekOne,
|
|
@@ -99,49 +100,32 @@ function hubPreGridLines(state, now, cols) {
|
|
|
99
100
|
lines.push(heading(trans.timetable.termPreviewWeek));
|
|
100
101
|
return { inlineLines: lines, fallbackLines: [...lines], week: 1, tt };
|
|
101
102
|
}
|
|
102
|
-
const today = meetingsOnDay(
|
|
103
|
+
const today = schedule.meetingsOnDay(week, todayWd);
|
|
103
104
|
const weekHeading = heading(trans.timetable.hubWeek);
|
|
104
105
|
const inlineLines = [
|
|
105
106
|
...lines,
|
|
106
|
-
heading(fmt(trans.timetable.todayHeading, {
|
|
107
|
+
heading(fmt(trans.timetable.todayHeading, {
|
|
108
|
+
weekday: weekdayShortLabel(todayWd),
|
|
109
|
+
week: String(week),
|
|
110
|
+
})),
|
|
107
111
|
...renderTodayTimeline(today, tt.periods, now, cols).split('\n'),
|
|
108
112
|
weekHeading,
|
|
109
113
|
];
|
|
110
114
|
return { inlineLines, fallbackLines: [...lines, weekHeading], week, tt };
|
|
111
115
|
}
|
|
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
116
|
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
117
|
function gridFitsInline(precedingLineCount, tt, week, now, bodyRows, cols, cursor, reservedRows) {
|
|
126
118
|
if (cols < MIN_GRID_COLS)
|
|
127
119
|
return false;
|
|
128
|
-
const gridLines = renderWeekGrid(tt
|
|
120
|
+
const gridLines = renderWeekGrid(tt, week, now, cols, cursor).split('\n');
|
|
129
121
|
return precedingLineCount + gridLines.length <= bodyRows - reservedRows;
|
|
130
122
|
}
|
|
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
123
|
function renderAdaptiveWeekGrid(inlineLines, fallbackLines, tt, week, todayWd, now, bodyRows, cols, cursor, reservedRows) {
|
|
140
124
|
if (gridFitsInline(inlineLines.length, tt, week, now, bodyRows, cols, cursor, reservedRows)) {
|
|
141
|
-
return [...inlineLines, ...renderWeekGrid(tt
|
|
125
|
+
return [...inlineLines, ...renderWeekGrid(tt, week, now, cols, cursor).split('\n')];
|
|
142
126
|
}
|
|
143
127
|
const selectedWd = cursor?.weekday ?? todayWd;
|
|
144
|
-
const dayMeetings =
|
|
128
|
+
const dayMeetings = createTimetableSchedule(tt).meetingsOnDay(week, selectedWd);
|
|
145
129
|
return [
|
|
146
130
|
...fallbackLines,
|
|
147
131
|
renderDaySwitcher(selectedWd, todayWd, cols),
|
|
@@ -164,7 +148,7 @@ function renderHubBody(state, now, bodyRows, cols) {
|
|
|
164
148
|
}
|
|
165
149
|
tail.push(...shortcutLines);
|
|
166
150
|
const content = pre
|
|
167
|
-
? renderAdaptiveWeekGrid(pre.inlineLines, pre.fallbackLines, pre.tt, pre.week,
|
|
151
|
+
? renderAdaptiveWeekGrid(pre.inlineLines, pre.fallbackLines, pre.tt, pre.week, createTimetableSchedule(pre.tt).weekdayAt(now), now, rows, cols, state.gridCursor, tail.length)
|
|
168
152
|
: [];
|
|
169
153
|
return { content, tail };
|
|
170
154
|
};
|
|
@@ -174,19 +158,14 @@ function renderHubBody(state, now, bodyRows, cols) {
|
|
|
174
158
|
const compact = build(renderShortcutLines(shortcuts, cols, true));
|
|
175
159
|
if (compact.tail.length >= rows)
|
|
176
160
|
return rows > 0 ? compact.tail.slice(-rows) : [];
|
|
177
|
-
return [
|
|
178
|
-
...compact.content.slice(0, rows - compact.tail.length),
|
|
179
|
-
...compact.tail,
|
|
180
|
-
];
|
|
161
|
+
return [...compact.content.slice(0, rows - compact.tail.length), ...compact.tail];
|
|
181
162
|
}
|
|
182
163
|
const TERM_PROGRESS_WIDTH = 20;
|
|
183
|
-
function renderTermProgressBar(w,
|
|
164
|
+
function renderTermProgressBar(w, cols) {
|
|
184
165
|
if (!w.nextBreakStart)
|
|
185
166
|
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);
|
|
167
|
+
const totalWeeks = Math.max(1, Math.round(localDayDifference(parseLocalMonday(w.weekOneMonday), parseLocalDate(w.nextBreakStart)) / 7));
|
|
168
|
+
const currentWeek = w.currentWeek;
|
|
190
169
|
const labelText = fmt(t().timetable.weekLabel2, { week: `${currentWeek}/${totalWeeks}` });
|
|
191
170
|
const label = type.hint(labelText);
|
|
192
171
|
const width = Number.isFinite(cols) ? Math.max(1, Math.floor(cols)) : Number.POSITIVE_INFINITY;
|
|
@@ -203,7 +182,7 @@ function renderTermProgressBar(w, now, cols) {
|
|
|
203
182
|
: [`${indent}${bar}`, ...hintLines(labelText, cols)];
|
|
204
183
|
}
|
|
205
184
|
function daysBetween(a, b) {
|
|
206
|
-
return Math.max(0,
|
|
185
|
+
return Math.max(0, localDayDifference(a, b));
|
|
207
186
|
}
|
|
208
187
|
function renderPublicBody(state, now, bodyRows, cols) {
|
|
209
188
|
const trans = t();
|
|
@@ -216,18 +195,18 @@ function renderPublicBody(state, now, bodyRows, cols) {
|
|
|
216
195
|
lines.push(...hintLines(trans.timetable.publicUnavailable, cols));
|
|
217
196
|
}
|
|
218
197
|
else if (w.status === 'onBreak') {
|
|
219
|
-
lines.push(...headingLines(fmt(trans.timetable.onBreak, { title: w.breakTitle }), cols));
|
|
198
|
+
lines.push(...headingLines(fmt(trans.timetable.onBreak, { title: sanitizeTerminalLine(w.breakTitle) }), cols));
|
|
220
199
|
}
|
|
221
200
|
else {
|
|
222
201
|
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,
|
|
202
|
+
lines.push(...headingLines(`${fmt(trans.timetable.academicYearSuffix, { year: sanitizeTerminalLine(w.academicYear) })} · ${semesterLabel} · ${fmt(trans.timetable.weekLabel2, { week: String(w.currentWeek) })}`, cols));
|
|
203
|
+
const bar = renderTermProgressBar(w, cols);
|
|
225
204
|
if (bar)
|
|
226
205
|
lines.push(...bar);
|
|
227
206
|
if (w.nextBreakStart && w.nextBreakTitle) {
|
|
228
207
|
lines.push(...hintLines(fmt(trans.timetable.daysUntilBreak, {
|
|
229
|
-
title: w.nextBreakTitle,
|
|
230
|
-
days: String(daysBetween(now,
|
|
208
|
+
title: sanitizeTerminalLine(w.nextBreakTitle),
|
|
209
|
+
days: String(daysBetween(now, parseLocalDate(w.nextBreakStart))),
|
|
231
210
|
}), cols));
|
|
232
211
|
}
|
|
233
212
|
}
|
|
@@ -283,20 +262,17 @@ export function renderSchedule(state, now, bodyRows = 100, cols = 80) {
|
|
|
283
262
|
case 'week': {
|
|
284
263
|
if (!state.timetable || !state.weekOne)
|
|
285
264
|
return [hint(trans.timetable.genericError)];
|
|
286
|
-
const
|
|
265
|
+
const schedule = createTimetableSchedule(state.timetable, {
|
|
266
|
+
weekOneMonday: state.weekOne,
|
|
267
|
+
});
|
|
268
|
+
const week = Math.max(1, schedule.weekAt(now));
|
|
287
269
|
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);
|
|
270
|
+
return renderAdaptiveWeekGrid(weekLines, weekLines, state.timetable, week, schedule.weekdayAt(now), now, bodyRows, cols, state.gridCursor, 2);
|
|
295
271
|
}
|
|
296
272
|
case 'termDensity':
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
273
|
+
if (!state.timetable || !state.weekOne)
|
|
274
|
+
return [hint(trans.timetable.genericError)];
|
|
275
|
+
return renderTermDensity(state.timetable.meetings, state.weekOne, createTimetableSchedule(state.timetable, { weekOneMonday: state.weekOne }).weekAt(now), cols).split('\n');
|
|
300
276
|
case 'termPicker':
|
|
301
277
|
return state.termField?.render(bodyRows, cols) ?? [];
|
|
302
278
|
case 'unresolved':
|