cc98-cli 0.4.0 → 0.4.2
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/CHANGELOG.md +14 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +49 -3
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/storage/settings-store.d.ts +10 -0
- package/dist/storage/settings-store.d.ts.map +1 -0
- package/dist/storage/settings-store.js +37 -0
- package/dist/storage/settings-store.js.map +1 -0
- package/dist/tui/components/content.d.ts +1 -0
- package/dist/tui/components/content.d.ts.map +1 -1
- package/dist/tui/components/content.js +26 -8
- package/dist/tui/components/content.js.map +1 -1
- package/dist/tui/components/status.d.ts +3 -0
- package/dist/tui/components/status.d.ts.map +1 -1
- package/dist/tui/components/status.js +21 -8
- package/dist/tui/components/status.js.map +1 -1
- package/dist/tui/controller.d.ts +17 -0
- package/dist/tui/controller.d.ts.map +1 -1
- package/dist/tui/controller.js +465 -108
- package/dist/tui/controller.js.map +1 -1
- package/dist/tui/keybindings.d.ts +24 -0
- package/dist/tui/keybindings.d.ts.map +1 -0
- package/dist/tui/keybindings.js +207 -0
- package/dist/tui/keybindings.js.map +1 -0
- package/dist/tui/navigation.d.ts.map +1 -1
- package/dist/tui/navigation.js +1 -0
- package/dist/tui/navigation.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +36 -9
- package/dist/tui/renderer.js.map +1 -1
- package/dist/tui/state/store.d.ts.map +1 -1
- package/dist/tui/state/store.js +13 -2
- package/dist/tui/state/store.js.map +1 -1
- package/dist/tui/state/types.d.ts +16 -0
- package/dist/tui/state/types.d.ts.map +1 -1
- package/dist/tui/topic-reader.d.ts +9 -1
- package/dist/tui/topic-reader.d.ts.map +1 -1
- package/dist/tui/topic-reader.js +23 -11
- package/dist/tui/topic-reader.js.map +1 -1
- package/dist/update.d.ts +4 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +71 -11
- package/dist/update.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -2
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/dist/tui/components/layout.d.ts +0 -3
- package/dist/tui/components/layout.d.ts.map +0 -1
- package/dist/tui/components/layout.js +0 -453
- package/dist/tui/components/layout.js.map +0 -1
package/dist/tui/controller.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { checkForUpdate } from "../update.js";
|
|
2
2
|
import { appVersion } from "../version.js";
|
|
3
3
|
import { getImageCache } from "../storage/image-cache.js";
|
|
4
|
+
import { SettingsStore } from "../storage/settings-store.js";
|
|
5
|
+
import { getKeybindingManager } from "./keybindings.js";
|
|
4
6
|
import { navItems, settingsItems } from "./navigation.js";
|
|
5
7
|
import { getStatus } from "./state/store.js";
|
|
6
8
|
import { asArray, asNumber, asObject, chatItem, chatMessageItems, flattenBoards, genericItem, historyItem, isAbortError, jsonPreviewLines, loadChatUserNames, mapLimit, noticeItem, overviewStats, topicItem, unreadStats, userItem } from "./helpers.js";
|
|
7
|
-
import { appendTopicPosts, buildTopicReader, currentTopicPost, findTopicPostByFloor,
|
|
9
|
+
import { appendTopicPosts, buildTopicReader, currentTopicPost, findTopicPostByFloor, getTopicPageInfo, jumpToPage, FLOORS_PER_PAGE } from "./topic-reader.js";
|
|
8
10
|
export class TuiController {
|
|
9
11
|
state;
|
|
10
12
|
client;
|
|
@@ -14,6 +16,9 @@ export class TuiController {
|
|
|
14
16
|
nextSignal;
|
|
15
17
|
abortCurrent;
|
|
16
18
|
loadVersion = 0;
|
|
19
|
+
keybindings;
|
|
20
|
+
settingsStore = new SettingsStore();
|
|
21
|
+
updateChecked = false;
|
|
17
22
|
constructor(state, client, tokenStore, render, close, nextSignal, abortCurrent) {
|
|
18
23
|
this.state = state;
|
|
19
24
|
this.client = client;
|
|
@@ -22,6 +27,7 @@ export class TuiController {
|
|
|
22
27
|
this.close = close;
|
|
23
28
|
this.nextSignal = nextSignal;
|
|
24
29
|
this.abortCurrent = abortCurrent;
|
|
30
|
+
this.keybindings = getKeybindingManager();
|
|
25
31
|
}
|
|
26
32
|
async load(force = false) {
|
|
27
33
|
const version = ++this.loadVersion;
|
|
@@ -44,7 +50,14 @@ export class TuiController {
|
|
|
44
50
|
this.state.currentChat = undefined;
|
|
45
51
|
this.render();
|
|
46
52
|
try {
|
|
53
|
+
// 加载快捷键配置
|
|
54
|
+
await this.keybindings.load();
|
|
47
55
|
this.state.account = await this.tokenStore.getCurrentAccountName();
|
|
56
|
+
// 异步检查更新(不阻塞主加载)
|
|
57
|
+
if (!this.updateChecked) {
|
|
58
|
+
this.updateChecked = true;
|
|
59
|
+
void this.checkUpdate();
|
|
60
|
+
}
|
|
48
61
|
const next = await this.loadView(nav.id, force, signal);
|
|
49
62
|
if (version !== this.loadVersion)
|
|
50
63
|
return;
|
|
@@ -75,11 +88,20 @@ export class TuiController {
|
|
|
75
88
|
this.handleInputKey(key);
|
|
76
89
|
return;
|
|
77
90
|
}
|
|
78
|
-
|
|
91
|
+
// 关闭更新通知(Esc 或任意键)
|
|
92
|
+
if (this.state.updateAvailable?.isNew) {
|
|
93
|
+
if (key === "\x1b" || key === "\r") {
|
|
94
|
+
this.dismissUpdate();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// 其他按键也关闭更新通知,继续处理原按键动作
|
|
98
|
+
this.dismissUpdate();
|
|
99
|
+
}
|
|
100
|
+
if (this.keybindings.matches(key, "quit")) {
|
|
79
101
|
this.close();
|
|
80
102
|
return;
|
|
81
103
|
}
|
|
82
|
-
if (key
|
|
104
|
+
if (this.keybindings.matches(key, "help")) {
|
|
83
105
|
this.state.modal = this.state.modal === "help" ? null : "help";
|
|
84
106
|
this.render();
|
|
85
107
|
return;
|
|
@@ -103,17 +125,17 @@ export class TuiController {
|
|
|
103
125
|
this.handleContentKey(key);
|
|
104
126
|
}
|
|
105
127
|
handleInputKey(key) {
|
|
106
|
-
if (key
|
|
128
|
+
if (this.keybindings.matches(key, "inputCancel")) {
|
|
107
129
|
this.state.inputMode = false;
|
|
108
130
|
this.state.inputValue = "";
|
|
109
131
|
this.render();
|
|
110
132
|
return;
|
|
111
133
|
}
|
|
112
|
-
if (key
|
|
134
|
+
if (this.keybindings.matches(key, "inputConfirm")) {
|
|
113
135
|
this.state.inputCallback?.(this.state.inputValue);
|
|
114
136
|
return;
|
|
115
137
|
}
|
|
116
|
-
if (key
|
|
138
|
+
if (this.keybindings.matches(key, "inputBackspace")) {
|
|
117
139
|
this.state.inputValue = this.state.inputValue.slice(0, -1);
|
|
118
140
|
this.render();
|
|
119
141
|
return;
|
|
@@ -124,8 +146,19 @@ export class TuiController {
|
|
|
124
146
|
}
|
|
125
147
|
}
|
|
126
148
|
handleModalKey(key) {
|
|
127
|
-
if (this.state.modal === "help"
|
|
128
|
-
|
|
149
|
+
if (this.state.modal === "help") {
|
|
150
|
+
this.closeModal();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (this.state.modal === "info") {
|
|
154
|
+
// 如果有确认回调,确认键执行回调,其它键关闭。
|
|
155
|
+
if (this.state.confirmCallback && this.keybindings.matches(key, "confirm")) {
|
|
156
|
+
const callback = this.state.confirmCallback;
|
|
157
|
+
this.state.confirmCallback = undefined;
|
|
158
|
+
this.closeModal();
|
|
159
|
+
callback();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
129
162
|
this.closeModal();
|
|
130
163
|
return;
|
|
131
164
|
}
|
|
@@ -142,31 +175,30 @@ export class TuiController {
|
|
|
142
175
|
}
|
|
143
176
|
}
|
|
144
177
|
handleSearchKey(key) {
|
|
145
|
-
if (key
|
|
146
|
-
// Esc 或 / 关闭搜索
|
|
178
|
+
if (this.keybindings.matches(key, "searchClose")) {
|
|
147
179
|
this.state.modal = null;
|
|
148
180
|
this.state.searchQuery = "";
|
|
149
181
|
this.render();
|
|
150
182
|
return;
|
|
151
183
|
}
|
|
152
|
-
if (key
|
|
184
|
+
if (this.keybindings.matches(key, "searchToggleMode")) {
|
|
153
185
|
this.state.searchMode = this.state.searchMode === "topics" ? "users" : "topics";
|
|
154
186
|
this.state.searchResults = [];
|
|
155
187
|
this.state.itemIndex = 0;
|
|
156
188
|
this.render();
|
|
157
189
|
return;
|
|
158
190
|
}
|
|
159
|
-
if ((key
|
|
191
|
+
if (this.keybindings.matches(key, "searchNext") && this.state.searchResults.length > 0) {
|
|
160
192
|
this.state.itemIndex = Math.min(this.state.searchResults.length - 1, this.state.itemIndex + 1);
|
|
161
193
|
this.render();
|
|
162
194
|
return;
|
|
163
195
|
}
|
|
164
|
-
if ((key
|
|
196
|
+
if (this.keybindings.matches(key, "searchPrev") && this.state.searchResults.length > 0) {
|
|
165
197
|
this.state.itemIndex = Math.max(0, this.state.itemIndex - 1);
|
|
166
198
|
this.render();
|
|
167
199
|
return;
|
|
168
200
|
}
|
|
169
|
-
if (key
|
|
201
|
+
if (this.keybindings.matches(key, "searchExecute")) {
|
|
170
202
|
const selected = this.state.searchResults[this.state.itemIndex];
|
|
171
203
|
if (selected) {
|
|
172
204
|
// 有选中项:打开
|
|
@@ -222,23 +254,22 @@ export class TuiController {
|
|
|
222
254
|
}
|
|
223
255
|
}
|
|
224
256
|
handleMenuKey(key) {
|
|
225
|
-
if (key
|
|
257
|
+
if (this.keybindings.matches(key, "menuNext")) {
|
|
226
258
|
this.state.menuIndex = Math.min(Math.max(0, this.state.menuItems.length - 1), this.state.menuIndex + 1);
|
|
227
259
|
this.render();
|
|
228
260
|
return;
|
|
229
261
|
}
|
|
230
|
-
if (key
|
|
262
|
+
if (this.keybindings.matches(key, "menuPrev")) {
|
|
231
263
|
this.state.menuIndex = Math.max(0, this.state.menuIndex - 1);
|
|
232
264
|
this.render();
|
|
233
265
|
return;
|
|
234
266
|
}
|
|
235
|
-
if (key
|
|
236
|
-
// Esc 或 o 关闭菜单
|
|
267
|
+
if (this.keybindings.matches(key, "menuClose")) {
|
|
237
268
|
this.state.modal = null;
|
|
238
269
|
this.render();
|
|
239
270
|
return;
|
|
240
271
|
}
|
|
241
|
-
if (key
|
|
272
|
+
if (this.keybindings.matches(key, "menuExecute")) {
|
|
242
273
|
// Enter 或 l 执行选中项
|
|
243
274
|
const selected = this.state.menuItems[this.state.menuIndex];
|
|
244
275
|
this.state.modal = null;
|
|
@@ -250,84 +281,137 @@ export class TuiController {
|
|
|
250
281
|
}
|
|
251
282
|
}
|
|
252
283
|
handleTopicKey(key) {
|
|
284
|
+
// 数字输入:收集跳转目标
|
|
253
285
|
if (/^\d$/.test(key) && this.state.topic) {
|
|
254
286
|
this.state.topic.floorInput = `${this.state.topic.floorInput}${key}`.slice(0, 6);
|
|
255
|
-
this.state.status =
|
|
287
|
+
this.state.status = `输入: ${this.state.topic.floorInput}`;
|
|
256
288
|
this.render();
|
|
257
289
|
return;
|
|
258
290
|
}
|
|
259
|
-
|
|
291
|
+
// 退格:删除输入
|
|
292
|
+
if (this.keybindings.matches(key, "inputBackspace") && this.state.topic?.floorInput) {
|
|
260
293
|
this.state.topic.floorInput = this.state.topic.floorInput.slice(0, -1);
|
|
261
|
-
this.state.status = this.state.topic.floorInput ?
|
|
294
|
+
this.state.status = this.state.topic.floorInput ? `输入: ${this.state.topic.floorInput}` : "";
|
|
262
295
|
this.render();
|
|
263
296
|
return;
|
|
264
297
|
}
|
|
265
|
-
|
|
266
|
-
|
|
298
|
+
// 数字 + 跳页键:跳页
|
|
299
|
+
if (this.keybindings.matches(key, "topicJumpPage") && this.state.topic?.floorInput) {
|
|
300
|
+
const page = Number(this.state.topic.floorInput);
|
|
301
|
+
this.state.topic.jumpTarget = { type: "page", value: page };
|
|
267
302
|
this.state.topic.floorInput = "";
|
|
268
|
-
|
|
269
|
-
|
|
303
|
+
this.state.status = `跳转到第 ${page} 页?${this.keybindings.formatActionKeys("confirm")} 确认 ${this.keybindings.formatActionKeys("back")} 取消`;
|
|
304
|
+
this.render();
|
|
270
305
|
return;
|
|
271
306
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
307
|
+
// 数字 + 跳楼键:跳楼
|
|
308
|
+
if (this.keybindings.matches(key, "topicJumpFloor") && this.state.topic?.floorInput) {
|
|
309
|
+
const floor = Number(this.state.topic.floorInput);
|
|
310
|
+
this.state.topic.jumpTarget = { type: "floor", value: floor };
|
|
311
|
+
this.state.topic.floorInput = "";
|
|
312
|
+
this.state.status = `跳转到第 ${floor} 楼?${this.keybindings.formatActionKeys("confirm")} 确认 ${this.keybindings.formatActionKeys("back")} 取消`;
|
|
275
313
|
this.render();
|
|
276
314
|
return;
|
|
277
315
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.state.
|
|
316
|
+
// 跳到最后一页
|
|
317
|
+
if (this.keybindings.matches(key, "topicJumpLast") && !this.state.topic?.floorInput && this.state.topic) {
|
|
318
|
+
const pageInfo = getTopicPageInfo(this.state.topic, this.state.topic.cursorLine);
|
|
319
|
+
this.state.topic.jumpTarget = { type: "page", value: pageInfo.totalPages };
|
|
320
|
+
this.state.status = `跳转到最后一页(第 ${pageInfo.totalPages} 页)?${this.keybindings.formatActionKeys("confirm")} 确认 ${this.keybindings.formatActionKeys("back")} 取消`;
|
|
281
321
|
this.render();
|
|
282
322
|
return;
|
|
283
323
|
}
|
|
284
|
-
|
|
324
|
+
// 确认跳转
|
|
325
|
+
if (this.keybindings.matches(key, "confirm") && this.state.topic?.jumpTarget) {
|
|
326
|
+
const target = this.state.topic.jumpTarget;
|
|
327
|
+
this.state.topic.jumpTarget = undefined;
|
|
328
|
+
this.state.status = "";
|
|
329
|
+
if (target.type === "page") {
|
|
330
|
+
void this.jumpToTopicPage(target.value, this.nextSignal());
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
void this.jumpToTopicFloor(target.value, this.nextSignal());
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// 取消跳转
|
|
338
|
+
if (this.keybindings.matches(key, "back") && (this.state.topic?.floorInput || this.state.topic?.jumpTarget)) {
|
|
285
339
|
this.state.topic.floorInput = "";
|
|
286
|
-
this.state.
|
|
340
|
+
this.state.topic.jumpTarget = undefined;
|
|
341
|
+
this.state.status = "";
|
|
287
342
|
this.render();
|
|
288
343
|
return;
|
|
289
344
|
}
|
|
290
|
-
|
|
345
|
+
// ]:下一层
|
|
346
|
+
if (this.keybindings.matches(key, "topicNextFloor") && this.state.topic) {
|
|
347
|
+
void this.jumpRelativeFloor(1);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// [:上一层
|
|
351
|
+
if (this.keybindings.matches(key, "topicPrevFloor") && this.state.topic) {
|
|
352
|
+
void this.jumpRelativeFloor(-1);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// }:下一页
|
|
356
|
+
if (this.keybindings.matches(key, "topicNextPage") && this.state.topic) {
|
|
357
|
+
void this.jumpToNextPage();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
// {:上一页
|
|
361
|
+
if (this.keybindings.matches(key, "topicPrevPage") && this.state.topic) {
|
|
362
|
+
void this.jumpToPrevPage();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// h/Esc:返回
|
|
366
|
+
if (this.keybindings.matches(key, "back")) {
|
|
291
367
|
this.leave();
|
|
292
368
|
return;
|
|
293
369
|
}
|
|
294
|
-
|
|
370
|
+
// j:下移
|
|
371
|
+
if (this.keybindings.matches(key, "topicScrollDown")) {
|
|
295
372
|
const maxScroll = Math.max(0, (this.state.topic?.lines.length ?? 0) - 1);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.render();
|
|
299
|
-
if (wasAtEnd && this.state.topic?.hasMore && !this.state.loadingMore) {
|
|
300
|
-
void this.loadNextTopicPage(this.nextSignal(), true);
|
|
373
|
+
if (this.state.topic) {
|
|
374
|
+
this.state.topic.cursorLine = Math.min(maxScroll, this.state.topic.cursorLine + 1);
|
|
301
375
|
}
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
if (key === "k" || key === "\x1b[A") {
|
|
305
|
-
this.state.scroll = Math.max(0, this.state.scroll - 1);
|
|
376
|
+
this.state.status = "";
|
|
306
377
|
this.render();
|
|
378
|
+
void this.checkAutoLoad();
|
|
307
379
|
return;
|
|
308
380
|
}
|
|
309
|
-
|
|
310
|
-
|
|
381
|
+
// k:上移
|
|
382
|
+
if (this.keybindings.matches(key, "topicScrollUp")) {
|
|
383
|
+
if (this.state.topic) {
|
|
384
|
+
this.state.topic.cursorLine = Math.max(0, this.state.topic.cursorLine - 1);
|
|
385
|
+
}
|
|
386
|
+
this.state.status = "";
|
|
387
|
+
this.render();
|
|
311
388
|
return;
|
|
312
389
|
}
|
|
313
|
-
|
|
390
|
+
// r:刷新
|
|
391
|
+
if (this.keybindings.matches(key, "topicRefresh") && this.state.topic) {
|
|
314
392
|
void this.openTopic(this.state.topic.topicId, true, this.nextSignal());
|
|
315
393
|
return;
|
|
316
394
|
}
|
|
317
|
-
|
|
395
|
+
// s:收藏
|
|
396
|
+
if (this.keybindings.matches(key, "topicFavorite"))
|
|
318
397
|
void this.toggleFavorite();
|
|
319
|
-
|
|
398
|
+
// l:点赞
|
|
399
|
+
if (this.keybindings.matches(key, "topicLike"))
|
|
320
400
|
void this.reactToCurrentPost(true);
|
|
321
|
-
|
|
401
|
+
// d:踩
|
|
402
|
+
if (this.keybindings.matches(key, "topicDislike"))
|
|
322
403
|
void this.reactToCurrentPost(false);
|
|
323
|
-
|
|
404
|
+
// u:查看用户
|
|
405
|
+
if (this.keybindings.matches(key, "topicUser"))
|
|
324
406
|
void this.showCurrentUser(this.nextSignal());
|
|
325
|
-
|
|
407
|
+
// v:查看投票
|
|
408
|
+
if (this.keybindings.matches(key, "topicVote"))
|
|
326
409
|
void this.showTopicVote(this.nextSignal());
|
|
327
|
-
|
|
410
|
+
// a:查看评价
|
|
411
|
+
if (this.keybindings.matches(key, "topicReaction"))
|
|
328
412
|
void this.showPostReactionState(this.nextSignal());
|
|
329
|
-
|
|
330
|
-
|
|
413
|
+
// o:打开图片/菜单
|
|
414
|
+
if (this.keybindings.matches(key, "topicOpenImage")) {
|
|
331
415
|
const currentLine = this.getCurrentTopicLine();
|
|
332
416
|
if (currentLine?.kind === "image" && currentLine.imageUrl) {
|
|
333
417
|
void this.openImage(currentLine.imageUrl);
|
|
@@ -336,8 +420,8 @@ export class TuiController {
|
|
|
336
420
|
this.openMenu();
|
|
337
421
|
}
|
|
338
422
|
}
|
|
339
|
-
|
|
340
|
-
|
|
423
|
+
// c:复制链接
|
|
424
|
+
if (this.keybindings.matches(key, "topicCopyLink")) {
|
|
341
425
|
const currentLine = this.getCurrentTopicLine();
|
|
342
426
|
if (currentLine?.kind === "image" && currentLine.imageUrl) {
|
|
343
427
|
void this.copyToClipboard(currentLine.imageUrl);
|
|
@@ -348,39 +432,39 @@ export class TuiController {
|
|
|
348
432
|
}
|
|
349
433
|
}
|
|
350
434
|
handleSettingsKey(key) {
|
|
351
|
-
if (key
|
|
435
|
+
if (this.keybindings.matches(key, "moveDown")) {
|
|
352
436
|
this.state.itemIndex = Math.min(settingsItems.length - 1, this.state.itemIndex + 1);
|
|
353
437
|
this.render();
|
|
354
438
|
return;
|
|
355
439
|
}
|
|
356
|
-
if (key
|
|
440
|
+
if (this.keybindings.matches(key, "moveUp")) {
|
|
357
441
|
this.state.itemIndex = Math.max(0, this.state.itemIndex - 1);
|
|
358
442
|
this.render();
|
|
359
443
|
return;
|
|
360
444
|
}
|
|
361
|
-
if (key
|
|
445
|
+
if (this.keybindings.matches(key, "back")) {
|
|
362
446
|
this.state.mode = "list";
|
|
363
447
|
this.state.focus = "nav";
|
|
364
448
|
this.state.status = getStatus(this.state);
|
|
365
449
|
this.render();
|
|
366
450
|
return;
|
|
367
451
|
}
|
|
368
|
-
if (key
|
|
452
|
+
if (this.keybindings.matches(key, "confirm") || this.keybindings.matches(key, "moveRight")) {
|
|
369
453
|
void this.activateSetting(settingsItems[this.state.itemIndex]);
|
|
370
454
|
}
|
|
371
455
|
}
|
|
372
456
|
handleNavKey(key) {
|
|
373
|
-
if (key
|
|
457
|
+
if (this.keybindings.matches(key, "moveDown")) {
|
|
374
458
|
this.state.navIndex = Math.min(navItems.length - 1, this.state.navIndex + 1);
|
|
375
459
|
void this.load();
|
|
376
460
|
return;
|
|
377
461
|
}
|
|
378
|
-
if (key
|
|
462
|
+
if (this.keybindings.matches(key, "moveUp")) {
|
|
379
463
|
this.state.navIndex = Math.max(0, this.state.navIndex - 1);
|
|
380
464
|
void this.load();
|
|
381
465
|
return;
|
|
382
466
|
}
|
|
383
|
-
if (key
|
|
467
|
+
if (this.keybindings.matches(key, "confirm") || this.keybindings.matches(key, "moveRight")) {
|
|
384
468
|
if (!this.state.loading && this.state.items.length > 0) {
|
|
385
469
|
if (navItems[this.state.navIndex]?.id === "settings")
|
|
386
470
|
this.state.mode = "settings";
|
|
@@ -391,25 +475,25 @@ export class TuiController {
|
|
|
391
475
|
}
|
|
392
476
|
return;
|
|
393
477
|
}
|
|
394
|
-
if (key
|
|
478
|
+
if (this.keybindings.matches(key, "refresh"))
|
|
395
479
|
void this.load(true);
|
|
396
480
|
}
|
|
397
481
|
handleContentKey(key) {
|
|
398
|
-
if (key
|
|
482
|
+
if (this.keybindings.matches(key, "listNext")) {
|
|
399
483
|
this.state.itemIndex = Math.min(Math.max(0, this.state.items.length - 1), this.state.itemIndex + 1);
|
|
400
484
|
this.render();
|
|
401
485
|
return;
|
|
402
486
|
}
|
|
403
|
-
if (key
|
|
487
|
+
if (this.keybindings.matches(key, "listPrev")) {
|
|
404
488
|
this.state.itemIndex = Math.max(0, this.state.itemIndex - 1);
|
|
405
489
|
this.render();
|
|
406
490
|
return;
|
|
407
491
|
}
|
|
408
|
-
if (key
|
|
492
|
+
if (this.keybindings.matches(key, "listBack")) {
|
|
409
493
|
this.leave();
|
|
410
494
|
return;
|
|
411
495
|
}
|
|
412
|
-
if (key
|
|
496
|
+
if (this.keybindings.matches(key, "listOpen")) {
|
|
413
497
|
const selected = this.state.items[this.state.itemIndex];
|
|
414
498
|
if (selected) {
|
|
415
499
|
void this.activateContentItem(selected, this.nextSignal());
|
|
@@ -420,15 +504,11 @@ export class TuiController {
|
|
|
420
504
|
}
|
|
421
505
|
return;
|
|
422
506
|
}
|
|
423
|
-
if ((key
|
|
424
|
-
void this.loadNextChatPage(this.nextSignal());
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (key === "r")
|
|
507
|
+
if (this.keybindings.matches(key, "listRefresh"))
|
|
428
508
|
void this.refresh();
|
|
429
|
-
if (key
|
|
509
|
+
if (this.keybindings.matches(key, "search"))
|
|
430
510
|
this.openSearch();
|
|
431
|
-
if (key
|
|
511
|
+
if (this.keybindings.matches(key, "menu"))
|
|
432
512
|
this.openMenu();
|
|
433
513
|
}
|
|
434
514
|
leave() {
|
|
@@ -643,34 +723,27 @@ export class TuiController {
|
|
|
643
723
|
this.render();
|
|
644
724
|
return;
|
|
645
725
|
}
|
|
726
|
+
if (selected.meta === "keybindings") {
|
|
727
|
+
void this.openKeybindingEditor();
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
646
730
|
if (selected.meta === "cache") {
|
|
647
|
-
this.
|
|
648
|
-
this.render();
|
|
649
|
-
try {
|
|
650
|
-
await this.client.clearCache();
|
|
651
|
-
this.state.status = "缓存已清理";
|
|
652
|
-
await this.load(true);
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
this.state.status = "缓存清理失败";
|
|
656
|
-
this.render();
|
|
657
|
-
}
|
|
731
|
+
void this.openCacheManager();
|
|
658
732
|
return;
|
|
659
733
|
}
|
|
660
734
|
if (selected.meta === "update") {
|
|
661
|
-
this.
|
|
662
|
-
this.render();
|
|
663
|
-
try {
|
|
664
|
-
const result = await checkForUpdate();
|
|
665
|
-
this.state.status = result.message;
|
|
666
|
-
}
|
|
667
|
-
catch (error) {
|
|
668
|
-
this.state.status = error instanceof Error ? error.message : "检查更新失败";
|
|
669
|
-
}
|
|
670
|
-
this.render();
|
|
735
|
+
void this.checkUpdate(true);
|
|
671
736
|
return;
|
|
672
737
|
}
|
|
673
|
-
|
|
738
|
+
if (selected.meta === "account") {
|
|
739
|
+
void this.openAccountSwitcher();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (selected.meta === "logout") {
|
|
743
|
+
void this.confirmLogout();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
this.state.status = "功能开发中...";
|
|
674
747
|
this.render();
|
|
675
748
|
}
|
|
676
749
|
async activateContentItem(selected, signal) {
|
|
@@ -690,6 +763,12 @@ export class TuiController {
|
|
|
690
763
|
await this.showUserDetailById(selected.userId, signal);
|
|
691
764
|
return;
|
|
692
765
|
}
|
|
766
|
+
// 账号切换
|
|
767
|
+
if (selected.meta?.startsWith("account:")) {
|
|
768
|
+
const accountName = selected.meta.slice(8);
|
|
769
|
+
await this.switchAccount(accountName);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
693
772
|
if (selected.action?.startsWith("notices:")) {
|
|
694
773
|
await this.openNoticeList(selected.action.split(":")[1], signal);
|
|
695
774
|
return;
|
|
@@ -800,6 +879,94 @@ export class TuiController {
|
|
|
800
879
|
this.render();
|
|
801
880
|
}
|
|
802
881
|
}
|
|
882
|
+
async checkAutoLoad() {
|
|
883
|
+
const topic = this.state.topic;
|
|
884
|
+
if (!topic?.hasMore || this.state.loadingMore)
|
|
885
|
+
return;
|
|
886
|
+
const viewportRows = Math.max(1, topic.viewportRows);
|
|
887
|
+
const viewportBottom = this.state.scroll + viewportRows;
|
|
888
|
+
if (viewportBottom >= topic.lines.length) {
|
|
889
|
+
void this.loadNextTopicPage(this.nextSignal(), true);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
async jumpRelativeFloor(delta) {
|
|
893
|
+
const topic = this.state.topic;
|
|
894
|
+
if (!topic)
|
|
895
|
+
return;
|
|
896
|
+
const current = currentTopicPost(topic, topic.cursorLine);
|
|
897
|
+
const currentFloor = current?.floor ?? 1;
|
|
898
|
+
const targetFloor = currentFloor + delta;
|
|
899
|
+
if (targetFloor < 1)
|
|
900
|
+
return;
|
|
901
|
+
const loaded = findTopicPostByFloor(topic, targetFloor);
|
|
902
|
+
if (loaded) {
|
|
903
|
+
topic.cursorLine = loaded.lineStart;
|
|
904
|
+
this.state.status = "";
|
|
905
|
+
this.render();
|
|
906
|
+
if (delta > 0)
|
|
907
|
+
void this.checkAutoLoad();
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (delta > 0 && topic.hasMore && !this.state.loadingMore) {
|
|
911
|
+
await this.jumpToTopicFloor(targetFloor, this.nextSignal());
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async jumpToNextPage() {
|
|
915
|
+
const topic = this.state.topic;
|
|
916
|
+
if (!topic)
|
|
917
|
+
return;
|
|
918
|
+
const pageInfo = getTopicPageInfo(topic, topic.cursorLine);
|
|
919
|
+
if (pageInfo.currentPage < pageInfo.totalPages) {
|
|
920
|
+
await this.jumpToTopicPage(pageInfo.currentPage + 1, this.nextSignal());
|
|
921
|
+
}
|
|
922
|
+
else if (topic.hasMore && !this.state.loadingMore) {
|
|
923
|
+
// 当前是最后一页,但还有更多内容,加载下一页
|
|
924
|
+
await this.loadNextTopicPage(this.nextSignal());
|
|
925
|
+
const newPageInfo = getTopicPageInfo(topic, topic.cursorLine);
|
|
926
|
+
topic.cursorLine = jumpToPage(topic, newPageInfo.currentPage + 1);
|
|
927
|
+
this.state.status = "";
|
|
928
|
+
this.render();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
async jumpToPrevPage() {
|
|
932
|
+
const topic = this.state.topic;
|
|
933
|
+
if (!topic)
|
|
934
|
+
return;
|
|
935
|
+
const pageInfo = getTopicPageInfo(topic, topic.cursorLine);
|
|
936
|
+
if (pageInfo.currentPage > 1) {
|
|
937
|
+
topic.cursorLine = jumpToPage(topic, pageInfo.currentPage - 1);
|
|
938
|
+
this.state.status = "";
|
|
939
|
+
this.render();
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
async jumpToTopicPage(page, signal) {
|
|
943
|
+
const topic = this.state.topic;
|
|
944
|
+
if (!topic)
|
|
945
|
+
return;
|
|
946
|
+
const pageInfo = getTopicPageInfo(topic, topic.cursorLine);
|
|
947
|
+
if (page < 1 || page > pageInfo.totalPages) {
|
|
948
|
+
this.state.status = `未找到第 ${page} 页`;
|
|
949
|
+
this.render();
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
// 如果目标页未加载,需要加载到该页
|
|
953
|
+
const targetFloor = (page - 1) * FLOORS_PER_PAGE + 1;
|
|
954
|
+
while (topic.hasMore && !findTopicPostByFloor(topic, targetFloor)) {
|
|
955
|
+
const previousLoaded = topic.loaded;
|
|
956
|
+
await this.loadNextTopicPage(signal, true);
|
|
957
|
+
if (topic.loaded === previousLoaded)
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
const post = findTopicPostByFloor(topic, targetFloor);
|
|
961
|
+
if (post) {
|
|
962
|
+
topic.cursorLine = post.lineStart;
|
|
963
|
+
this.state.status = "";
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
this.state.status = `未找到第 ${page} 页`;
|
|
967
|
+
}
|
|
968
|
+
this.render();
|
|
969
|
+
}
|
|
803
970
|
async loadNextTopicPage(signal, quiet = false) {
|
|
804
971
|
const topic = this.state.topic;
|
|
805
972
|
if (!topic?.hasMore || this.state.loadingMore)
|
|
@@ -810,7 +977,7 @@ export class TuiController {
|
|
|
810
977
|
try {
|
|
811
978
|
const posts = asArray(await this.client.getTopicPosts(topic.topicId, topic.loaded, topic.size, false, signal));
|
|
812
979
|
appendTopicPosts(topic, posts);
|
|
813
|
-
this.state.status =
|
|
980
|
+
this.state.status = "";
|
|
814
981
|
}
|
|
815
982
|
catch (error) {
|
|
816
983
|
if (!isAbortError(error))
|
|
@@ -827,17 +994,20 @@ export class TuiController {
|
|
|
827
994
|
return;
|
|
828
995
|
const loaded = findTopicPostByFloor(topic, floor);
|
|
829
996
|
if (loaded) {
|
|
830
|
-
|
|
831
|
-
this.state.status =
|
|
997
|
+
topic.cursorLine = loaded.lineStart;
|
|
998
|
+
this.state.status = "";
|
|
832
999
|
this.render();
|
|
833
1000
|
return;
|
|
834
1001
|
}
|
|
835
1002
|
while (topic.hasMore && !findTopicPostByFloor(topic, floor)) {
|
|
1003
|
+
const previousLoaded = topic.loaded;
|
|
836
1004
|
await this.loadNextTopicPage(signal, true);
|
|
1005
|
+
if (topic.loaded === previousLoaded)
|
|
1006
|
+
break;
|
|
837
1007
|
}
|
|
838
1008
|
const post = findTopicPostByFloor(topic, floor);
|
|
839
|
-
|
|
840
|
-
this.state.status = post ?
|
|
1009
|
+
topic.cursorLine = post?.lineStart ?? topic.cursorLine;
|
|
1010
|
+
this.state.status = post ? "" : `未找到 ${floor} 楼`;
|
|
841
1011
|
this.render();
|
|
842
1012
|
}
|
|
843
1013
|
async runReadOnlyAction(action, signal) {
|
|
@@ -955,7 +1125,7 @@ export class TuiController {
|
|
|
955
1125
|
const topic = this.state.topic;
|
|
956
1126
|
if (!topic)
|
|
957
1127
|
return;
|
|
958
|
-
const post = currentTopicPost(topic,
|
|
1128
|
+
const post = currentTopicPost(topic, topic.cursorLine);
|
|
959
1129
|
if (!post?.id) {
|
|
960
1130
|
this.state.status = "当前楼层没有可操作的帖子 ID";
|
|
961
1131
|
this.render();
|
|
@@ -974,7 +1144,7 @@ export class TuiController {
|
|
|
974
1144
|
const topic = this.state.topic;
|
|
975
1145
|
if (!topic)
|
|
976
1146
|
return;
|
|
977
|
-
const post = currentTopicPost(topic,
|
|
1147
|
+
const post = currentTopicPost(topic, topic.cursorLine);
|
|
978
1148
|
if (!post?.userId) {
|
|
979
1149
|
this.state.status = "当前楼层没有用户 ID";
|
|
980
1150
|
this.render();
|
|
@@ -1030,7 +1200,7 @@ export class TuiController {
|
|
|
1030
1200
|
const topic = this.state.topic;
|
|
1031
1201
|
if (!topic)
|
|
1032
1202
|
return;
|
|
1033
|
-
const post = currentTopicPost(topic,
|
|
1203
|
+
const post = currentTopicPost(topic, topic.cursorLine);
|
|
1034
1204
|
if (!post?.id)
|
|
1035
1205
|
return;
|
|
1036
1206
|
try {
|
|
@@ -1081,7 +1251,7 @@ export class TuiController {
|
|
|
1081
1251
|
if (!topic)
|
|
1082
1252
|
return undefined;
|
|
1083
1253
|
for (const post of topic.posts) {
|
|
1084
|
-
const line = post.lines.find((entry) => entry.line ===
|
|
1254
|
+
const line = post.lines.find((entry) => entry.line === topic.cursorLine);
|
|
1085
1255
|
if (line)
|
|
1086
1256
|
return line;
|
|
1087
1257
|
}
|
|
@@ -1148,6 +1318,147 @@ export class TuiController {
|
|
|
1148
1318
|
}
|
|
1149
1319
|
this.render();
|
|
1150
1320
|
}
|
|
1321
|
+
async openKeybindingEditor() {
|
|
1322
|
+
// 显示快捷键配置信息
|
|
1323
|
+
const config = this.keybindings.getConfig();
|
|
1324
|
+
const lines = [
|
|
1325
|
+
"快捷键配置文件: ~/.cc98-cli/keybindings.json",
|
|
1326
|
+
"",
|
|
1327
|
+
"当前配置:"
|
|
1328
|
+
];
|
|
1329
|
+
// 显示主要快捷键
|
|
1330
|
+
const mainActions = [
|
|
1331
|
+
"moveUp", "moveDown", "moveLeft", "moveRight", "confirm", "back",
|
|
1332
|
+
"search", "refresh", "menu", "help", "quit",
|
|
1333
|
+
"topicNextPage", "topicPrevPage", "topicNextFloor", "topicPrevFloor",
|
|
1334
|
+
"topicJumpPage", "topicJumpFloor", "topicJumpLast"
|
|
1335
|
+
];
|
|
1336
|
+
for (const action of mainActions) {
|
|
1337
|
+
const keys = config[action] ?? [];
|
|
1338
|
+
const desc = this.keybindings.getActionDescription(action);
|
|
1339
|
+
const keyStr = keys.map(k => this.keybindings.formatKey(k)).join("/");
|
|
1340
|
+
lines.push(` ${desc}: ${keyStr}`);
|
|
1341
|
+
}
|
|
1342
|
+
lines.push("", "编辑配置文件后重启生效。", "", "按 Esc 返回设置");
|
|
1343
|
+
this.state.modal = "info";
|
|
1344
|
+
this.state.infoTitle = "快捷键设置";
|
|
1345
|
+
this.state.infoLines = lines;
|
|
1346
|
+
this.render();
|
|
1347
|
+
}
|
|
1348
|
+
async openAccountSwitcher() {
|
|
1349
|
+
try {
|
|
1350
|
+
const accounts = await this.tokenStore.listAccounts();
|
|
1351
|
+
const currentAccount = await this.tokenStore.getCurrentAccountName();
|
|
1352
|
+
if (accounts.length === 0) {
|
|
1353
|
+
this.state.modal = "info";
|
|
1354
|
+
this.state.infoTitle = "切换账号";
|
|
1355
|
+
this.state.infoLines = ["暂无保存的账号", "", "请先登录账号。"];
|
|
1356
|
+
this.render();
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
// 构建账号列表
|
|
1360
|
+
const items = accounts.map(account => ({
|
|
1361
|
+
title: account.displayName || account.username || account.account,
|
|
1362
|
+
meta: `account:${account.account}`,
|
|
1363
|
+
detail: `${account.account === currentAccount ? "✓ 当前" : "切换到此账号"}${account.userId ? ` · ID: ${account.userId}` : ""}`
|
|
1364
|
+
}));
|
|
1365
|
+
this.snapshotParent();
|
|
1366
|
+
this.state.viewTitle = "切换账号";
|
|
1367
|
+
this.state.items = items;
|
|
1368
|
+
this.state.stats = [{ title: "账号数", detail: `${accounts.length}` }];
|
|
1369
|
+
this.state.itemIndex = accounts.findIndex(a => a.account === currentAccount);
|
|
1370
|
+
this.state.scroll = 0;
|
|
1371
|
+
this.state.focus = "content";
|
|
1372
|
+
this.state.mode = "list";
|
|
1373
|
+
this.state.status = "选择账号: j/k 选择 Enter 切换 h 返回";
|
|
1374
|
+
this.render();
|
|
1375
|
+
}
|
|
1376
|
+
catch (error) {
|
|
1377
|
+
this.state.status = error instanceof Error ? error.message : "读取账号失败";
|
|
1378
|
+
this.render();
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
async switchAccount(accountName) {
|
|
1382
|
+
try {
|
|
1383
|
+
await this.tokenStore.useAccount(accountName);
|
|
1384
|
+
this.state.status = `已切换到账号: ${accountName}`;
|
|
1385
|
+
this.state.parentList = undefined;
|
|
1386
|
+
this.state.mode = "list";
|
|
1387
|
+
this.state.focus = "nav";
|
|
1388
|
+
await this.load(true);
|
|
1389
|
+
}
|
|
1390
|
+
catch (error) {
|
|
1391
|
+
this.state.status = error instanceof Error ? error.message : "切换账号失败";
|
|
1392
|
+
this.render();
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async confirmLogout() {
|
|
1396
|
+
const account = await this.tokenStore.getCurrentAccountName();
|
|
1397
|
+
const lines = [
|
|
1398
|
+
`当前账号: ${account || "未知"}`,
|
|
1399
|
+
"",
|
|
1400
|
+
"退出登录将清除所有保存的账号信息。",
|
|
1401
|
+
"清除后需要重新登录。",
|
|
1402
|
+
"",
|
|
1403
|
+
`${this.keybindings.formatActionKeys("confirm")} 确认 ${this.keybindings.formatActionKeys("back")} 取消`
|
|
1404
|
+
];
|
|
1405
|
+
this.state.modal = "info";
|
|
1406
|
+
this.state.infoTitle = "退出登录";
|
|
1407
|
+
this.state.infoLines = lines;
|
|
1408
|
+
this.state.confirmCallback = () => void this.performLogout();
|
|
1409
|
+
this.render();
|
|
1410
|
+
}
|
|
1411
|
+
async performLogout() {
|
|
1412
|
+
try {
|
|
1413
|
+
await this.tokenStore.clear();
|
|
1414
|
+
this.state.status = "已退出登录";
|
|
1415
|
+
this.state.parentList = undefined;
|
|
1416
|
+
this.state.mode = "list";
|
|
1417
|
+
this.state.focus = "nav";
|
|
1418
|
+
await this.load(true);
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
this.state.status = error instanceof Error ? error.message : "退出失败";
|
|
1422
|
+
this.render();
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async openCacheManager() {
|
|
1426
|
+
try {
|
|
1427
|
+
const stats = await this.client.getCacheStats();
|
|
1428
|
+
const cacheDir = "~/.cc98-cli/cache/";
|
|
1429
|
+
const lines = [
|
|
1430
|
+
`缓存目录: ${cacheDir}`,
|
|
1431
|
+
`文件数量: ${stats.fileCacheEntries}`,
|
|
1432
|
+
"",
|
|
1433
|
+
"缓存策略:",
|
|
1434
|
+
" 版面主题: 30s",
|
|
1435
|
+
" 版面信息: 24h",
|
|
1436
|
+
" 用户信息: 5min",
|
|
1437
|
+
"",
|
|
1438
|
+
"Enter 清理缓存 Esc 返回"
|
|
1439
|
+
];
|
|
1440
|
+
this.state.modal = "info";
|
|
1441
|
+
this.state.infoTitle = "缓存管理";
|
|
1442
|
+
this.state.infoLines = lines;
|
|
1443
|
+
this.state.confirmCallback = () => void this.clearCache();
|
|
1444
|
+
this.render();
|
|
1445
|
+
}
|
|
1446
|
+
catch (error) {
|
|
1447
|
+
this.state.status = error instanceof Error ? error.message : "读取缓存信息失败";
|
|
1448
|
+
this.render();
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
async clearCache() {
|
|
1452
|
+
try {
|
|
1453
|
+
await this.client.clearCache();
|
|
1454
|
+
this.state.status = "缓存已清理";
|
|
1455
|
+
await this.load(true);
|
|
1456
|
+
}
|
|
1457
|
+
catch {
|
|
1458
|
+
this.state.status = "缓存清理失败";
|
|
1459
|
+
this.render();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1151
1462
|
async openFriendUsers(type, signal) {
|
|
1152
1463
|
const ids = asArray(await this.client.getFriendIds(type, 0, 20, false, signal)).filter((id) => typeof id === "number");
|
|
1153
1464
|
const users = asArray(await this.client.getUsers(ids, false, signal));
|
|
@@ -1196,5 +1507,51 @@ export class TuiController {
|
|
|
1196
1507
|
this.state.focus = "content";
|
|
1197
1508
|
this.state.scroll = 0;
|
|
1198
1509
|
}
|
|
1510
|
+
async checkUpdate(forceShow = false) {
|
|
1511
|
+
if (forceShow) {
|
|
1512
|
+
this.state.status = "正在检查 GitHub Release...";
|
|
1513
|
+
this.render();
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
const result = await checkForUpdate();
|
|
1517
|
+
if (!result.updateAvailable || !result.latest) {
|
|
1518
|
+
this.state.updateAvailable = undefined;
|
|
1519
|
+
if (forceShow) {
|
|
1520
|
+
this.state.status = result.message;
|
|
1521
|
+
this.render();
|
|
1522
|
+
}
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const lastSeen = await this.settingsStore.getLastSeenVersion();
|
|
1526
|
+
const isNew = forceShow || lastSeen !== result.latest.version;
|
|
1527
|
+
this.state.updateAvailable = {
|
|
1528
|
+
version: result.latest.version,
|
|
1529
|
+
tagName: result.latest.tagName,
|
|
1530
|
+
url: result.latest.url,
|
|
1531
|
+
body: result.latest.body,
|
|
1532
|
+
isNew
|
|
1533
|
+
};
|
|
1534
|
+
if (forceShow) {
|
|
1535
|
+
this.state.status = result.message;
|
|
1536
|
+
}
|
|
1537
|
+
this.render();
|
|
1538
|
+
}
|
|
1539
|
+
catch (error) {
|
|
1540
|
+
if (forceShow) {
|
|
1541
|
+
this.state.status = error instanceof Error ? error.message : "检查更新失败";
|
|
1542
|
+
this.render();
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
dismissUpdate() {
|
|
1547
|
+
if (this.state.updateAvailable) {
|
|
1548
|
+
const version = this.state.updateAvailable.version;
|
|
1549
|
+
this.state.updateAvailable = undefined;
|
|
1550
|
+
this.render();
|
|
1551
|
+
void this.settingsStore.setLastSeenVersion(version).catch(() => {
|
|
1552
|
+
// 忽略已读状态写入失败,避免影响 TUI 操作。
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1199
1556
|
}
|
|
1200
1557
|
//# sourceMappingURL=controller.js.map
|