@nbtca/prompt 1.0.5 → 1.0.7
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.d.ts +1 -1
- package/dist/config/data.js +1 -1
- package/dist/core/logo.d.ts +3 -3
- package/dist/core/logo.js +40 -40
- package/dist/core/logo.js.map +1 -1
- package/dist/core/menu.d.ts +3 -3
- package/dist/core/menu.d.ts.map +1 -1
- package/dist/core/menu.js +112 -67
- package/dist/core/menu.js.map +1 -1
- package/dist/core/ui.d.ts +11 -11
- package/dist/core/ui.js +15 -15
- package/dist/core/ui.js.map +1 -1
- package/dist/features/calendar.d.ts.map +1 -1
- package/dist/features/calendar.js +21 -15
- package/dist/features/calendar.js.map +1 -1
- package/dist/features/docs.d.ts.map +1 -1
- package/dist/features/docs.js +35 -29
- package/dist/features/docs.js.map +1 -1
- package/dist/features/repair.d.ts.map +1 -1
- package/dist/features/repair.js +6 -10
- package/dist/features/repair.js.map +1 -1
- package/dist/features/website.d.ts +3 -3
- package/dist/features/website.d.ts.map +1 -1
- package/dist/features/website.js +11 -9
- package/dist/features/website.js.map +1 -1
- package/dist/i18n/index.d.ts +140 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +113 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locales/en.json +107 -0
- package/dist/i18n/locales/zh.json +107 -0
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -10
- package/dist/main.js.map +1 -1
- package/package.json +2 -2
- package/src/config/data.ts +1 -1
- package/src/core/logo.ts +40 -40
- package/src/core/menu.ts +119 -67
- package/src/core/ui.ts +15 -15
- package/src/features/calendar.ts +23 -15
- package/src/features/docs.ts +35 -29
- package/src/features/repair.ts +6 -10
- package/src/features/website.ts +11 -9
- package/src/i18n/index.ts +236 -0
- package/src/i18n/locales/en.json +107 -0
- package/src/i18n/locales/zh.json +107 -0
- package/src/main.ts +11 -10
package/src/core/logo.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Smart logo display module
|
|
3
|
+
* Attempts to display iTerm2 image format logo, falls back to ASCII art
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSync } from 'fs';
|
|
@@ -12,20 +12,20 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Create blue-toned gradient effect
|
|
16
16
|
*/
|
|
17
17
|
function createBlueGradient(text: string): string {
|
|
18
|
-
//
|
|
18
|
+
// Blue gradient: deep blue -> sky blue -> cyan
|
|
19
19
|
const blueGradient = gradient([
|
|
20
|
-
{ color: '#1e3a8a', pos: 0 }, //
|
|
21
|
-
{ color: '#0ea5e9', pos: 0.5 }, //
|
|
22
|
-
{ color: '#06b6d4', pos: 1 } //
|
|
20
|
+
{ color: '#1e3a8a', pos: 0 }, // Deep blue
|
|
21
|
+
{ color: '#0ea5e9', pos: 0.5 }, // Sky blue
|
|
22
|
+
{ color: '#06b6d4', pos: 1 } // Cyan
|
|
23
23
|
]);
|
|
24
24
|
return blueGradient(text);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Convert hex color to RGB
|
|
29
29
|
*/
|
|
30
30
|
function hexToRgb(hex: string): [number, number, number] {
|
|
31
31
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
@@ -35,7 +35,7 @@ function hexToRgb(hex: string): [number, number, number] {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* Convert RGB to hex color
|
|
39
39
|
*/
|
|
40
40
|
function rgbToHex(r: number, g: number, b: number): string {
|
|
41
41
|
return '#' + [r, g, b].map(x => {
|
|
@@ -45,7 +45,7 @@ function rgbToHex(r: number, g: number, b: number): string {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Linear interpolation between two colors
|
|
49
49
|
*/
|
|
50
50
|
function interpolateColor(color1: string, color2: string, factor: number): string {
|
|
51
51
|
const [r1, g1, b1] = hexToRgb(color1);
|
|
@@ -59,45 +59,45 @@ function interpolateColor(color1: string, color2: string, factor: number): strin
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* Easing function - smooth in-out effect
|
|
63
63
|
*/
|
|
64
64
|
function easeInOutSine(t: number): number {
|
|
65
65
|
return -(Math.cos(Math.PI * t) - 1) / 2;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Display gradient animation effect (optimized - truly smooth animation)
|
|
70
70
|
*/
|
|
71
71
|
async function animateGradient(text: string, duration: number = 1200): Promise<void> {
|
|
72
|
-
const frames = 60; // 60
|
|
72
|
+
const frames = 60; // 60 frames for truly smooth animation
|
|
73
73
|
const frameDelay = duration / frames;
|
|
74
74
|
|
|
75
|
-
//
|
|
75
|
+
// Define color sequence - forms complete blue spectrum cycle
|
|
76
76
|
const colorSequence = [
|
|
77
|
-
'#1e3a8a', //
|
|
78
|
-
'#2563eb', //
|
|
79
|
-
'#3b82f6', //
|
|
80
|
-
'#0ea5e9', //
|
|
81
|
-
'#06b6d4', //
|
|
82
|
-
'#14b8a6', //
|
|
83
|
-
'#06b6d4', //
|
|
84
|
-
'#0ea5e9', //
|
|
85
|
-
'#3b82f6', //
|
|
86
|
-
'#2563eb', //
|
|
87
|
-
'#1e3a8a', //
|
|
77
|
+
'#1e3a8a', // Deep blue
|
|
78
|
+
'#2563eb', // Blue
|
|
79
|
+
'#3b82f6', // Bright blue
|
|
80
|
+
'#0ea5e9', // Sky blue
|
|
81
|
+
'#06b6d4', // Cyan
|
|
82
|
+
'#14b8a6', // Teal
|
|
83
|
+
'#06b6d4', // Cyan
|
|
84
|
+
'#0ea5e9', // Sky blue
|
|
85
|
+
'#3b82f6', // Bright blue
|
|
86
|
+
'#2563eb', // Blue
|
|
87
|
+
'#1e3a8a', // Deep blue
|
|
88
88
|
];
|
|
89
89
|
|
|
90
90
|
for (let i = 0; i < frames; i++) {
|
|
91
|
-
//
|
|
91
|
+
// Use smooth sine easing
|
|
92
92
|
const progress = easeInOutSine(i / frames);
|
|
93
93
|
|
|
94
|
-
//
|
|
94
|
+
// Calculate position in color sequence
|
|
95
95
|
const position = progress * (colorSequence.length - 1);
|
|
96
96
|
const index1 = Math.floor(position);
|
|
97
97
|
const index2 = Math.min(index1 + 1, colorSequence.length - 1);
|
|
98
98
|
const localProgress = position - index1;
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Interpolate between adjacent colors, generating three smoothly transitioning colors
|
|
101
101
|
const color1 = interpolateColor(
|
|
102
102
|
colorSequence[index1]!,
|
|
103
103
|
colorSequence[index2]!,
|
|
@@ -120,44 +120,44 @@ async function animateGradient(text: string, duration: number = 1200): Promise<v
|
|
|
120
120
|
localProgress
|
|
121
121
|
);
|
|
122
122
|
|
|
123
|
-
//
|
|
123
|
+
// Create gradient for current frame
|
|
124
124
|
const frameGradient = gradient(color1, color2, color3);
|
|
125
125
|
|
|
126
|
-
//
|
|
126
|
+
// Clear current line and display new frame
|
|
127
127
|
process.stdout.write('\r' + frameGradient(text));
|
|
128
128
|
|
|
129
129
|
await new Promise(resolve => setTimeout(resolve, frameDelay));
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Finally display static blue gradient
|
|
133
133
|
process.stdout.write('\r' + createBlueGradient(text) + '\n');
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
*
|
|
137
|
+
* Attempt to read and display logo file
|
|
138
138
|
*/
|
|
139
139
|
export async function printLogo(): Promise<void> {
|
|
140
140
|
try {
|
|
141
|
-
//
|
|
141
|
+
// Try to read iTerm2 image format logo
|
|
142
142
|
const logoPath = join(__dirname, '../logo/logo.txt');
|
|
143
143
|
const logoContent = readFileSync(logoPath, 'utf-8');
|
|
144
144
|
|
|
145
|
-
//
|
|
145
|
+
// If successfully read and content is valid, display directly
|
|
146
146
|
if (logoContent && logoContent.length > 100) {
|
|
147
147
|
console.log(logoContent);
|
|
148
148
|
await printDescription();
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
} catch (error) {
|
|
152
|
-
// iTerm2 logo
|
|
152
|
+
// iTerm2 logo read failed, continue trying ASCII logo
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
//
|
|
155
|
+
// Fallback: display ASCII art logo
|
|
156
156
|
try {
|
|
157
157
|
const asciiLogoPath = join(__dirname, '../logo/ascii-logo.txt');
|
|
158
158
|
const asciiContent = readFileSync(asciiLogoPath, 'utf-8');
|
|
159
159
|
|
|
160
|
-
//
|
|
160
|
+
// Display ASCII logo with gradient colors
|
|
161
161
|
console.log();
|
|
162
162
|
const lines = asciiContent.split('\n').filter(line => line.trim());
|
|
163
163
|
lines.forEach(line => {
|
|
@@ -166,7 +166,7 @@ export async function printLogo(): Promise<void> {
|
|
|
166
166
|
|
|
167
167
|
await printDescription();
|
|
168
168
|
} catch (error) {
|
|
169
|
-
//
|
|
169
|
+
// If ASCII logo also fails, display simple text logo
|
|
170
170
|
console.log();
|
|
171
171
|
console.log(createBlueGradient(' NBTCA'));
|
|
172
172
|
await printDescription();
|
|
@@ -174,14 +174,14 @@ export async function printLogo(): Promise<void> {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
*
|
|
177
|
+
* Display description text (with gradient animation)
|
|
178
178
|
*/
|
|
179
179
|
async function printDescription(): Promise<void> {
|
|
180
180
|
const tagline = 'To be at the intersection of technology and liberal arts.';
|
|
181
181
|
|
|
182
182
|
console.log();
|
|
183
183
|
|
|
184
|
-
//
|
|
184
|
+
// Display gradient animation
|
|
185
185
|
await animateGradient(tagline, 1500);
|
|
186
186
|
|
|
187
187
|
console.log();
|
package/src/core/menu.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Minimalist menu system
|
|
3
|
+
* Six core feature menus
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import inquirer from 'inquirer';
|
|
@@ -11,88 +11,99 @@ import { showDocsMenu } from '../features/docs.js';
|
|
|
11
11
|
import { openHomepage, openGithub } from '../features/website.js';
|
|
12
12
|
import { printDivider, printNewLine } from './ui.js';
|
|
13
13
|
import { APP_INFO, URLS } from '../config/data.js';
|
|
14
|
+
import { t, getCurrentLanguage, setLanguage, clearTranslationCache, type Language } from '../i18n/index.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
+
* Get main menu options
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
19
|
+
function getMainMenuOptions() {
|
|
20
|
+
const trans = t();
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
23
|
+
name: '[*] ' + trans.menu.events.padEnd(16) + ' ' + chalk.gray(trans.menu.eventsDesc),
|
|
24
|
+
value: 'events',
|
|
25
|
+
short: trans.menu.events
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: '[*] ' + trans.menu.repair.padEnd(16) + ' ' + chalk.gray(trans.menu.repairDesc),
|
|
29
|
+
value: 'repair',
|
|
30
|
+
short: trans.menu.repair
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: '[*] ' + trans.menu.docs.padEnd(16) + ' ' + chalk.gray(trans.menu.docsDesc),
|
|
34
|
+
value: 'docs',
|
|
35
|
+
short: trans.menu.docs
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: '[*] ' + trans.menu.website.padEnd(16) + ' ' + chalk.gray(trans.menu.websiteDesc),
|
|
39
|
+
value: 'website',
|
|
40
|
+
short: trans.menu.website
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: '[*] ' + trans.menu.github.padEnd(16) + ' ' + chalk.gray(trans.menu.githubDesc),
|
|
44
|
+
value: 'github',
|
|
45
|
+
short: trans.menu.github
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: '[?] ' + trans.menu.about.padEnd(16) + ' ' + chalk.gray(trans.menu.aboutDesc),
|
|
49
|
+
value: 'about',
|
|
50
|
+
short: trans.menu.about
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: '[⚙] ' + trans.menu.language.padEnd(16) + ' ' + chalk.gray(trans.menu.languageDesc),
|
|
54
|
+
value: 'language',
|
|
55
|
+
short: trans.menu.language
|
|
56
|
+
},
|
|
57
|
+
new inquirer.Separator(' '),
|
|
58
|
+
{
|
|
59
|
+
name: chalk.dim('[x] ' + trans.common.exit),
|
|
60
|
+
value: 'exit',
|
|
61
|
+
short: trans.common.exit
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
}
|
|
56
65
|
|
|
57
66
|
/**
|
|
58
|
-
*
|
|
67
|
+
* Display main menu
|
|
59
68
|
*/
|
|
60
69
|
export async function showMainMenu(): Promise<void> {
|
|
61
70
|
while (true) {
|
|
62
71
|
try {
|
|
72
|
+
const trans = t();
|
|
73
|
+
|
|
63
74
|
// Show keybinding hints
|
|
64
|
-
console.log(chalk.dim('
|
|
75
|
+
console.log(chalk.dim(' ' + trans.menu.navigationHint));
|
|
65
76
|
console.log();
|
|
66
77
|
|
|
67
78
|
const { action } = await inquirer.prompt<{ action: string }>([
|
|
68
79
|
{
|
|
69
80
|
type: 'list',
|
|
70
81
|
name: 'action',
|
|
71
|
-
message:
|
|
72
|
-
choices:
|
|
82
|
+
message: trans.menu.chooseAction,
|
|
83
|
+
choices: getMainMenuOptions(),
|
|
73
84
|
pageSize: 15,
|
|
74
85
|
loop: false
|
|
75
86
|
} as any
|
|
76
87
|
]);
|
|
77
88
|
|
|
78
|
-
//
|
|
89
|
+
// Handle user selection
|
|
79
90
|
if (action === 'exit') {
|
|
80
91
|
console.log();
|
|
81
|
-
console.log(chalk.dim(
|
|
92
|
+
console.log(chalk.dim(trans.common.goodbye));
|
|
82
93
|
process.exit(0);
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
await handleAction(action);
|
|
86
97
|
|
|
87
|
-
//
|
|
98
|
+
// Show divider after operation
|
|
88
99
|
printNewLine();
|
|
89
100
|
printDivider();
|
|
90
101
|
printNewLine();
|
|
91
102
|
} catch (err: any) {
|
|
92
|
-
//
|
|
103
|
+
// Handle Ctrl+C exit
|
|
93
104
|
if (err.message?.includes('User force closed')) {
|
|
94
105
|
console.log();
|
|
95
|
-
console.log(chalk.dim(
|
|
106
|
+
console.log(chalk.dim(t().common.goodbye));
|
|
96
107
|
process.exit(0);
|
|
97
108
|
}
|
|
98
109
|
throw err;
|
|
@@ -101,7 +112,7 @@ export async function showMainMenu(): Promise<void> {
|
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
/**
|
|
104
|
-
*
|
|
115
|
+
* Handle user action
|
|
105
116
|
*/
|
|
106
117
|
async function handleAction(action: string): Promise<void> {
|
|
107
118
|
switch (action) {
|
|
@@ -129,33 +140,74 @@ async function handleAction(action: string): Promise<void> {
|
|
|
129
140
|
showAbout();
|
|
130
141
|
break;
|
|
131
142
|
|
|
143
|
+
case 'language':
|
|
144
|
+
await showLanguageMenu();
|
|
145
|
+
break;
|
|
146
|
+
|
|
132
147
|
default:
|
|
133
|
-
console.log(chalk.gray('
|
|
148
|
+
console.log(chalk.gray('Unknown action'));
|
|
134
149
|
}
|
|
135
150
|
}
|
|
136
151
|
|
|
137
152
|
/**
|
|
138
|
-
*
|
|
153
|
+
* Display about information
|
|
139
154
|
*/
|
|
140
155
|
function showAbout(): void {
|
|
156
|
+
const trans = t();
|
|
141
157
|
console.log();
|
|
142
|
-
console.log(chalk.bold('>>
|
|
158
|
+
console.log(chalk.bold('>> ' + trans.about.title));
|
|
143
159
|
console.log();
|
|
144
|
-
console.log(chalk.dim(
|
|
145
|
-
console.log(chalk.dim(
|
|
146
|
-
console.log(chalk.dim(
|
|
160
|
+
console.log(chalk.dim(trans.about.project.padEnd(12)) + APP_INFO.name);
|
|
161
|
+
console.log(chalk.dim(trans.about.version.padEnd(12)) + `v${APP_INFO.version}`);
|
|
162
|
+
console.log(chalk.dim(trans.about.description.padEnd(12)) + APP_INFO.fullDescription);
|
|
147
163
|
console.log();
|
|
148
|
-
console.log(chalk.dim(
|
|
149
|
-
console.log(chalk.dim(
|
|
150
|
-
console.log(chalk.dim(
|
|
164
|
+
console.log(chalk.dim(trans.about.github.padEnd(12)) + chalk.cyan(APP_INFO.repository));
|
|
165
|
+
console.log(chalk.dim(trans.about.website.padEnd(12)) + chalk.cyan(URLS.homepage));
|
|
166
|
+
console.log(chalk.dim(trans.about.email.padEnd(12)) + chalk.cyan(URLS.email));
|
|
151
167
|
console.log();
|
|
152
|
-
console.log(chalk.dim(
|
|
153
|
-
console.log('
|
|
154
|
-
console.log('
|
|
155
|
-
console.log('
|
|
156
|
-
console.log('
|
|
168
|
+
console.log(chalk.dim(trans.about.features));
|
|
169
|
+
console.log(' ' + trans.about.feature1);
|
|
170
|
+
console.log(' ' + trans.about.feature2);
|
|
171
|
+
console.log(' ' + trans.about.feature3);
|
|
172
|
+
console.log(' ' + trans.about.feature4);
|
|
157
173
|
console.log();
|
|
158
|
-
console.log(chalk.dim(
|
|
159
|
-
console.log(chalk.dim(
|
|
174
|
+
console.log(chalk.dim(trans.about.license.padEnd(12)) + 'MIT License');
|
|
175
|
+
console.log(chalk.dim(trans.about.author.padEnd(12)) + 'm1ngsama');
|
|
160
176
|
console.log();
|
|
161
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Display language selection menu
|
|
181
|
+
*/
|
|
182
|
+
async function showLanguageMenu(): Promise<void> {
|
|
183
|
+
const trans = t();
|
|
184
|
+
const currentLang = getCurrentLanguage();
|
|
185
|
+
|
|
186
|
+
console.log();
|
|
187
|
+
console.log(chalk.bold('>> ' + trans.language.title));
|
|
188
|
+
console.log();
|
|
189
|
+
console.log(chalk.dim(trans.language.currentLanguage + ': ') + chalk.cyan(trans.language[currentLang]));
|
|
190
|
+
console.log();
|
|
191
|
+
|
|
192
|
+
const { language } = await inquirer.prompt<{ language: Language }>([
|
|
193
|
+
{
|
|
194
|
+
type: 'list',
|
|
195
|
+
name: 'language',
|
|
196
|
+
message: trans.language.selectLanguage,
|
|
197
|
+
choices: [
|
|
198
|
+
{ name: trans.language.zh, value: 'zh' as Language },
|
|
199
|
+
{ name: trans.language.en, value: 'en' as Language }
|
|
200
|
+
],
|
|
201
|
+
default: currentLang
|
|
202
|
+
}
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
if (language !== currentLang) {
|
|
206
|
+
setLanguage(language);
|
|
207
|
+
clearTranslationCache();
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(chalk.green('✓ ' + t().language.changed));
|
|
210
|
+
console.log();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
package/src/core/ui.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Minimalist UI component library
|
|
3
|
+
* Provides basic terminal UI components
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Display header title
|
|
10
10
|
*/
|
|
11
11
|
export function printHeader(title: string): void {
|
|
12
12
|
console.log(chalk.dim(title));
|
|
@@ -14,7 +14,7 @@ export function printHeader(title: string): void {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Display divider line
|
|
18
18
|
*/
|
|
19
19
|
export function printDivider(): void {
|
|
20
20
|
const terminalWidth = process.stdout.columns || 80;
|
|
@@ -22,7 +22,7 @@ export function printDivider(): void {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Display loading spinner
|
|
26
26
|
*/
|
|
27
27
|
export async function showSpinner(text: string, duration: number): Promise<void> {
|
|
28
28
|
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -44,42 +44,42 @@ export async function showSpinner(text: string, duration: number): Promise<void>
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Display success message
|
|
48
48
|
*/
|
|
49
49
|
export function success(msg: string): void {
|
|
50
|
-
console.log(chalk.green('✓') + ' ' + msg);
|
|
50
|
+
console.log(chalk.green('[✓]') + ' ' + msg);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* Display error message
|
|
55
55
|
*/
|
|
56
56
|
export function error(msg: string): void {
|
|
57
|
-
console.log(chalk.red('✗') + ' ' + msg);
|
|
57
|
+
console.log(chalk.red('[✗]') + ' ' + msg);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Display info message
|
|
62
62
|
*/
|
|
63
63
|
export function info(msg: string): void {
|
|
64
|
-
console.log(chalk.blue('ℹ') + ' ' + msg);
|
|
64
|
+
console.log(chalk.blue('[ℹ]') + ' ' + msg);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
68
|
+
* Display warning message
|
|
69
69
|
*/
|
|
70
70
|
export function warning(msg: string): void {
|
|
71
|
-
console.log(chalk.yellow('⚠') + ' ' + msg);
|
|
71
|
+
console.log(chalk.yellow('[⚠]') + ' ' + msg);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Clear screen
|
|
76
76
|
*/
|
|
77
77
|
export function clearScreen(): void {
|
|
78
78
|
console.clear();
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
*
|
|
82
|
+
* Print empty lines
|
|
83
83
|
*/
|
|
84
84
|
export function printNewLine(count: number = 1): void {
|
|
85
85
|
for (let i = 0; i < count; i++) {
|
package/src/features/calendar.ts
CHANGED
|
@@ -7,6 +7,7 @@ import axios from 'axios';
|
|
|
7
7
|
import ICAL from 'ical.js';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { error, info, printDivider } from '../core/ui.js';
|
|
10
|
+
import { t } from '../i18n/index.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 活动接口
|
|
@@ -43,13 +44,17 @@ export async function fetchEvents(): Promise<Event[]> {
|
|
|
43
44
|
const event = new ICAL.Event(vevent);
|
|
44
45
|
const startDate = event.startDate.toJSDate();
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
+
// Only show events within next 30 days
|
|
47
48
|
if (startDate >= now && startDate <= thirtyDaysLater) {
|
|
49
|
+
const trans = t();
|
|
50
|
+
const untitledEvent = trans.calendar.eventName === 'Event Name' ? 'Untitled Event' : '未命名活动';
|
|
51
|
+
const tbdLocation = trans.calendar.location === 'Location' ? 'TBD' : '待定';
|
|
52
|
+
|
|
48
53
|
events.push({
|
|
49
54
|
date: formatDate(startDate),
|
|
50
55
|
time: formatTime(startDate),
|
|
51
|
-
title: event.summary ||
|
|
52
|
-
location: event.location ||
|
|
56
|
+
title: event.summary || untitledEvent,
|
|
57
|
+
location: event.location || tbdLocation,
|
|
53
58
|
startDate: startDate
|
|
54
59
|
});
|
|
55
60
|
}
|
|
@@ -60,7 +65,7 @@ export async function fetchEvents(): Promise<Event[]> {
|
|
|
60
65
|
|
|
61
66
|
return events;
|
|
62
67
|
} catch (err) {
|
|
63
|
-
throw new Error(
|
|
68
|
+
throw new Error(t().calendar.error);
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -68,30 +73,32 @@ export async function fetchEvents(): Promise<Event[]> {
|
|
|
68
73
|
* 以表格形式显示活动
|
|
69
74
|
*/
|
|
70
75
|
export function displayEvents(events: Event[]): void {
|
|
76
|
+
const trans = t();
|
|
77
|
+
|
|
71
78
|
if (events.length === 0) {
|
|
72
|
-
info(
|
|
79
|
+
info(trans.calendar.noEvents);
|
|
73
80
|
return;
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
console.log();
|
|
77
|
-
console.log(chalk.cyan.bold('
|
|
84
|
+
console.log(chalk.cyan.bold(' ' + trans.calendar.title) + chalk.gray(` ${trans.calendar.subtitle}`));
|
|
78
85
|
console.log();
|
|
79
86
|
|
|
80
|
-
//
|
|
87
|
+
// Table header
|
|
81
88
|
const dateWidth = 14;
|
|
82
89
|
const titleWidth = 25;
|
|
83
90
|
const locationWidth = 15;
|
|
84
91
|
|
|
85
92
|
console.log(
|
|
86
93
|
' ' +
|
|
87
|
-
chalk.bold(
|
|
88
|
-
chalk.bold(
|
|
89
|
-
chalk.bold(
|
|
94
|
+
chalk.bold(trans.calendar.dateTime.padEnd(dateWidth)) +
|
|
95
|
+
chalk.bold(trans.calendar.eventName.padEnd(titleWidth)) +
|
|
96
|
+
chalk.bold(trans.calendar.location)
|
|
90
97
|
);
|
|
91
98
|
|
|
92
99
|
printDivider();
|
|
93
100
|
|
|
94
|
-
//
|
|
101
|
+
// Event list
|
|
95
102
|
events.forEach(event => {
|
|
96
103
|
const dateTime = `${event.date} ${event.time}`.padEnd(dateWidth);
|
|
97
104
|
const title = truncate(event.title, titleWidth - 2).padEnd(titleWidth);
|
|
@@ -140,14 +147,15 @@ function truncate(str: string, maxLength: number): string {
|
|
|
140
147
|
* 主函数:获取并显示活动
|
|
141
148
|
*/
|
|
142
149
|
export async function showCalendar(): Promise<void> {
|
|
150
|
+
const trans = t();
|
|
143
151
|
try {
|
|
144
|
-
info(
|
|
152
|
+
info(trans.calendar.loading);
|
|
145
153
|
const events = await fetchEvents();
|
|
146
|
-
console.log('\r' + ' '.repeat(50) + '\r'); //
|
|
154
|
+
console.log('\r' + ' '.repeat(50) + '\r'); // Clear loading message
|
|
147
155
|
displayEvents(events);
|
|
148
156
|
} catch (err) {
|
|
149
|
-
error(
|
|
150
|
-
console.log(chalk.gray('
|
|
157
|
+
error(trans.calendar.error);
|
|
158
|
+
console.log(chalk.gray(' ' + trans.calendar.errorHint));
|
|
151
159
|
console.log();
|
|
152
160
|
}
|
|
153
161
|
}
|