@nbtca/prompt 1.0.23 → 1.0.25
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/config/data.js +5 -1
- package/dist/config/paths.js +24 -0
- package/dist/config/preferences.js +4 -15
- package/dist/core/icons.js +18 -5
- package/dist/core/logo.js +1 -2
- package/dist/core/menu.js +7 -122
- package/dist/core/text.js +23 -14
- package/dist/core/ui.js +16 -1
- package/dist/core/vim-keys.js +11 -6
- package/dist/features/calendar.js +66 -16
- package/dist/features/docs.js +270 -131
- package/dist/features/links.js +37 -0
- package/dist/features/settings.js +120 -0
- package/dist/features/status.js +28 -32
- package/dist/features/theme.js +9 -101
- package/dist/features/update.js +74 -0
- package/dist/i18n/index.js +20 -21
- package/dist/i18n/locales/en.json +43 -54
- package/dist/i18n/locales/zh.json +43 -54
- package/dist/index.js +115 -84
- package/dist/main.js +13 -14
- package/package.json +13 -11
- package/dist/config/data.d.ts +0 -23
- package/dist/config/data.d.ts.map +0 -1
- package/dist/config/data.js.map +0 -1
- package/dist/config/preferences.d.ts +0 -14
- package/dist/config/preferences.d.ts.map +0 -1
- package/dist/config/preferences.js.map +0 -1
- package/dist/config/theme.d.ts +0 -23
- package/dist/config/theme.d.ts.map +0 -1
- package/dist/config/theme.js +0 -25
- package/dist/config/theme.js.map +0 -1
- package/dist/core/icons.d.ts +0 -3
- package/dist/core/icons.d.ts.map +0 -1
- package/dist/core/icons.js.map +0 -1
- package/dist/core/logo.d.ts +0 -9
- package/dist/core/logo.d.ts.map +0 -1
- package/dist/core/logo.js.map +0 -1
- package/dist/core/menu.d.ts +0 -14
- package/dist/core/menu.d.ts.map +0 -1
- package/dist/core/menu.js.map +0 -1
- package/dist/core/text.d.ts +0 -7
- package/dist/core/text.d.ts.map +0 -1
- package/dist/core/text.js.map +0 -1
- package/dist/core/ui.d.ts +0 -38
- package/dist/core/ui.d.ts.map +0 -1
- package/dist/core/ui.js.map +0 -1
- package/dist/core/vim-keys.d.ts +0 -8
- package/dist/core/vim-keys.d.ts.map +0 -1
- package/dist/core/vim-keys.js.map +0 -1
- package/dist/features/calendar.d.ts +0 -29
- package/dist/features/calendar.d.ts.map +0 -1
- package/dist/features/calendar.js.map +0 -1
- package/dist/features/docs.d.ts +0 -8
- package/dist/features/docs.d.ts.map +0 -1
- package/dist/features/docs.js.map +0 -1
- package/dist/features/repair.d.ts +0 -10
- package/dist/features/repair.d.ts.map +0 -1
- package/dist/features/repair.js +0 -29
- package/dist/features/repair.js.map +0 -1
- package/dist/features/status.d.ts +0 -31
- package/dist/features/status.d.ts.map +0 -1
- package/dist/features/status.js.map +0 -1
- package/dist/features/theme.d.ts +0 -8
- package/dist/features/theme.d.ts.map +0 -1
- package/dist/features/theme.js.map +0 -1
- package/dist/features/website.d.ts +0 -30
- package/dist/features/website.d.ts.map +0 -1
- package/dist/features/website.js +0 -48
- package/dist/features/website.js.map +0 -1
- package/dist/i18n/index.d.ts +0 -209
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/i18n/index.js.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/main.d.ts +0 -12
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/types.d.ts +0 -48
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
package/dist/config/data.js
CHANGED
|
@@ -26,6 +26,11 @@ export const URLS = {
|
|
|
26
26
|
calendar: 'https://ical.nbtca.space',
|
|
27
27
|
email: 'contact@nbtca.space',
|
|
28
28
|
};
|
|
29
|
+
export const GITHUB_REPO = {
|
|
30
|
+
owner: 'nbtca',
|
|
31
|
+
repo: 'documents',
|
|
32
|
+
branch: 'main',
|
|
33
|
+
};
|
|
29
34
|
export const APP_INFO = {
|
|
30
35
|
name: 'Prompt',
|
|
31
36
|
version: readPackageVersion(),
|
|
@@ -35,4 +40,3 @@ export const APP_INFO = {
|
|
|
35
40
|
license: 'MIT',
|
|
36
41
|
repository: 'https://github.com/nbtca/prompt'
|
|
37
42
|
};
|
|
38
|
-
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
function getXdgConfigDir() {
|
|
5
|
+
const xdgHome = process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config');
|
|
6
|
+
return join(xdgHome, 'nbtca');
|
|
7
|
+
}
|
|
8
|
+
function getLegacyConfigDir() {
|
|
9
|
+
return join(homedir(), '.nbtca');
|
|
10
|
+
}
|
|
11
|
+
export function getConfigDir() {
|
|
12
|
+
const xdgDir = getXdgConfigDir();
|
|
13
|
+
if (existsSync(xdgDir))
|
|
14
|
+
return xdgDir;
|
|
15
|
+
const legacyDir = getLegacyConfigDir();
|
|
16
|
+
if (existsSync(legacyDir))
|
|
17
|
+
return legacyDir;
|
|
18
|
+
return xdgDir;
|
|
19
|
+
}
|
|
20
|
+
export function getWritableConfigDir() {
|
|
21
|
+
const dir = getXdgConfigDir();
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { getConfigDir, getWritableConfigDir } from './paths.js';
|
|
3
4
|
const DEFAULT_PREFERENCES = {
|
|
4
5
|
iconMode: 'auto',
|
|
5
6
|
colorMode: 'auto',
|
|
6
7
|
};
|
|
7
|
-
function getConfigDir() {
|
|
8
|
-
const homeDir = process.env['HOME'] || process.env['USERPROFILE'] || '';
|
|
9
|
-
return path.join(homeDir, '.nbtca');
|
|
10
|
-
}
|
|
11
8
|
function getPreferencesPath() {
|
|
12
9
|
return path.join(getConfigDir(), 'preferences.json');
|
|
13
10
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
if (!fs.existsSync(configDir)) {
|
|
17
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
18
|
-
}
|
|
11
|
+
function getWritablePreferencesPath() {
|
|
12
|
+
return path.join(getWritableConfigDir(), 'preferences.json');
|
|
19
13
|
}
|
|
20
14
|
export function loadPreferences() {
|
|
21
15
|
try {
|
|
22
16
|
const prefPath = getPreferencesPath();
|
|
23
|
-
if (!fs.existsSync(prefPath)) {
|
|
24
|
-
return { ...DEFAULT_PREFERENCES };
|
|
25
|
-
}
|
|
26
17
|
const raw = JSON.parse(fs.readFileSync(prefPath, 'utf-8'));
|
|
27
18
|
const iconMode = raw.iconMode === 'ascii' || raw.iconMode === 'unicode' || raw.iconMode === 'auto'
|
|
28
19
|
? raw.iconMode
|
|
@@ -38,8 +29,7 @@ export function loadPreferences() {
|
|
|
38
29
|
}
|
|
39
30
|
function savePreferences(preferences) {
|
|
40
31
|
try {
|
|
41
|
-
|
|
42
|
-
fs.writeFileSync(getPreferencesPath(), JSON.stringify(preferences, null, 2));
|
|
32
|
+
fs.writeFileSync(getWritablePreferencesPath(), JSON.stringify(preferences, null, 2));
|
|
43
33
|
return true;
|
|
44
34
|
}
|
|
45
35
|
catch {
|
|
@@ -87,4 +77,3 @@ export function applyColorModePreference(forcePlain) {
|
|
|
87
77
|
delete process.env['NO_COLOR'];
|
|
88
78
|
}
|
|
89
79
|
}
|
|
90
|
-
//# sourceMappingURL=preferences.js.map
|
package/dist/core/icons.js
CHANGED
|
@@ -3,18 +3,31 @@ function localeSupportsUnicode() {
|
|
|
3
3
|
const locale = `${process.env['LC_ALL'] || ''} ${process.env['LANG'] || ''}`.toLowerCase();
|
|
4
4
|
return locale.includes('utf-8') || locale.includes('utf8');
|
|
5
5
|
}
|
|
6
|
+
let cachedUseUnicode = null;
|
|
6
7
|
export function useUnicodeIcons() {
|
|
8
|
+
if (cachedUseUnicode !== null)
|
|
9
|
+
return cachedUseUnicode;
|
|
7
10
|
const configured = resolveIconMode();
|
|
8
|
-
if (configured === 'ascii')
|
|
11
|
+
if (configured === 'ascii') {
|
|
12
|
+
cachedUseUnicode = false;
|
|
9
13
|
return false;
|
|
10
|
-
|
|
14
|
+
}
|
|
15
|
+
if (configured === 'unicode') {
|
|
16
|
+
cachedUseUnicode = true;
|
|
11
17
|
return true;
|
|
18
|
+
}
|
|
12
19
|
const term = (process.env['TERM'] || '').toLowerCase();
|
|
13
|
-
if (!process.stdout.isTTY || term === 'dumb')
|
|
20
|
+
if (!process.stdout.isTTY || term === 'dumb') {
|
|
21
|
+
cachedUseUnicode = false;
|
|
14
22
|
return false;
|
|
15
|
-
|
|
23
|
+
}
|
|
24
|
+
cachedUseUnicode = localeSupportsUnicode();
|
|
25
|
+
return cachedUseUnicode;
|
|
26
|
+
}
|
|
27
|
+
/** Invalidate the cached icon mode (call after theme changes). */
|
|
28
|
+
export function resetIconCache() {
|
|
29
|
+
cachedUseUnicode = null;
|
|
16
30
|
}
|
|
17
31
|
export function pickIcon(unicodeIcon, asciiIcon) {
|
|
18
32
|
return useUnicodeIcons() ? unicodeIcon : asciiIcon;
|
|
19
33
|
}
|
|
20
|
-
//# sourceMappingURL=icons.js.map
|
package/dist/core/logo.js
CHANGED
|
@@ -36,7 +36,7 @@ function printDescription() {
|
|
|
36
36
|
/**
|
|
37
37
|
* Attempt to read and display logo file
|
|
38
38
|
*/
|
|
39
|
-
export
|
|
39
|
+
export function printLogo() {
|
|
40
40
|
if (!process.stdout.isTTY) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
@@ -68,4 +68,3 @@ export async function printLogo() {
|
|
|
68
68
|
printDescription();
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
//# sourceMappingURL=logo.js.map
|
package/dist/core/menu.js
CHANGED
|
@@ -1,39 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimalist menu system
|
|
3
|
-
* Modern @clack/prompts select() with emoji icons and hint text
|
|
4
3
|
*/
|
|
5
|
-
import { select, isCancel, outro
|
|
4
|
+
import { select, isCancel, outro } from '@clack/prompts';
|
|
6
5
|
import chalk from 'chalk';
|
|
7
6
|
import { showCalendar } from '../features/calendar.js';
|
|
8
|
-
import { openRepairService } from '../features/repair.js';
|
|
9
7
|
import { showDocsMenu } from '../features/docs.js';
|
|
10
8
|
import { showServiceStatus } from '../features/status.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { pickIcon } from './icons.js';
|
|
15
|
-
import { padEndV } from './text.js';
|
|
16
|
-
import { APP_INFO, URLS } from '../config/data.js';
|
|
17
|
-
import { t, getCurrentLanguage, setLanguage, clearTranslationCache } from '../i18n/index.js';
|
|
18
|
-
/**
|
|
19
|
-
* Get main menu options — 6 items
|
|
20
|
-
*/
|
|
9
|
+
import { showLinksMenu } from '../features/links.js';
|
|
10
|
+
import { showSettingsMenu } from '../features/settings.js';
|
|
11
|
+
import { t } from '../i18n/index.js';
|
|
21
12
|
function getMainMenuOptions() {
|
|
22
13
|
const trans = t();
|
|
23
14
|
return [
|
|
24
15
|
{ value: 'events', label: trans.menu.events, hint: trans.menu.eventsDesc },
|
|
25
|
-
{ value: 'repair', label: trans.menu.repair, hint: trans.menu.repairDesc },
|
|
26
16
|
{ value: 'docs', label: trans.menu.docs, hint: trans.menu.docsDesc },
|
|
27
17
|
{ value: 'status', label: trans.menu.status, hint: trans.menu.statusDesc },
|
|
28
18
|
{ value: 'links', label: trans.menu.links, hint: trans.menu.linksDesc },
|
|
29
|
-
{ value: '
|
|
30
|
-
{ value: 'theme', label: trans.menu.theme, hint: trans.menu.themeDesc },
|
|
31
|
-
{ value: 'language', label: trans.menu.language, hint: trans.menu.languageDesc },
|
|
19
|
+
{ value: 'settings', label: trans.menu.settings, hint: trans.menu.settingsDesc },
|
|
32
20
|
];
|
|
33
21
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Display main menu — loops until user exits via Ctrl+C
|
|
36
|
-
*/
|
|
37
22
|
export async function showMainMenu() {
|
|
38
23
|
while (true) {
|
|
39
24
|
const trans = t();
|
|
@@ -46,22 +31,13 @@ export async function showMainMenu() {
|
|
|
46
31
|
process.exit(0);
|
|
47
32
|
}
|
|
48
33
|
await runMenuAction(action);
|
|
49
|
-
printNewLine();
|
|
50
|
-
printDivider();
|
|
51
|
-
printNewLine();
|
|
52
34
|
}
|
|
53
35
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Handle user action
|
|
56
|
-
*/
|
|
57
36
|
export async function runMenuAction(action) {
|
|
58
37
|
switch (action) {
|
|
59
38
|
case 'events':
|
|
60
39
|
await showCalendar();
|
|
61
40
|
break;
|
|
62
|
-
case 'repair':
|
|
63
|
-
await openRepairService();
|
|
64
|
-
break;
|
|
65
41
|
case 'docs':
|
|
66
42
|
await showDocsMenu();
|
|
67
43
|
break;
|
|
@@ -71,99 +47,8 @@ export async function runMenuAction(action) {
|
|
|
71
47
|
case 'links':
|
|
72
48
|
await showLinksMenu();
|
|
73
49
|
break;
|
|
74
|
-
case '
|
|
75
|
-
await
|
|
76
|
-
break;
|
|
77
|
-
case 'github':
|
|
78
|
-
await openGithub();
|
|
50
|
+
case 'settings':
|
|
51
|
+
await showSettingsMenu();
|
|
79
52
|
break;
|
|
80
|
-
case 'roadmap':
|
|
81
|
-
await openRoadmap();
|
|
82
|
-
break;
|
|
83
|
-
case 'about':
|
|
84
|
-
showAbout();
|
|
85
|
-
break;
|
|
86
|
-
case 'theme':
|
|
87
|
-
await showThemeMenu();
|
|
88
|
-
break;
|
|
89
|
-
case 'language':
|
|
90
|
-
await showLanguageMenu();
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Links submenu — website, GitHub, roadmap
|
|
96
|
-
*/
|
|
97
|
-
async function showLinksMenu() {
|
|
98
|
-
const trans = t();
|
|
99
|
-
const link = await select({
|
|
100
|
-
message: trans.menu.chooseLink,
|
|
101
|
-
options: [
|
|
102
|
-
{ value: 'website', label: trans.menu.website, hint: 'nbtca.space' },
|
|
103
|
-
{ value: 'github', label: 'GitHub', hint: 'github.com/nbtca' },
|
|
104
|
-
{ value: 'roadmap', label: trans.menu.roadmap, hint: trans.menu.roadmapDesc },
|
|
105
|
-
],
|
|
106
|
-
});
|
|
107
|
-
if (isCancel(link))
|
|
108
|
-
return;
|
|
109
|
-
switch (link) {
|
|
110
|
-
case 'website':
|
|
111
|
-
await openHomepage();
|
|
112
|
-
break;
|
|
113
|
-
case 'github':
|
|
114
|
-
await openGithub();
|
|
115
|
-
break;
|
|
116
|
-
case 'roadmap':
|
|
117
|
-
await openRoadmap();
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Display about information using clack note() box
|
|
123
|
-
*/
|
|
124
|
-
function showAbout() {
|
|
125
|
-
const trans = t();
|
|
126
|
-
const pad = 12;
|
|
127
|
-
const row = (label, value) => `${chalk.dim(padEndV(label, pad))}${value}`;
|
|
128
|
-
const link = (label, url) => row(label, chalk.cyan(url));
|
|
129
|
-
const content = [
|
|
130
|
-
row(trans.about.project, APP_INFO.name),
|
|
131
|
-
row(trans.about.version, `v${APP_INFO.version}`),
|
|
132
|
-
row(trans.about.description, APP_INFO.fullDescription),
|
|
133
|
-
'',
|
|
134
|
-
link(trans.about.github, APP_INFO.repository),
|
|
135
|
-
link(trans.about.website, URLS.homepage),
|
|
136
|
-
link(trans.about.email, URLS.email),
|
|
137
|
-
'',
|
|
138
|
-
row(trans.about.license, `MIT ${pickIcon('·', '|')} Author: m1ngsama`),
|
|
139
|
-
].join('\n');
|
|
140
|
-
note(content, trans.about.title);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Display language selection menu
|
|
144
|
-
*/
|
|
145
|
-
async function showLanguageMenu() {
|
|
146
|
-
const trans = t();
|
|
147
|
-
const currentLang = getCurrentLanguage();
|
|
148
|
-
const language = await select({
|
|
149
|
-
message: trans.language.selectLanguage,
|
|
150
|
-
options: [
|
|
151
|
-
{ value: 'zh', label: trans.language.zh, hint: currentLang === 'zh' ? `${pickIcon('✓', '*')} current` : undefined },
|
|
152
|
-
{ value: 'en', label: trans.language.en, hint: currentLang === 'en' ? `${pickIcon('✓', '*')} current` : undefined },
|
|
153
|
-
],
|
|
154
|
-
initialValue: currentLang,
|
|
155
|
-
});
|
|
156
|
-
if (isCancel(language))
|
|
157
|
-
return;
|
|
158
|
-
if (language !== currentLang) {
|
|
159
|
-
const persisted = setLanguage(language);
|
|
160
|
-
clearTranslationCache();
|
|
161
|
-
if (persisted) {
|
|
162
|
-
success(t().language.changed);
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
warning(t().language.changedSessionOnly);
|
|
166
|
-
}
|
|
167
53
|
}
|
|
168
54
|
}
|
|
169
|
-
//# sourceMappingURL=menu.js.map
|
package/dist/core/text.js
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
/** Width of a single Unicode character: 2 for CJK/fullwidth, 1 otherwise. */
|
|
2
2
|
function charWidth(ch) {
|
|
3
3
|
const cp = ch.codePointAt(0) ?? 0;
|
|
4
|
-
return ((cp >= 0x1100 && cp <= 0x115F) ||
|
|
5
|
-
(cp >= 0x2E80 && cp <= 0x303F) ||
|
|
6
|
-
(cp >= 0x3040 && cp <= 0x33FF) ||
|
|
7
|
-
(cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
8
|
-
(cp >= 0x4E00 && cp <= 0x9FFF) ||
|
|
9
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) ||
|
|
10
|
-
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
11
|
-
(cp >= 0xFE30 && cp <= 0xFE4F) ||
|
|
12
|
-
(cp >= 0xFF00 && cp <= 0xFF60) ||
|
|
13
|
-
(cp >= 0xFFE0 && cp <= 0xFFE6)
|
|
14
|
-
|
|
4
|
+
return ((cp >= 0x1100 && cp <= 0x115F) ||
|
|
5
|
+
(cp >= 0x2E80 && cp <= 0x303F) ||
|
|
6
|
+
(cp >= 0x3040 && cp <= 0x33FF) ||
|
|
7
|
+
(cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
8
|
+
(cp >= 0x4E00 && cp <= 0x9FFF) ||
|
|
9
|
+
(cp >= 0xAC00 && cp <= 0xD7AF) ||
|
|
10
|
+
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
11
|
+
(cp >= 0xFE30 && cp <= 0xFE4F) ||
|
|
12
|
+
(cp >= 0xFF00 && cp <= 0xFF60) ||
|
|
13
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
14
|
+
(cp >= 0x20000 && cp <= 0x2A6DF) ||
|
|
15
|
+
(cp >= 0x2A700 && cp <= 0x2CEAF) ||
|
|
16
|
+
(cp >= 0x2CEB0 && cp <= 0x2EBEF) ||
|
|
17
|
+
(cp >= 0x30000 && cp <= 0x323AF)) ? 2 : 1;
|
|
15
18
|
}
|
|
16
|
-
/**
|
|
19
|
+
/** Strip ANSI escape sequences from a string. */
|
|
20
|
+
// eslint-disable-next-line no-control-regex
|
|
21
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
22
|
+
export function stripAnsi(str) {
|
|
23
|
+
return str.replace(ANSI_RE, '');
|
|
24
|
+
}
|
|
25
|
+
/** Total visual width of a string (CJK characters count as 2, ANSI codes ignored). */
|
|
17
26
|
export function visualWidth(str) {
|
|
27
|
+
const plain = stripAnsi(str);
|
|
18
28
|
let w = 0;
|
|
19
|
-
for (const ch of
|
|
29
|
+
for (const ch of plain)
|
|
20
30
|
w += charWidth(ch);
|
|
21
31
|
return w;
|
|
22
32
|
}
|
|
@@ -40,4 +50,3 @@ export function truncate(str, maxWidth) {
|
|
|
40
50
|
}
|
|
41
51
|
return str.slice(0, i) + '...';
|
|
42
52
|
}
|
|
43
|
-
//# sourceMappingURL=text.js.map
|
package/dist/core/ui.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { log, spinner as clackSpinner } from '@clack/prompts';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { pickIcon } from './icons.js';
|
|
8
|
+
import { t } from '../i18n/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Display success message
|
|
10
11
|
*/
|
|
@@ -62,4 +63,18 @@ export function createSpinner(msg) {
|
|
|
62
63
|
s.start(msg);
|
|
63
64
|
return s;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
+
export function handleGracefulExit(err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err ?? '');
|
|
68
|
+
if (message.includes('SIGINT') || message.includes('User force closed')) {
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.dim(t().common.goodbye));
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
if (message) {
|
|
74
|
+
console.error(message);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.error('Error occurred:', err);
|
|
78
|
+
}
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
package/dist/core/vim-keys.js
CHANGED
|
@@ -4,21 +4,27 @@
|
|
|
4
4
|
* before readline processes them. This avoids patching the keypress layer which
|
|
5
5
|
* breaks in Node.js v25+ (emitKeys generator crash).
|
|
6
6
|
*/
|
|
7
|
-
// Maps single-byte vim keys to terminal escape sequences
|
|
7
|
+
// Maps single-byte vim keys to terminal escape sequences (ranger-style hjkl)
|
|
8
8
|
const VIM_TO_SEQ = {
|
|
9
|
+
h: Buffer.from('\u0003'), // back/cancel (ranger: go to parent)
|
|
9
10
|
j: Buffer.from('\u001b[B'), // down arrow
|
|
10
11
|
k: Buffer.from('\u001b[A'), // up arrow
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
l: Buffer.from('\r'), // enter/confirm (ranger: open/enter)
|
|
13
|
+
g: Buffer.from('\u001b[H'), // home (first item)
|
|
14
|
+
G: Buffer.from('\u001b[F'), // end (last item)
|
|
15
|
+
q: Buffer.from('\u0003'), // quit
|
|
14
16
|
};
|
|
17
|
+
let vimActive = true;
|
|
18
|
+
export function setVimKeysActive(active) {
|
|
19
|
+
vimActive = active;
|
|
20
|
+
}
|
|
15
21
|
export function enableVimKeys() {
|
|
16
22
|
const stdin = process.stdin;
|
|
17
23
|
if (!stdin.isTTY)
|
|
18
24
|
return;
|
|
19
25
|
const originalEmit = stdin.emit.bind(stdin);
|
|
20
26
|
stdin.emit = function (event, ...args) {
|
|
21
|
-
if (event === 'data') {
|
|
27
|
+
if (event === 'data' && vimActive) {
|
|
22
28
|
const chunk = args[0];
|
|
23
29
|
if (Buffer.isBuffer(chunk) && chunk.length === 1) {
|
|
24
30
|
const seq = VIM_TO_SEQ[String.fromCharCode(chunk[0])];
|
|
@@ -29,4 +35,3 @@ export function enableVimKeys() {
|
|
|
29
35
|
return originalEmit(event, ...args);
|
|
30
36
|
};
|
|
31
37
|
}
|
|
32
|
-
//# sourceMappingURL=vim-keys.js.map
|
|
@@ -2,22 +2,27 @@
|
|
|
2
2
|
* ICS calendar module
|
|
3
3
|
* Fetches and renders upcoming events with Unicode box table.
|
|
4
4
|
*/
|
|
5
|
-
import axios from 'axios';
|
|
6
5
|
import ICAL from 'ical.js';
|
|
7
6
|
import chalk from 'chalk';
|
|
8
|
-
import {
|
|
7
|
+
import { select, isCancel } from '@clack/prompts';
|
|
8
|
+
import { info, createSpinner } from '../core/ui.js';
|
|
9
9
|
import { pickIcon } from '../core/icons.js';
|
|
10
10
|
import { padEndV, truncate } from '../core/text.js';
|
|
11
11
|
import { t } from '../i18n/index.js';
|
|
12
|
+
import { APP_INFO, URLS } from '../config/data.js';
|
|
12
13
|
export async function fetchEvents() {
|
|
13
14
|
try {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
17
|
+
const response = await fetch('https://ical.nbtca.space', {
|
|
18
|
+
signal: controller.signal,
|
|
19
|
+
headers: { 'User-Agent': `NBTCA-CLI/${APP_INFO.version}` },
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
throw new Error(`HTTP ${response.status}`);
|
|
24
|
+
const data = await response.text();
|
|
25
|
+
const jcalData = ICAL.parse(data);
|
|
21
26
|
const comp = new ICAL.Component(jcalData);
|
|
22
27
|
const vevents = comp.getAllSubcomponents('vevent');
|
|
23
28
|
const events = [];
|
|
@@ -28,13 +33,14 @@ export async function fetchEvents() {
|
|
|
28
33
|
const startDate = event.startDate.toJSDate();
|
|
29
34
|
if (startDate >= now && startDate <= thirtyDaysLater) {
|
|
30
35
|
const trans = t();
|
|
31
|
-
const untitledEvent = trans.calendar.
|
|
32
|
-
const tbdLocation = trans.calendar.
|
|
36
|
+
const untitledEvent = trans.calendar.untitledEvent;
|
|
37
|
+
const tbdLocation = trans.calendar.tbdLocation;
|
|
33
38
|
events.push({
|
|
34
39
|
date: formatDate(startDate),
|
|
35
40
|
time: formatTime(startDate),
|
|
36
41
|
title: event.summary || untitledEvent,
|
|
37
42
|
location: event.location || tbdLocation,
|
|
43
|
+
description: event.description || '',
|
|
38
44
|
startDate
|
|
39
45
|
});
|
|
40
46
|
}
|
|
@@ -42,8 +48,11 @@ export async function fetchEvents() {
|
|
|
42
48
|
events.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
43
49
|
return events;
|
|
44
50
|
}
|
|
45
|
-
catch {
|
|
46
|
-
|
|
51
|
+
catch (err) {
|
|
52
|
+
const detail = err instanceof Error
|
|
53
|
+
? (err.name === 'AbortError' ? 'Request timed out' : err.message)
|
|
54
|
+
: String(err);
|
|
55
|
+
throw new Error(`${t().calendar.error}: ${detail}`);
|
|
47
56
|
}
|
|
48
57
|
}
|
|
49
58
|
export function serializeEvents(events) {
|
|
@@ -52,12 +61,17 @@ export function serializeEvents(events) {
|
|
|
52
61
|
time: event.time,
|
|
53
62
|
title: event.title,
|
|
54
63
|
location: event.location,
|
|
64
|
+
description: event.description,
|
|
55
65
|
startDateISO: event.startDate.toISOString()
|
|
56
66
|
}));
|
|
57
67
|
}
|
|
58
68
|
function formatDate(date) {
|
|
69
|
+
const now = new Date();
|
|
59
70
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
60
71
|
const day = String(date.getDate()).padStart(2, '0');
|
|
72
|
+
if (date.getFullYear() !== now.getFullYear()) {
|
|
73
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
74
|
+
}
|
|
61
75
|
return `${month}-${day}`;
|
|
62
76
|
}
|
|
63
77
|
function formatTime(date) {
|
|
@@ -73,7 +87,7 @@ export function renderEventsTable(events, options) {
|
|
|
73
87
|
const color = options?.color !== false;
|
|
74
88
|
if (events.length === 0)
|
|
75
89
|
return trans.calendar.noEvents;
|
|
76
|
-
const dateWidth =
|
|
90
|
+
const dateWidth = 16;
|
|
77
91
|
const titleWidth = 30;
|
|
78
92
|
const locationWidth = 16;
|
|
79
93
|
const h = pickIcon('─', '-');
|
|
@@ -108,14 +122,31 @@ export function renderEventsTable(events, options) {
|
|
|
108
122
|
lines.push(dim(bottom));
|
|
109
123
|
return lines.join('\n');
|
|
110
124
|
}
|
|
111
|
-
|
|
125
|
+
function displayEvents(events) {
|
|
112
126
|
if (events.length === 0) {
|
|
113
127
|
info(t().calendar.noEvents);
|
|
114
128
|
return;
|
|
115
129
|
}
|
|
116
130
|
console.log();
|
|
117
131
|
console.log(renderEventsTable(events, { color: true }));
|
|
118
|
-
|
|
132
|
+
console.log(chalk.dim(` ${pickIcon('📅', '[ical]')} ${t().calendar.subscribeHint}: ${URLS.calendar}`));
|
|
133
|
+
console.log();
|
|
134
|
+
}
|
|
135
|
+
async function showEventDetail(event) {
|
|
136
|
+
const trans = t();
|
|
137
|
+
console.log();
|
|
138
|
+
console.log(chalk.bold.cyan(` ${event.title}`));
|
|
139
|
+
console.log(chalk.dim(` ${event.date} ${event.time} ${pickIcon('·', '|')} ${event.location}`));
|
|
140
|
+
if (event.description) {
|
|
141
|
+
console.log();
|
|
142
|
+
const lines = event.description.trim().split('\n');
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
console.log(chalk.white(` ${line}`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.log(chalk.dim(` ${trans.calendar.noDescription}`));
|
|
149
|
+
}
|
|
119
150
|
console.log();
|
|
120
151
|
}
|
|
121
152
|
export async function showCalendar() {
|
|
@@ -125,6 +156,26 @@ export async function showCalendar() {
|
|
|
125
156
|
const events = await fetchEvents();
|
|
126
157
|
s.stop(`${events.length} ${trans.calendar.eventsFound}`);
|
|
127
158
|
displayEvents(events);
|
|
159
|
+
if (events.length > 0) {
|
|
160
|
+
const options = [
|
|
161
|
+
...events.map((e, i) => ({
|
|
162
|
+
value: String(i),
|
|
163
|
+
label: `${e.date} ${e.time} ${e.title}`,
|
|
164
|
+
hint: e.location,
|
|
165
|
+
})),
|
|
166
|
+
{ value: '__back__', label: chalk.dim(trans.common.back) },
|
|
167
|
+
];
|
|
168
|
+
const selected = await select({
|
|
169
|
+
message: trans.calendar.viewDetail,
|
|
170
|
+
options,
|
|
171
|
+
});
|
|
172
|
+
if (!isCancel(selected) && selected !== '__back__') {
|
|
173
|
+
const idx = Number.parseInt(selected, 10);
|
|
174
|
+
const event = events[idx];
|
|
175
|
+
if (event)
|
|
176
|
+
await showEventDetail(event);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
128
179
|
}
|
|
129
180
|
catch {
|
|
130
181
|
s.error(trans.calendar.error);
|
|
@@ -132,4 +183,3 @@ export async function showCalendar() {
|
|
|
132
183
|
console.log();
|
|
133
184
|
}
|
|
134
185
|
}
|
|
135
|
-
//# sourceMappingURL=calendar.js.map
|