@nbtca/prompt 1.3.2 → 1.4.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 +44 -0
- package/SECURITY.md +47 -0
- package/dist/app/app.js +202 -0
- package/dist/app/chrome.js +104 -0
- package/dist/app/fields/list-field.js +174 -0
- package/dist/app/fields/text-field.js +38 -0
- package/dist/app/frame.js +48 -0
- package/dist/app/keys.js +20 -0
- package/dist/app/tabs.js +11 -0
- package/dist/app/view.js +1 -0
- package/dist/app/views/docs-render.js +82 -0
- package/dist/app/views/docs.js +457 -0
- package/dist/app/views/events-render.js +111 -0
- package/dist/app/views/events.js +228 -0
- package/dist/app/views/home.js +236 -0
- package/dist/app/views/schedule-grid-cursor.js +52 -0
- package/dist/app/views/schedule-render.js +317 -0
- package/dist/app/views/schedule.js +472 -0
- package/dist/app/views/settings-render.js +53 -0
- package/dist/app/views/settings.js +153 -0
- package/dist/auth/cookie-transport.js +222 -0
- package/dist/auth/errors.js +18 -0
- package/dist/auth/nbt-auth.js +239 -0
- package/dist/auth/session-store.js +118 -0
- package/dist/config/paths.js +22 -2
- package/dist/core/canvas.js +23 -0
- package/dist/core/capabilities.js +42 -0
- package/dist/core/components/confirm.js +75 -0
- package/dist/core/components/input-session.js +24 -0
- package/dist/core/components/menu.js +122 -0
- package/dist/core/components/messages.js +16 -0
- package/dist/core/components/note.js +18 -0
- package/dist/core/components/painter.js +26 -0
- package/dist/core/components/screen.js +18 -0
- package/dist/core/components/spinner.js +47 -0
- package/dist/core/components/text-input.js +98 -0
- package/dist/core/logo.js +40 -15
- package/dist/core/menu.js +24 -6
- package/dist/core/motion.js +86 -0
- package/dist/core/text.js +127 -5
- package/dist/core/theme.js +61 -0
- package/dist/core/transitions.js +19 -0
- package/dist/core/ui.js +5 -29
- package/dist/features/calendar-heatmap.js +29 -27
- package/dist/features/calendar-query.js +50 -0
- package/dist/features/calendar.js +192 -98
- package/dist/features/docs.js +258 -55
- package/dist/features/links.js +7 -4
- package/dist/features/schedule-query.js +47 -0
- package/dist/features/schedule-render.js +574 -0
- package/dist/features/schedule-store.js +73 -0
- package/dist/features/schedule-view.js +260 -0
- package/dist/features/settings.js +41 -30
- package/dist/features/status.js +37 -13
- package/dist/features/student-timetable.js +346 -0
- package/dist/features/update.js +16 -8
- package/dist/i18n/locales/en.json +149 -6
- package/dist/i18n/locales/zh.json +149 -6
- package/dist/index.js +59 -5
- package/dist/logo/ca-dotmatrix-large.txt +26 -0
- package/dist/logo/ca-dotmatrix-small.txt +12 -0
- package/dist/logo/ca-dotmatrix.txt +18 -16
- package/dist/logo/ca-logo.png +0 -0
- package/dist/main.js +33 -13
- package/package.json +10 -7
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { c, type, space, glyph } from '../../core/theme.js';
|
|
2
|
+
import { pickIcon } from '../../core/icons.js';
|
|
3
|
+
import { t, fmt } from '../../i18n/index.js';
|
|
4
|
+
import { renderListFieldWithContext } from '../fields/list-field.js';
|
|
5
|
+
import { currentWeekNumber, campusWeekday, meetingsOnDay, nextMeeting } from '../../features/schedule-query.js';
|
|
6
|
+
import { renderNextClassBanner, renderWeekGrid, renderUnresolvedItems, renderTodayTimeline, weekdayShortLabel, renderTermDensity, renderMeetingDetail, renderDayTimeline, renderDaySwitcher, } from '../../features/schedule-render.js';
|
|
7
|
+
import { renderEventBrief } from '../../features/calendar.js';
|
|
8
|
+
import { visualWidth, wrapAnsiToVisualWidth } from '../../core/text.js';
|
|
9
|
+
function heading(label) {
|
|
10
|
+
return `${space.indent}${type.heading(label)}`;
|
|
11
|
+
}
|
|
12
|
+
function hint(label) {
|
|
13
|
+
return `${space.indent}${type.hint(label)}`;
|
|
14
|
+
}
|
|
15
|
+
function wrappedIndentedLines(label, cols, style) {
|
|
16
|
+
const width = Number.isFinite(cols) ? Math.max(1, Math.floor(cols)) : Number.POSITIVE_INFINITY;
|
|
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}`);
|
|
20
|
+
}
|
|
21
|
+
function headingLines(label, cols) {
|
|
22
|
+
return wrappedIndentedLines(label, cols, type.heading);
|
|
23
|
+
}
|
|
24
|
+
function hintLines(label, cols) {
|
|
25
|
+
return wrappedIndentedLines(label, cols, type.hint);
|
|
26
|
+
}
|
|
27
|
+
function wrappedRenderedLine(line, cols) {
|
|
28
|
+
const content = line.startsWith(space.indent) ? line.slice(space.indent.length) : line;
|
|
29
|
+
return wrappedIndentedLines(content, cols, (value) => value);
|
|
30
|
+
}
|
|
31
|
+
export function hubShortcuts(tt) {
|
|
32
|
+
const trans = t();
|
|
33
|
+
const shortcuts = [
|
|
34
|
+
{ key: 'w', label: trans.timetable.hubFullGrid },
|
|
35
|
+
{ key: 't', label: trans.timetable.hubTermDensity },
|
|
36
|
+
{ key: 's', label: trans.timetable.hubSwitchTerm },
|
|
37
|
+
{ key: 'e', label: trans.timetable.hubExport },
|
|
38
|
+
];
|
|
39
|
+
if (tt.unresolvedItems.length > 0) {
|
|
40
|
+
shortcuts.push({
|
|
41
|
+
key: 'u',
|
|
42
|
+
label: `${pickIcon('⚠', '!')} ${tt.unresolvedItems.length}`,
|
|
43
|
+
showKey: false,
|
|
44
|
+
warn: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
shortcuts.push({ key: 'x', label: trans.timetable.hubLogout });
|
|
48
|
+
return shortcuts;
|
|
49
|
+
}
|
|
50
|
+
function renderShortcutLines(shortcuts, cols, compact = false) {
|
|
51
|
+
const available = Math.max(1, cols - visualWidth(space.indent));
|
|
52
|
+
const parts = shortcuts.map((shortcut) => {
|
|
53
|
+
const text = compact
|
|
54
|
+
? shortcut.showKey === false ? `[${shortcut.key}] ${shortcut.label}` : `[${shortcut.key}]`
|
|
55
|
+
: shortcut.showKey === false ? `[${shortcut.label}]` : `[${shortcut.key}] ${shortcut.label}`;
|
|
56
|
+
return shortcut.warn ? c.warn(text) : type.hint(text);
|
|
57
|
+
});
|
|
58
|
+
const lines = [];
|
|
59
|
+
let current = '';
|
|
60
|
+
for (const part of parts) {
|
|
61
|
+
const next = current ? `${current} ${part}` : part;
|
|
62
|
+
if (current && visualWidth(next) > available) {
|
|
63
|
+
lines.push(`${space.indent}${current}`);
|
|
64
|
+
current = part;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
current = next;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (current)
|
|
71
|
+
lines.push(`${space.indent}${current}`);
|
|
72
|
+
return lines;
|
|
73
|
+
}
|
|
74
|
+
function hubPreGridLines(state, now, cols) {
|
|
75
|
+
const trans = t();
|
|
76
|
+
const tt = state.timetable;
|
|
77
|
+
if (!tt || !state.weekOne)
|
|
78
|
+
return null;
|
|
79
|
+
const week = currentWeekNumber(state.weekOne, now);
|
|
80
|
+
const lines = [];
|
|
81
|
+
const banner = renderNextClassBanner(nextMeeting(tt.meetings, tt.periods, state.weekOne, now), now, cols);
|
|
82
|
+
lines.push(banner || hint(trans.timetable.noNextClass));
|
|
83
|
+
lines.push('');
|
|
84
|
+
const todayWd = campusWeekday(now);
|
|
85
|
+
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
|
+
lines.push(heading(trans.timetable.termNotStarted));
|
|
94
|
+
lines.push(hint(fmt(trans.timetable.termStartsIn, {
|
|
95
|
+
date: state.weekOne,
|
|
96
|
+
days: String(daysBetween(now, new Date(`${state.weekOne}T00:00:00`))),
|
|
97
|
+
})));
|
|
98
|
+
lines.push('');
|
|
99
|
+
lines.push(heading(trans.timetable.termPreviewWeek));
|
|
100
|
+
return { inlineLines: lines, fallbackLines: [...lines], week: 1, tt };
|
|
101
|
+
}
|
|
102
|
+
const today = meetingsOnDay(tt.meetings, todayWd, week);
|
|
103
|
+
const weekHeading = heading(trans.timetable.hubWeek);
|
|
104
|
+
const inlineLines = [
|
|
105
|
+
...lines,
|
|
106
|
+
heading(fmt(trans.timetable.todayHeading, { weekday: weekdayShortLabel(todayWd), week: String(week) })),
|
|
107
|
+
...renderTodayTimeline(today, tt.periods, now, cols).split('\n'),
|
|
108
|
+
weekHeading,
|
|
109
|
+
];
|
|
110
|
+
return { inlineLines, fallbackLines: [...lines, weekHeading], week, tt };
|
|
111
|
+
}
|
|
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
|
+
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
|
+
function gridFitsInline(precedingLineCount, tt, week, now, bodyRows, cols, cursor, reservedRows) {
|
|
126
|
+
if (cols < MIN_GRID_COLS)
|
|
127
|
+
return false;
|
|
128
|
+
const gridLines = renderWeekGrid(tt.meetings, tt.periods, week, now, cols, cursor).split('\n');
|
|
129
|
+
return precedingLineCount + gridLines.length <= bodyRows - reservedRows;
|
|
130
|
+
}
|
|
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
|
+
function renderAdaptiveWeekGrid(inlineLines, fallbackLines, tt, week, todayWd, now, bodyRows, cols, cursor, reservedRows) {
|
|
140
|
+
if (gridFitsInline(inlineLines.length, tt, week, now, bodyRows, cols, cursor, reservedRows)) {
|
|
141
|
+
return [...inlineLines, ...renderWeekGrid(tt.meetings, tt.periods, week, now, cols, cursor).split('\n')];
|
|
142
|
+
}
|
|
143
|
+
const selectedWd = cursor?.weekday ?? todayWd;
|
|
144
|
+
const dayMeetings = meetingsOnDay(tt.meetings, selectedWd, week);
|
|
145
|
+
return [
|
|
146
|
+
...fallbackLines,
|
|
147
|
+
renderDaySwitcher(selectedWd, todayWd, cols),
|
|
148
|
+
...renderDayTimeline(dayMeetings, tt.periods, now, selectedWd === todayWd, cursor?.period, cols).split('\n'),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
function renderHubBody(state, now, bodyRows, cols) {
|
|
152
|
+
const tt = state.timetable;
|
|
153
|
+
const pre = hubPreGridLines(state, now, cols);
|
|
154
|
+
const shortcuts = tt ? hubShortcuts(tt) : [];
|
|
155
|
+
const rows = Math.max(0, Math.floor(bodyRows));
|
|
156
|
+
const build = (shortcutLines) => {
|
|
157
|
+
const tail = [];
|
|
158
|
+
if (pre && (state.statusMessage || shortcutLines.length > 0))
|
|
159
|
+
tail.push('');
|
|
160
|
+
if (state.statusMessage) {
|
|
161
|
+
tail.push(...hintLines(state.statusMessage, cols));
|
|
162
|
+
if (shortcutLines.length > 0)
|
|
163
|
+
tail.push('');
|
|
164
|
+
}
|
|
165
|
+
tail.push(...shortcutLines);
|
|
166
|
+
const content = pre
|
|
167
|
+
? renderAdaptiveWeekGrid(pre.inlineLines, pre.fallbackLines, pre.tt, pre.week, campusWeekday(now), now, rows, cols, state.gridCursor, tail.length)
|
|
168
|
+
: [];
|
|
169
|
+
return { content, tail };
|
|
170
|
+
};
|
|
171
|
+
const full = build(renderShortcutLines(shortcuts, cols));
|
|
172
|
+
if (full.content.length + full.tail.length <= rows)
|
|
173
|
+
return [...full.content, ...full.tail];
|
|
174
|
+
const compact = build(renderShortcutLines(shortcuts, cols, true));
|
|
175
|
+
if (compact.tail.length >= rows)
|
|
176
|
+
return rows > 0 ? compact.tail.slice(-rows) : [];
|
|
177
|
+
return [
|
|
178
|
+
...compact.content.slice(0, rows - compact.tail.length),
|
|
179
|
+
...compact.tail,
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
const TERM_PROGRESS_WIDTH = 20;
|
|
183
|
+
function renderTermProgressBar(w, now, cols) {
|
|
184
|
+
if (!w.nextBreakStart)
|
|
185
|
+
return null;
|
|
186
|
+
const weekOneMs = new Date(`${w.weekOneMonday}T00:00:00`).getTime();
|
|
187
|
+
const nextBreakMs = new Date(`${w.nextBreakStart}T00:00:00`).getTime();
|
|
188
|
+
const totalWeeks = Math.max(1, Math.round((nextBreakMs - weekOneMs) / (7 * 86400000)));
|
|
189
|
+
const currentWeek = currentWeekNumber(w.weekOneMonday, now);
|
|
190
|
+
const labelText = fmt(t().timetable.weekLabel2, { week: `${currentWeek}/${totalWeeks}` });
|
|
191
|
+
const label = type.hint(labelText);
|
|
192
|
+
const width = Number.isFinite(cols) ? Math.max(1, Math.floor(cols)) : Number.POSITIVE_INFINITY;
|
|
193
|
+
const indent = visualWidth(space.indent) < width ? space.indent : '';
|
|
194
|
+
const contentWidth = Math.max(1, width - visualWidth(indent));
|
|
195
|
+
const inlineBarWidth = Math.min(TERM_PROGRESS_WIDTH, contentWidth - visualWidth(label) - 2);
|
|
196
|
+
const barWidth = inlineBarWidth >= 1 ? inlineBarWidth : Math.min(TERM_PROGRESS_WIDTH, contentWidth);
|
|
197
|
+
const filledCols = Math.max(0, Math.min(barWidth, Math.round((currentWeek / totalWeeks) * barWidth)));
|
|
198
|
+
const filledChar = glyph.barFilled();
|
|
199
|
+
const emptyChar = glyph.barEmpty();
|
|
200
|
+
const bar = type.body(filledChar.repeat(filledCols) + emptyChar.repeat(barWidth - filledCols));
|
|
201
|
+
return inlineBarWidth >= 1
|
|
202
|
+
? [`${indent}${bar} ${label}`]
|
|
203
|
+
: [`${indent}${bar}`, ...hintLines(labelText, cols)];
|
|
204
|
+
}
|
|
205
|
+
function daysBetween(a, b) {
|
|
206
|
+
return Math.max(0, Math.ceil((b.getTime() - a.getTime()) / 86400000));
|
|
207
|
+
}
|
|
208
|
+
function renderPublicBody(state, now, bodyRows, cols) {
|
|
209
|
+
const trans = t();
|
|
210
|
+
const lines = [];
|
|
211
|
+
const w = state.publicWindow;
|
|
212
|
+
if (w === undefined) {
|
|
213
|
+
lines.push(...hintLines(trans.common.loading, cols));
|
|
214
|
+
}
|
|
215
|
+
else if (w === null) {
|
|
216
|
+
lines.push(...hintLines(trans.timetable.publicUnavailable, cols));
|
|
217
|
+
}
|
|
218
|
+
else if (w.status === 'onBreak') {
|
|
219
|
+
lines.push(...headingLines(fmt(trans.timetable.onBreak, { title: w.breakTitle }), cols));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
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, now, cols);
|
|
225
|
+
if (bar)
|
|
226
|
+
lines.push(...bar);
|
|
227
|
+
if (w.nextBreakStart && w.nextBreakTitle) {
|
|
228
|
+
lines.push(...hintLines(fmt(trans.timetable.daysUntilBreak, {
|
|
229
|
+
title: w.nextBreakTitle,
|
|
230
|
+
days: String(daysBetween(now, new Date(`${w.nextBreakStart}T00:00:00`))),
|
|
231
|
+
}), cols));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
lines.push('');
|
|
235
|
+
const loginLines = [...hintLines(trans.timetable.publicLoginHint, cols), ''];
|
|
236
|
+
const rows = Number.isFinite(bodyRows)
|
|
237
|
+
? Math.max(0, Math.floor(bodyRows))
|
|
238
|
+
: Number.POSITIVE_INFINITY;
|
|
239
|
+
const fieldRows = state.publicField
|
|
240
|
+
? Math.min(3, rows, state.publicField.render(Number.POSITIVE_INFINITY, cols).length)
|
|
241
|
+
: 0;
|
|
242
|
+
if (state.publicUpcoming && state.publicUpcoming.length > 0) {
|
|
243
|
+
const activityHeading = headingLines(trans.calendar.recentActivity, cols);
|
|
244
|
+
const eventBudget = Math.max(0, rows - lines.length - activityHeading.length - 1 - loginLines.length - fieldRows);
|
|
245
|
+
const eventLines = [];
|
|
246
|
+
for (const event of state.publicUpcoming) {
|
|
247
|
+
const wrapped = wrappedRenderedLine(renderEventBrief(event, now), cols);
|
|
248
|
+
if (eventLines.length + wrapped.length > eventBudget)
|
|
249
|
+
break;
|
|
250
|
+
eventLines.push(...wrapped);
|
|
251
|
+
}
|
|
252
|
+
if (eventLines.length > 0)
|
|
253
|
+
lines.push(...activityHeading, ...eventLines, '');
|
|
254
|
+
}
|
|
255
|
+
lines.push(...loginLines);
|
|
256
|
+
return state.publicField
|
|
257
|
+
? renderListFieldWithContext(lines, state.publicField, bodyRows, cols)
|
|
258
|
+
: lines;
|
|
259
|
+
}
|
|
260
|
+
export function renderSchedule(state, now, bodyRows = 100, cols = 80) {
|
|
261
|
+
const trans = t();
|
|
262
|
+
switch (state.mode) {
|
|
263
|
+
case 'loading':
|
|
264
|
+
return hintLines(trans.common.loading, cols);
|
|
265
|
+
case 'public':
|
|
266
|
+
return renderPublicBody(state, now, bodyRows, cols);
|
|
267
|
+
case 'needsLoginId':
|
|
268
|
+
return [
|
|
269
|
+
...(state.errorMessage ? [...hintLines(state.errorMessage, cols), ''] : []),
|
|
270
|
+
...(state.idField?.render(cols) ?? []),
|
|
271
|
+
];
|
|
272
|
+
case 'needsLoginPassword':
|
|
273
|
+
return state.passwordField?.render(cols) ?? [];
|
|
274
|
+
case 'authenticating':
|
|
275
|
+
return hintLines(state.statusMessage ?? trans.common.loading, cols);
|
|
276
|
+
case 'needsWeekOne':
|
|
277
|
+
return [
|
|
278
|
+
...(state.errorMessage ? [...hintLines(state.errorMessage, cols), ''] : []),
|
|
279
|
+
...(state.weekOneField?.render(cols) ?? []),
|
|
280
|
+
];
|
|
281
|
+
case 'hub':
|
|
282
|
+
return renderHubBody(state, now, bodyRows, cols);
|
|
283
|
+
case 'week': {
|
|
284
|
+
if (!state.timetable || !state.weekOne)
|
|
285
|
+
return [hint(trans.timetable.genericError)];
|
|
286
|
+
const week = currentWeekNumber(state.weekOne, now);
|
|
287
|
+
const weekLines = [heading(trans.timetable.hubWeek), ''];
|
|
288
|
+
// Standalone week mode gets the *whole* bodyRows to itself (no
|
|
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);
|
|
295
|
+
}
|
|
296
|
+
case 'termDensity':
|
|
297
|
+
return state.timetable && state.weekOne
|
|
298
|
+
? renderTermDensity(state.timetable.meetings, state.weekOne, currentWeekNumber(state.weekOne, now), cols).split('\n')
|
|
299
|
+
: [hint(trans.timetable.genericError)];
|
|
300
|
+
case 'termPicker':
|
|
301
|
+
return state.termField?.render(bodyRows, cols) ?? [];
|
|
302
|
+
case 'unresolved':
|
|
303
|
+
return [
|
|
304
|
+
heading(trans.timetable.unresolvedTitle),
|
|
305
|
+
'',
|
|
306
|
+
...renderUnresolvedItems(state.timetable?.unresolvedItems ?? [], cols).split('\n'),
|
|
307
|
+
];
|
|
308
|
+
case 'meetingDetail':
|
|
309
|
+
return state.detailMeeting && state.timetable
|
|
310
|
+
? renderMeetingDetail(state.detailMeeting, state.timetable.periods, cols).split('\n')
|
|
311
|
+
: [hint(trans.timetable.genericError)];
|
|
312
|
+
case 'error':
|
|
313
|
+
return hintLines(state.errorMessage ?? trans.timetable.genericError, cols);
|
|
314
|
+
default:
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
}
|