@nbtca/prompt 1.5.8 → 1.5.10
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/dist/app/app.js +22 -3
- package/dist/app/chrome.js +2 -1
- package/dist/app/help.js +44 -0
- package/dist/app/views/docs.js +29 -4
- package/dist/app/views/schedule.js +5 -0
- package/dist/auth/cookie-transport.js +5 -0
- package/dist/auth/nbt-auth.js +15 -7
- package/dist/core/logo.js +4 -2
- package/dist/core/motion.js +2 -1
- package/dist/features/docs-store.js +34 -0
- package/dist/features/docs.js +15 -1
- package/dist/features/student-timetable.js +2 -0
- package/dist/i18n/locales/en.json +16 -0
- package/dist/i18n/locales/zh.json +16 -0
- package/package.json +2 -2
package/dist/app/app.js
CHANGED
|
@@ -8,6 +8,8 @@ import { docsView } from './views/docs.js';
|
|
|
8
8
|
import { eventsView } from './views/events.js';
|
|
9
9
|
import { settingsView } from './views/settings.js';
|
|
10
10
|
import { getAppTabs } from './tabs.js';
|
|
11
|
+
import { renderHelp } from './help.js';
|
|
12
|
+
import { t } from '../i18n/index.js';
|
|
11
13
|
import { SPINNER_FRAME_MS } from '../core/components/spinner.js';
|
|
12
14
|
export async function runApp() {
|
|
13
15
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
@@ -22,6 +24,7 @@ export async function runApp() {
|
|
|
22
24
|
let keyFlushTimer;
|
|
23
25
|
let clockTimer;
|
|
24
26
|
let busyTimer;
|
|
27
|
+
let helpOpen = false;
|
|
25
28
|
let painted;
|
|
26
29
|
let lastBody = { length: 0, height: 0 };
|
|
27
30
|
const viewIds = getAppTabs().map((tab) => tab.id);
|
|
@@ -77,11 +80,14 @@ export async function runApp() {
|
|
|
77
80
|
const tabs = getAppTabs();
|
|
78
81
|
const chrome = resolveChromeLayout(rows);
|
|
79
82
|
const header = renderHeader(tabs, view, cols, chrome.headerLines, active?.contextPath?.());
|
|
80
|
-
const
|
|
81
|
-
const
|
|
83
|
+
const activeTab = tabs.find((tab) => tab.id === view);
|
|
84
|
+
const body = helpOpen
|
|
85
|
+
? renderHelp(activeTab?.title ?? '', active?.shortcuts?.() ?? [], tabs.length, cols)
|
|
86
|
+
: (active?.render(ctx) ?? []);
|
|
87
|
+
const bodyScroll = !helpOpen && active?.capturesInput?.() ? Number.MAX_SAFE_INTEGER : scroll;
|
|
82
88
|
const height = computeBodyRows(rows, chrome.headerLines, chrome.footerLines);
|
|
83
89
|
lastBody = { length: body.length, height };
|
|
84
|
-
const footer = renderFooter(view, cols, tabs.length, active?.footerHint?.(tabs.length, cols), chrome.footerLines, active?.scrollsBody?.() === true ? scrollPercent() : undefined);
|
|
90
|
+
const footer = renderFooter(view, cols, tabs.length, helpOpen ? t().help.close : active?.footerHint?.(tabs.length, cols), chrome.footerLines, !helpOpen && active?.scrollsBody?.() === true ? scrollPercent() : undefined);
|
|
85
91
|
const lines = composeFrameLines(header, body, footer, rows, cols, bodyScroll);
|
|
86
92
|
const patch = diffFrame(painted?.cols === cols ? painted.lines : undefined, lines);
|
|
87
93
|
painted = { cols, lines };
|
|
@@ -127,6 +133,19 @@ export async function runApp() {
|
|
|
127
133
|
render();
|
|
128
134
|
return;
|
|
129
135
|
}
|
|
136
|
+
if (helpOpen) {
|
|
137
|
+
if (key === '?' || key === '\x1b') {
|
|
138
|
+
helpOpen = false;
|
|
139
|
+
render();
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (key === '?') {
|
|
144
|
+
helpOpen = true;
|
|
145
|
+
scroll = 0;
|
|
146
|
+
render();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
130
149
|
const g = routeGlobalKey(key, viewIds, view);
|
|
131
150
|
if (g.quit) {
|
|
132
151
|
quit();
|
package/dist/app/chrome.js
CHANGED
|
@@ -112,7 +112,7 @@ export function passiveFooterHint(tabCount, cols = Number.POSITIVE_INFINITY) {
|
|
|
112
112
|
const trans = t();
|
|
113
113
|
const dot = pickIcon('·', '-');
|
|
114
114
|
const compactTabs = tabCount > 1 ? `1-${tabCount}/Tab ${dot} ` : '';
|
|
115
|
-
return fitFooterHint(cols, `${digitTabHint(tabCount)}Esc ${dot} q ${trans.menu.hintQuit}`, `${
|
|
115
|
+
return fitFooterHint(cols, `${digitTabHint(tabCount)}Esc ${dot} q ${trans.menu.hintQuit} ${dot} ${trans.help.hint}`, `${digitTabHint(tabCount)}Esc ${dot} q ${trans.menu.hintQuit}`, `${compactTabs}Esc ${dot} q ${dot} ?`, `Esc ${dot} q ${dot} ?`, `Esc ${dot} q`, 'q');
|
|
116
116
|
}
|
|
117
117
|
function interactiveFooterHint(tabCount, cols) {
|
|
118
118
|
const trans = t();
|
|
@@ -122,6 +122,7 @@ function interactiveFooterHint(tabCount, cols) {
|
|
|
122
122
|
const localFull = `${trans.menu.hintMove} ${dot} ${trans.menu.hintOpen} ${dot} Esc ${dot} q ${trans.menu.hintQuit}`;
|
|
123
123
|
const localCompact = `${trans.menu.hintMove} ${trans.menu.hintOpen} Esc q`;
|
|
124
124
|
const candidates = [
|
|
125
|
+
`${fullTabs}${localFull} ${dot} ${trans.help.hint}`,
|
|
125
126
|
`${fullTabs}${localFull}`,
|
|
126
127
|
`${compactTabs}${localFull}`,
|
|
127
128
|
localFull,
|
package/dist/app/help.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type, space, glyph } from '../core/theme.js';
|
|
2
|
+
import { pickIcon } from '../core/icons.js';
|
|
3
|
+
import { t } from '../i18n/index.js';
|
|
4
|
+
import { padEndV, visualWidth, wrapAnsiWithIndent } from '../core/text.js';
|
|
5
|
+
function row(shortcut, keyWidth, cols) {
|
|
6
|
+
const key = padEndV(type.label(shortcut.key), keyWidth);
|
|
7
|
+
const line = `${space.indent}${space.indent}${key} ${type.hint(shortcut.label)}`;
|
|
8
|
+
if (visualWidth(line) <= cols)
|
|
9
|
+
return [line];
|
|
10
|
+
return wrapAnsiWithIndent(`${type.label(shortcut.key)} ${type.hint(shortcut.label)}`, cols, space.indent + space.indent);
|
|
11
|
+
}
|
|
12
|
+
function group(title, shortcuts, cols) {
|
|
13
|
+
if (shortcuts.length === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const keyWidth = shortcuts.reduce((width, item) => Math.max(width, visualWidth(item.key)), 0);
|
|
16
|
+
return [
|
|
17
|
+
...wrapAnsiWithIndent(type.heading(title), cols, space.indent),
|
|
18
|
+
...shortcuts.flatMap((shortcut) => row(shortcut, keyWidth, cols)),
|
|
19
|
+
'',
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
export function globalShortcuts(tabCount) {
|
|
23
|
+
const trans = t();
|
|
24
|
+
const updown = glyph.updown();
|
|
25
|
+
return [
|
|
26
|
+
...(tabCount > 1 ? [{ key: `1-${String(tabCount)}`, label: trans.help.tabs }] : []),
|
|
27
|
+
{ key: 'Tab', label: trans.help.nextTab },
|
|
28
|
+
{ key: `${updown} / j k`, label: trans.help.scroll },
|
|
29
|
+
{ key: `PgUp/PgDn / ${pickIcon('␣', 'Space')}`, label: trans.help.page },
|
|
30
|
+
{ key: 'Home/End / g G', label: trans.help.ends },
|
|
31
|
+
{ key: pickIcon('⏎', 'Enter'), label: trans.help.open },
|
|
32
|
+
{ key: 'Esc', label: trans.help.back },
|
|
33
|
+
{ key: 'q', label: trans.help.quit },
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
export function renderHelp(viewTitle, viewShortcuts, tabCount, cols) {
|
|
37
|
+
const trans = t();
|
|
38
|
+
return [
|
|
39
|
+
...wrapAnsiWithIndent(type.heading(trans.help.title), cols, space.indent),
|
|
40
|
+
'',
|
|
41
|
+
...group(trans.help.sectionGlobal, globalShortcuts(tabCount), cols),
|
|
42
|
+
...group(viewTitle, viewShortcuts, cols),
|
|
43
|
+
];
|
|
44
|
+
}
|
package/dist/app/views/docs.js
CHANGED
|
@@ -7,7 +7,7 @@ import { pickIcon } from '../../core/icons.js';
|
|
|
7
7
|
import { glyph } from '../../core/theme.js';
|
|
8
8
|
import { fmt, getCurrentLanguage, t } from '../../i18n/index.js';
|
|
9
9
|
import { sanitizeTerminalLine, truncate } from '../../core/text.js';
|
|
10
|
-
import { localizeDocSections, fetchSections, fetchDocMetadata, fetchSectionMetadata, searchDocuments, getArchivedGroups, displayDocTitle, loadDocForReader, openDocsInBrowser, docsUrlFromPath, clearDocsCache, } from '../../features/docs.js';
|
|
10
|
+
import { localizeDocSections, fetchSections, peekSections, fetchDocMetadata, fetchSectionMetadata, searchDocuments, getArchivedGroups, displayDocTitle, loadDocForReader, openDocsInBrowser, docsUrlFromPath, clearDocsCache, } from '../../features/docs.js';
|
|
11
11
|
let state = { mode: 'loading' };
|
|
12
12
|
let sections = [];
|
|
13
13
|
let archivedGroups = new Map();
|
|
@@ -29,6 +29,9 @@ const DOC_HINT_WIDTH = 44;
|
|
|
29
29
|
function isLifecycleActive(ctx, generation) {
|
|
30
30
|
return generation === lifecycleGeneration && ctx.signal?.aborted !== true;
|
|
31
31
|
}
|
|
32
|
+
function sectionsSignature(value) {
|
|
33
|
+
return value.map((section) => `${section.key}:${String(section.files.length)}`).join('|');
|
|
34
|
+
}
|
|
32
35
|
function dedupeAdjacent(segments) {
|
|
33
36
|
return segments.filter((segment, index) => segment !== segments[index - 1]);
|
|
34
37
|
}
|
|
@@ -379,16 +382,27 @@ export const docsView = {
|
|
|
379
382
|
return;
|
|
380
383
|
}
|
|
381
384
|
const requestId = ++sectionsRequestId;
|
|
382
|
-
|
|
385
|
+
const cached = peekSections();
|
|
386
|
+
if (cached) {
|
|
387
|
+
sections = localizeDocSections(cached, t());
|
|
388
|
+
loadedLanguage = getCurrentLanguage();
|
|
389
|
+
goToSections();
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
state = { mode: 'loading' };
|
|
393
|
+
}
|
|
383
394
|
ctx.rerender();
|
|
384
395
|
try {
|
|
385
396
|
const nextSections = await fetchSections(ctx.signal);
|
|
386
397
|
if (!isLifecycleActive(ctx, generation) || requestId !== sectionsRequestId)
|
|
387
398
|
return;
|
|
388
|
-
|
|
399
|
+
const localized = localizeDocSections(nextSections, t());
|
|
400
|
+
const changed = sectionsSignature(localized) !== sectionsSignature(sections);
|
|
401
|
+
sections = localized;
|
|
389
402
|
loaded = true;
|
|
390
403
|
loadedLanguage = getCurrentLanguage();
|
|
391
|
-
|
|
404
|
+
if (!cached || changed || state.mode !== 'sections')
|
|
405
|
+
goToSections();
|
|
392
406
|
}
|
|
393
407
|
catch {
|
|
394
408
|
if (!isLifecycleActive(ctx, generation) || requestId !== sectionsRequestId)
|
|
@@ -451,6 +465,17 @@ export const docsView = {
|
|
|
451
465
|
return undefined;
|
|
452
466
|
}
|
|
453
467
|
},
|
|
468
|
+
shortcuts() {
|
|
469
|
+
const trans = t();
|
|
470
|
+
if (state.mode !== 'reader')
|
|
471
|
+
return [];
|
|
472
|
+
return [
|
|
473
|
+
...((state.readerLinks?.length ?? 0) > 0
|
|
474
|
+
? [{ key: 'f', label: trans.docs.readerLinksHint }]
|
|
475
|
+
: []),
|
|
476
|
+
{ key: 'b', label: trans.docs.openBrowser },
|
|
477
|
+
];
|
|
478
|
+
},
|
|
454
479
|
scrollsBody() {
|
|
455
480
|
return state.mode === 'reader' && state.readerLinksField === undefined;
|
|
456
481
|
},
|
|
@@ -298,6 +298,11 @@ export const scheduleView = {
|
|
|
298
298
|
isBusy() {
|
|
299
299
|
return state.mode === 'loading';
|
|
300
300
|
},
|
|
301
|
+
shortcuts() {
|
|
302
|
+
return state.mode === 'hub' && state.timetable
|
|
303
|
+
? hubShortcuts(state.timetable).map(({ key, label }) => ({ key, label }))
|
|
304
|
+
: [];
|
|
305
|
+
},
|
|
301
306
|
capturesInput() {
|
|
302
307
|
return (state.mode === 'needsLoginId' ||
|
|
303
308
|
state.mode === 'needsLoginPassword' ||
|
|
@@ -12,6 +12,8 @@ const WEBVPN_PATHS = new Set([
|
|
|
12
12
|
'/vpn_key/update',
|
|
13
13
|
]);
|
|
14
14
|
const AUTH_PATHS = new Set(['/authserver/login', '/authserver/checkNeedCaptcha.htl']);
|
|
15
|
+
// CAS parks accounts owing a profile here instead of issuing a ticket.
|
|
16
|
+
const PROFILE_GATE_PATH = '/authserver/improveInfo/improveUserInfo.do';
|
|
15
17
|
const JWXT_EXACT_PATHS = new Set([
|
|
16
18
|
'/sso/jziotlogin',
|
|
17
19
|
'/jwglxt/ticketlogin',
|
|
@@ -92,6 +94,9 @@ export function assertAllowedCampusUrl(url) {
|
|
|
92
94
|
}
|
|
93
95
|
if (hostname === JWXT_HOST && isAllowedJwxtPath(url.pathname))
|
|
94
96
|
return;
|
|
97
|
+
if (hostname === AUTH_HOST && url.pathname === PROFILE_GATE_PATH) {
|
|
98
|
+
throw new AuthError('PROFILE_INCOMPLETE', 'credentials', 'The campus requires this account to complete its profile.');
|
|
99
|
+
}
|
|
95
100
|
throw new AuthError('UNTRUSTED_URL', 'session', 'The campus service URL is not allowed.');
|
|
96
101
|
}
|
|
97
102
|
function safeHeaders(headers) {
|
package/dist/auth/nbt-auth.js
CHANGED
|
@@ -88,19 +88,21 @@ function classifyRejectedLogin(html) {
|
|
|
88
88
|
if (/锁定|冻结|次数过多|稍后再试/.test(visibleError)) {
|
|
89
89
|
return new AuthError('ACCOUNT_LOCKED', 'credentials', 'The campus account is temporarily locked.');
|
|
90
90
|
}
|
|
91
|
-
if (/激活|未启用/.test(visibleError)) {
|
|
92
|
-
return new AuthError('ACCOUNT_INACTIVE', 'credentials', 'The campus account must be activated first.');
|
|
93
|
-
}
|
|
94
91
|
if (/验证码|滑块|captcha/i.test(visibleError)) {
|
|
95
92
|
return new AuthError('INTERACTIVE_CHALLENGE', 'credentials', 'The campus login requires an interactive browser challenge.');
|
|
96
93
|
}
|
|
97
|
-
|
|
94
|
+
// Key `accountLogin_account_pwd_error`; localized it also tells first-time users
|
|
95
|
+
// to activate, so it must be classified before the activation branch below.
|
|
96
|
+
if (/用户名|密码|credential|password|pwd/i.test(visibleError)) {
|
|
98
97
|
return new AuthError('INVALID_CREDENTIALS', 'credentials', 'The student id or password was rejected.');
|
|
99
98
|
}
|
|
99
|
+
if (/激活|未启用/.test(visibleError)) {
|
|
100
|
+
return new AuthError('ACCOUNT_INACTIVE', 'credentials', 'The campus account must be activated first.');
|
|
101
|
+
}
|
|
100
102
|
return new AuthError('UNEXPECTED_RESPONSE', 'credentials', 'Campus login could not be confirmed.');
|
|
101
103
|
}
|
|
102
|
-
async function readText(response, stage) {
|
|
103
|
-
if (response.status < 200 || response.status >= 300) {
|
|
104
|
+
async function readText(response, stage, toleratedStatus) {
|
|
105
|
+
if ((response.status < 200 || response.status >= 300) && response.status !== toleratedStatus) {
|
|
104
106
|
throw new AuthError('HTTP_ERROR', stage, 'The campus service returned an error.', {
|
|
105
107
|
retryable: response.status >= 500,
|
|
106
108
|
});
|
|
@@ -220,15 +222,21 @@ export async function loginWithStudentPassword(username, password, options = {})
|
|
|
220
222
|
method: 'POST',
|
|
221
223
|
headers: {
|
|
222
224
|
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
225
|
+
// No Accept-Language on purpose: unresolved, the campus renders the bare
|
|
226
|
+
// message key, which is the only unambiguous error id this login offers.
|
|
223
227
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
224
228
|
Referer: loginResponse.url,
|
|
225
229
|
},
|
|
226
230
|
body: body.toString(),
|
|
227
231
|
...signalOption(options.signal),
|
|
228
232
|
}, 'credentials');
|
|
229
|
-
|
|
233
|
+
// A rejected login is a 401 carrying the login page; classify it, don't report HTTP_ERROR.
|
|
234
|
+
const credentialHtml = await readText(credentialResponse, 'credentials', 401);
|
|
230
235
|
if (hasLoginFingerprint(credentialHtml))
|
|
231
236
|
throw classifyRejectedLogin(credentialHtml);
|
|
237
|
+
if (credentialResponse.status === 401) {
|
|
238
|
+
throw new AuthError('HTTP_ERROR', 'credentials', 'The campus service returned an error.');
|
|
239
|
+
}
|
|
232
240
|
await verifyJwxtSession(cookies, options.signal);
|
|
233
241
|
const authenticatedAt = (options.now ?? (() => new Date()))().toISOString();
|
|
234
242
|
return createAuthenticatedSession(cookies, {
|
package/dist/core/logo.js
CHANGED
|
@@ -5,7 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import { useUnicodeIcons } from './icons.js';
|
|
6
6
|
import { APP_INFO } from '../config/data.js';
|
|
7
7
|
import { typeReveal, materializeBraille } from './motion.js';
|
|
8
|
-
import { brandGradient as brand } from './theme.js';
|
|
8
|
+
import { brandGradient as brand, c } from './theme.js';
|
|
9
9
|
import { visualWidth } from './text.js';
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const TAGLINE = 'To be at the intersection of technology and liberal arts.';
|
|
@@ -69,7 +69,9 @@ export async function runStartup() {
|
|
|
69
69
|
return;
|
|
70
70
|
const color = !process.env['NO_COLOR'];
|
|
71
71
|
process.stdout.write('\n');
|
|
72
|
-
await materializeBraille(art, (s) => paint(s, color)
|
|
72
|
+
await materializeBraille(art, (s) => paint(s, color), {
|
|
73
|
+
paintProgress: (s) => (color ? c.brand(s) : s),
|
|
74
|
+
});
|
|
73
75
|
await typeReveal([
|
|
74
76
|
'',
|
|
75
77
|
color ? brand(TAGLINE) : TAGLINE,
|
package/dist/core/motion.js
CHANGED
|
@@ -83,7 +83,8 @@ export async function materializeBraille(art, paint, opts = {}) {
|
|
|
83
83
|
}
|
|
84
84
|
shown++;
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
const painter = f === frameCount ? paint : (opts.paintProgress ?? paint);
|
|
87
|
+
write(painter(renderFrame()) + '\n');
|
|
87
88
|
if (f < frameCount) {
|
|
88
89
|
await sleep(frameMs);
|
|
89
90
|
write(ansi.cursorUp(lines.length) + ansi.cursorToCol0 + ansi.eraseDown);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getStateDir, getWritableStateDir } from '../config/paths.js';
|
|
4
|
+
const INDEX_FILE = 'docs-index.json';
|
|
5
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6
|
+
function isDocItem(value) {
|
|
7
|
+
const item = value;
|
|
8
|
+
return (typeof item?.name === 'string' &&
|
|
9
|
+
typeof item.path === 'string' &&
|
|
10
|
+
(item.type === 'file' || item.type === 'dir'));
|
|
11
|
+
}
|
|
12
|
+
export function saveDocsIndex(docs, dir) {
|
|
13
|
+
try {
|
|
14
|
+
fs.writeFileSync(path.join(dir ?? getWritableStateDir(), INDEX_FILE), JSON.stringify(docs), {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
mode: 0o600,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
/* best effort */
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function loadDocsIndex(dir, maxAgeMs = MAX_AGE_MS) {
|
|
24
|
+
try {
|
|
25
|
+
const file = path.join(dir ?? getStateDir(), INDEX_FILE);
|
|
26
|
+
if (Date.now() - fs.statSync(file).mtimeMs > maxAgeMs)
|
|
27
|
+
return null;
|
|
28
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
29
|
+
return Array.isArray(parsed) && parsed.every(isDocItem) ? parsed : null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/features/docs.js
CHANGED
|
@@ -11,6 +11,7 @@ import { spawn, execFileSync } from 'child_process';
|
|
|
11
11
|
import { URLS } from '../config/data.js';
|
|
12
12
|
import { t, fmt, getCurrentLanguage } from '../i18n/index.js';
|
|
13
13
|
import { enterScreen, breadcrumb } from '../core/transitions.js';
|
|
14
|
+
import { loadDocsIndex, saveDocsIndex } from './docs-store.js';
|
|
14
15
|
import { sanitizeTerminalLine, sanitizeTerminalText, stripAnsi, truncate } from '../core/text.js';
|
|
15
16
|
import { clearDocsClients, runDocsClientOperation } from './docs-client.js';
|
|
16
17
|
import { launchBrowserUrl } from './links.js';
|
|
@@ -742,7 +743,20 @@ export async function fetchAllDocs(signal) {
|
|
|
742
743
|
return runDocsClientOperation(signal, (client) => client.listAll());
|
|
743
744
|
}
|
|
744
745
|
export async function fetchSections(signal) {
|
|
745
|
-
|
|
746
|
+
const docs = await fetchAllDocs(signal);
|
|
747
|
+
saveDocsIndex(docs);
|
|
748
|
+
return buildSections(docs);
|
|
749
|
+
}
|
|
750
|
+
export function peekSections() {
|
|
751
|
+
const docs = loadDocsIndex();
|
|
752
|
+
if (!docs)
|
|
753
|
+
return null;
|
|
754
|
+
try {
|
|
755
|
+
return buildSections(docs);
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
746
760
|
}
|
|
747
761
|
async function loadSections() {
|
|
748
762
|
const trans = t();
|
|
@@ -59,6 +59,8 @@ export function safeMessage(error) {
|
|
|
59
59
|
return trans.accountInactive;
|
|
60
60
|
case 'INTERACTIVE_CHALLENGE':
|
|
61
61
|
return trans.challenge;
|
|
62
|
+
case 'PROFILE_INCOMPLETE':
|
|
63
|
+
return trans.profileIncomplete;
|
|
62
64
|
case 'SESSION_EXPIRED':
|
|
63
65
|
return trans.sessionExpired;
|
|
64
66
|
case 'TIMEOUT':
|
|
@@ -12,6 +12,21 @@
|
|
|
12
12
|
"moreAbove": "{count} more above",
|
|
13
13
|
"moreBelow": "{count} more below"
|
|
14
14
|
},
|
|
15
|
+
"help": {
|
|
16
|
+
"title": "Keys",
|
|
17
|
+
"sectionGlobal": "Anywhere",
|
|
18
|
+
"sectionView": "Here",
|
|
19
|
+
"tabs": "Switch tab",
|
|
20
|
+
"nextTab": "Next tab",
|
|
21
|
+
"scroll": "Scroll a line",
|
|
22
|
+
"page": "Scroll a page",
|
|
23
|
+
"ends": "Jump to top or bottom",
|
|
24
|
+
"open": "Open",
|
|
25
|
+
"back": "Back",
|
|
26
|
+
"quit": "Quit",
|
|
27
|
+
"close": "Esc or ? to close",
|
|
28
|
+
"hint": "? keys"
|
|
29
|
+
},
|
|
15
30
|
"menu": {
|
|
16
31
|
"events": "Events",
|
|
17
32
|
"eventsDesc": "",
|
|
@@ -218,6 +233,7 @@
|
|
|
218
233
|
"sessionExpired": "The login session expired. Please sign in again.",
|
|
219
234
|
"timeout": "The school service timed out.",
|
|
220
235
|
"network": "Could not connect to the school service.",
|
|
236
|
+
"profileIncomplete": "The school wants this account's profile completed first; finish it in a browser, then try again.",
|
|
221
237
|
"untrustedUrl": "The school returned an unapproved redirect; the request stopped safely.",
|
|
222
238
|
"httpError": "The school login service returned an error status.",
|
|
223
239
|
"loginChanged": "The school login page changed; credentials were not submitted.",
|
|
@@ -12,6 +12,21 @@
|
|
|
12
12
|
"moreAbove": "上方还有 {count} 项",
|
|
13
13
|
"moreBelow": "下方还有 {count} 项"
|
|
14
14
|
},
|
|
15
|
+
"help": {
|
|
16
|
+
"title": "快捷键",
|
|
17
|
+
"sectionGlobal": "全局",
|
|
18
|
+
"sectionView": "当前页",
|
|
19
|
+
"tabs": "切换标签页",
|
|
20
|
+
"nextTab": "下一个标签页",
|
|
21
|
+
"scroll": "滚动一行",
|
|
22
|
+
"page": "翻页",
|
|
23
|
+
"ends": "跳到开头或结尾",
|
|
24
|
+
"open": "打开",
|
|
25
|
+
"back": "返回",
|
|
26
|
+
"quit": "退出",
|
|
27
|
+
"close": "Esc 或 ? 关闭",
|
|
28
|
+
"hint": "? 快捷键"
|
|
29
|
+
},
|
|
15
30
|
"menu": {
|
|
16
31
|
"events": "活动",
|
|
17
32
|
"eventsDesc": "",
|
|
@@ -218,6 +233,7 @@
|
|
|
218
233
|
"sessionExpired": "登录状态已过期,请重新登录。",
|
|
219
234
|
"timeout": "学校服务响应超时。",
|
|
220
235
|
"network": "无法连接学校服务。",
|
|
236
|
+
"profileIncomplete": "学校要求先补全个人资料;请在浏览器登录统一身份认证完成后再试。",
|
|
221
237
|
"untrustedUrl": "学校返回了未授权的登录跳转,已安全中止。",
|
|
222
238
|
"httpError": "学校登录服务返回了错误状态。",
|
|
223
239
|
"loginChanged": "学校登录页结构已经变化,当前版本未提交凭据。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbtca/prompt",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"typescript": "^5.9.3",
|
|
69
69
|
"typescript-eslint": "^8.67.0",
|
|
70
70
|
"vite": "^6.4.3",
|
|
71
|
-
"vitest": "^
|
|
71
|
+
"vitest": "^4.1.11"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
74
74
|
"node": ">=20.12.0"
|