@walavave/cc98-cli 0.2.2 → 0.2.4
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 +32 -0
- package/README.md +9 -1
- package/dist/api/client.d.ts +11 -4
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +46 -8
- package/dist/api/client.js.map +1 -1
- package/dist/api/endpoints.d.ts +6 -0
- package/dist/api/endpoints.d.ts.map +1 -1
- package/dist/api/endpoints.js +6 -0
- package/dist/api/endpoints.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -2
- package/dist/config.js.map +1 -1
- package/dist/tui/app-data.d.ts +7 -24
- package/dist/tui/app-data.d.ts.map +1 -1
- package/dist/tui/app-data.js +7 -944
- package/dist/tui/app-data.js.map +1 -1
- package/dist/tui/app-runtime/content/list.d.ts +5 -0
- package/dist/tui/app-runtime/content/list.d.ts.map +1 -0
- package/dist/tui/app-runtime/content/list.js +115 -0
- package/dist/tui/app-runtime/content/list.js.map +1 -0
- package/dist/tui/app-runtime/content/navigation.d.ts +5 -0
- package/dist/tui/app-runtime/content/navigation.d.ts.map +1 -0
- package/dist/tui/app-runtime/content/navigation.js +104 -0
- package/dist/tui/app-runtime/content/navigation.js.map +1 -0
- package/dist/tui/app-runtime/content/search.d.ts +4 -0
- package/dist/tui/app-runtime/content/search.d.ts.map +1 -0
- package/dist/tui/app-runtime/content/search.js +122 -0
- package/dist/tui/app-runtime/content/search.js.map +1 -0
- package/dist/tui/app-runtime/content.d.ts +3 -8
- package/dist/tui/app-runtime/content.d.ts.map +1 -1
- package/dist/tui/app-runtime/content.js +3 -274
- package/dist/tui/app-runtime/content.js.map +1 -1
- package/dist/tui/app-runtime/interactions.d.ts.map +1 -1
- package/dist/tui/app-runtime/interactions.js +5 -17
- package/dist/tui/app-runtime/interactions.js.map +1 -1
- package/dist/tui/app-runtime/keyboard.d.ts.map +1 -1
- package/dist/tui/app-runtime/keyboard.js +32 -10
- package/dist/tui/app-runtime/keyboard.js.map +1 -1
- package/dist/tui/app-runtime/me.d.ts +5 -0
- package/dist/tui/app-runtime/me.d.ts.map +1 -0
- package/dist/tui/app-runtime/me.js +83 -0
- package/dist/tui/app-runtime/me.js.map +1 -0
- package/dist/tui/app-runtime/modals.d.ts.map +1 -1
- package/dist/tui/app-runtime/modals.js +27 -11
- package/dist/tui/app-runtime/modals.js.map +1 -1
- package/dist/tui/app-runtime/mouse.d.ts.map +1 -1
- package/dist/tui/app-runtime/mouse.js +8 -2
- package/dist/tui/app-runtime/mouse.js.map +1 -1
- package/dist/tui/app-runtime/state.d.ts.map +1 -1
- package/dist/tui/app-runtime/state.js +6 -3
- package/dist/tui/app-runtime/state.js.map +1 -1
- package/dist/tui/app-runtime/topic.d.ts.map +1 -1
- package/dist/tui/app-runtime/topic.js +75 -5
- package/dist/tui/app-runtime/topic.js.map +1 -1
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +12 -2
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/cached-client.d.ts +13 -0
- package/dist/tui/cached-client.d.ts.map +1 -1
- package/dist/tui/cached-client.js +53 -0
- package/dist/tui/cached-client.js.map +1 -1
- package/dist/tui/data/accounts.d.ts +6 -0
- package/dist/tui/data/accounts.d.ts.map +1 -0
- package/dist/tui/data/accounts.js +30 -0
- package/dist/tui/data/accounts.js.map +1 -0
- package/dist/tui/data/content.d.ts +9 -0
- package/dist/tui/data/content.d.ts.map +1 -0
- package/dist/tui/data/content.js +196 -0
- package/dist/tui/data/content.js.map +1 -0
- package/dist/tui/data/feed-status.d.ts +3 -0
- package/dist/tui/data/feed-status.d.ts.map +1 -0
- package/dist/tui/data/feed-status.js +37 -0
- package/dist/tui/data/feed-status.js.map +1 -0
- package/dist/tui/data/items.d.ts +6 -0
- package/dist/tui/data/items.d.ts.map +1 -0
- package/dist/tui/data/items.js +67 -0
- package/dist/tui/data/items.js.map +1 -0
- package/dist/tui/data/me.d.ts +8 -0
- package/dist/tui/data/me.d.ts.map +1 -0
- package/dist/tui/data/me.js +167 -0
- package/dist/tui/data/me.js.map +1 -0
- package/dist/tui/data/navigation-state.d.ts +12 -0
- package/dist/tui/data/navigation-state.d.ts.map +1 -0
- package/dist/tui/data/navigation-state.js +118 -0
- package/dist/tui/data/navigation-state.js.map +1 -0
- package/dist/tui/data/search.d.ts +5 -0
- package/dist/tui/data/search.d.ts.map +1 -0
- package/dist/tui/data/search.js +89 -0
- package/dist/tui/data/search.js.map +1 -0
- package/dist/tui/data/topic.d.ts +8 -0
- package/dist/tui/data/topic.d.ts.map +1 -0
- package/dist/tui/data/topic.js +406 -0
- package/dist/tui/data/topic.js.map +1 -0
- package/dist/tui/data/utils.d.ts +10 -0
- package/dist/tui/data/utils.d.ts.map +1 -0
- package/dist/tui/data/utils.js +49 -0
- package/dist/tui/data/utils.js.map +1 -0
- package/dist/tui/data/view-items.d.ts +16 -0
- package/dist/tui/data/view-items.d.ts.map +1 -0
- package/dist/tui/data/view-items.js +179 -0
- package/dist/tui/data/view-items.js.map +1 -0
- package/dist/tui/data/view-loader.d.ts +10 -0
- package/dist/tui/data/view-loader.d.ts.map +1 -0
- package/dist/tui/data/view-loader.js +157 -0
- package/dist/tui/data/view-loader.js.map +1 -0
- package/dist/tui/keymap.d.ts +1 -1
- package/dist/tui/keymap.d.ts.map +1 -1
- package/dist/tui/keymap.js +2 -0
- package/dist/tui/keymap.js.map +1 -1
- package/dist/tui/renderer.d.ts +2 -0
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +186 -45
- package/dist/tui/renderer.js.map +1 -1
- package/dist/tui/tui-model.d.ts +52 -1
- package/dist/tui/tui-model.d.ts.map +1 -1
- package/dist/tui/tui-model.js.map +1 -1
- package/dist/tui/ubb-renderer.d.ts.map +1 -1
- package/dist/tui/ubb-renderer.js +7 -2
- package/dist/tui/ubb-renderer.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/tui/app-data.js
CHANGED
|
@@ -1,945 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
return {
|
|
9
|
-
title: "搜索",
|
|
10
|
-
query: "",
|
|
11
|
-
draft: "",
|
|
12
|
-
loaded: 0,
|
|
13
|
-
size: 10,
|
|
14
|
-
hasMore: false,
|
|
15
|
-
searched: false,
|
|
16
|
-
focus: "input"
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export async function openTopic(client, state, topicId, render, config, force = false, signal) {
|
|
20
|
-
state.mode = "topic";
|
|
21
|
-
state.loading = true;
|
|
22
|
-
state.loadingMore = false;
|
|
23
|
-
state.error = undefined;
|
|
24
|
-
state.scroll = 0;
|
|
25
|
-
state.imageViewer = undefined;
|
|
26
|
-
state.topic = {
|
|
27
|
-
topicId,
|
|
28
|
-
title: `#${topicId}`,
|
|
29
|
-
meta: "",
|
|
30
|
-
lines: [],
|
|
31
|
-
posts: [],
|
|
32
|
-
loaded: 0,
|
|
33
|
-
size: 10,
|
|
34
|
-
hasMore: true,
|
|
35
|
-
imageCount: 0,
|
|
36
|
-
linkCount: 0,
|
|
37
|
-
floorInput: ""
|
|
38
|
-
};
|
|
39
|
-
state.status = "正在打开帖子...";
|
|
40
|
-
render();
|
|
41
|
-
try {
|
|
42
|
-
const [topicRaw, postsRaw] = await Promise.all([
|
|
43
|
-
client.getTopic(topicId, force, signal),
|
|
44
|
-
client.getTopicPosts(topicId, 0, 10, force, signal)
|
|
45
|
-
]);
|
|
46
|
-
const topic = asObject(topicRaw);
|
|
47
|
-
const posts = asArray(postsRaw);
|
|
48
|
-
const reader = buildTopicReader(topicId, topic, posts, 10, config);
|
|
49
|
-
state.topic = reader;
|
|
50
|
-
state.viewTitle = reader.title;
|
|
51
|
-
void loadTopicImagePreviews(state, render, config, state.sidebarWidth);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
if (isAbortError(error)) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
58
|
-
state.status = state.parentList
|
|
59
|
-
? "版面读取失败;Esc/Backspace 返回版面列表 h 返回左栏 r 重试"
|
|
60
|
-
: "版面读取失败;h 返回左栏 r 重试";
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
state.loading = false;
|
|
64
|
-
if (!state.error && state.mode === "topic" && state.topic) {
|
|
65
|
-
state.status = getStatus(state);
|
|
66
|
-
}
|
|
67
|
-
render();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export async function openBoard(client, state, boardId, boardTitle, render, force = false, signal, pushParent = true) {
|
|
71
|
-
prepareListView(state, {
|
|
72
|
-
title: boardTitle,
|
|
73
|
-
status: "正在读取版面帖子...",
|
|
74
|
-
currentBoard: { boardId, title: boardTitle },
|
|
75
|
-
pushParent
|
|
76
|
-
});
|
|
77
|
-
render();
|
|
78
|
-
try {
|
|
79
|
-
const topics = asArray(await client.getBoardTopics(boardId, 0, 12, false, force, signal));
|
|
80
|
-
state.items = topics.map((topic) => topicItem(topic));
|
|
81
|
-
state.status = "版面帖子:j/k 选择 l 打开帖子 h 返回 r 刷新";
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
if (isAbortError(error)) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
state.loading = false;
|
|
91
|
-
render();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
export async function openChat(client, state, userId, title, render, force = false, signal, pushParent = true) {
|
|
95
|
-
prepareListView(state, {
|
|
96
|
-
title,
|
|
97
|
-
status: "正在读取私信...",
|
|
98
|
-
currentChat: { userId, title, loaded: 0, size: 10, hasMore: true },
|
|
99
|
-
pushParent
|
|
100
|
-
});
|
|
101
|
-
const chat = state.currentChat;
|
|
102
|
-
if (!chat) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
render();
|
|
106
|
-
try {
|
|
107
|
-
const messages = asArray(await client.getChatHistory(userId, 0, 10, force, signal));
|
|
108
|
-
state.items = chatMessageItems(messages, title, userId);
|
|
109
|
-
chat.loaded = messages.length;
|
|
110
|
-
chat.hasMore = messages.length === chat.size;
|
|
111
|
-
state.itemIndex = Math.max(0, state.items.length - 1);
|
|
112
|
-
state.status = chat.hasMore
|
|
113
|
-
? "私信:j/k 滚动 n/Space 更早消息 Esc/Backspace 返回联系人 h 返回左栏"
|
|
114
|
-
: "私信:j/k 滚动 Esc/Backspace 返回联系人 h 返回左栏";
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
if (isAbortError(error)) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
121
|
-
state.status = "私信读取失败;Esc/Backspace 返回联系人 h 返回左栏 r 重试";
|
|
122
|
-
}
|
|
123
|
-
finally {
|
|
124
|
-
state.loading = false;
|
|
125
|
-
render();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
export async function loadNextChatPage(client, state, render, signal) {
|
|
129
|
-
if (!state.currentChat || state.loadingMore || !state.currentChat.hasMore) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
state.loadingMore = true;
|
|
133
|
-
state.status = "正在读取更早私信...";
|
|
134
|
-
render();
|
|
135
|
-
try {
|
|
136
|
-
const chat = state.currentChat;
|
|
137
|
-
const messages = asArray(await client.getChatHistory(chat.userId, chat.loaded, chat.size, false, signal));
|
|
138
|
-
const olderItems = chatMessageItems(messages, chat.title, chat.userId);
|
|
139
|
-
state.items = [...olderItems, ...state.items];
|
|
140
|
-
state.itemIndex += olderItems.length;
|
|
141
|
-
state.scroll += olderItems.length;
|
|
142
|
-
chat.loaded += messages.length;
|
|
143
|
-
chat.hasMore = messages.length === chat.size;
|
|
144
|
-
state.status = chat.hasMore
|
|
145
|
-
? "私信:j/k 滚动 n/Space 更早消息 Esc/Backspace 返回联系人 h 返回左栏"
|
|
146
|
-
: "已到最早私信;j/k 滚动 Esc/Backspace 返回联系人 h 返回左栏";
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
if (isAbortError(error)) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
153
|
-
state.status = "更早私信读取失败;n/Space 重试 Esc/Backspace 返回联系人";
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
state.loadingMore = false;
|
|
157
|
-
render();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
export async function executeSearch(client, state, render, force = false, signal) {
|
|
161
|
-
const search = state.currentSearch;
|
|
162
|
-
if (!search) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const query = search.draft.trim();
|
|
166
|
-
search.query = query;
|
|
167
|
-
state.error = undefined;
|
|
168
|
-
state.itemIndex = 0;
|
|
169
|
-
state.scroll = 0;
|
|
170
|
-
state.imageViewer = undefined;
|
|
171
|
-
if (!query) {
|
|
172
|
-
search.loaded = 0;
|
|
173
|
-
search.hasMore = false;
|
|
174
|
-
search.searched = false;
|
|
175
|
-
search.focus = "input";
|
|
176
|
-
state.items = [];
|
|
177
|
-
state.loading = false;
|
|
178
|
-
state.loadingMore = false;
|
|
179
|
-
state.status = "搜索:输入关键词后 Enter 执行 j 进入结果 h 返回左栏";
|
|
180
|
-
render();
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
state.loading = true;
|
|
184
|
-
state.loadingMore = false;
|
|
185
|
-
state.status = `正在搜索 “${query}”...`;
|
|
186
|
-
render();
|
|
187
|
-
try {
|
|
188
|
-
const topics = asArray(await client.searchTopics(query, 0, search.size, force, signal));
|
|
189
|
-
state.items = topics.map((topic) => topicItem(topic));
|
|
190
|
-
search.loaded = topics.length;
|
|
191
|
-
search.hasMore = topics.length === search.size;
|
|
192
|
-
search.searched = true;
|
|
193
|
-
search.focus = state.items.length > 0 ? "results" : "input";
|
|
194
|
-
state.status = state.items.length === 0
|
|
195
|
-
? `未找到 “${query}” 的相关帖子`
|
|
196
|
-
: search.hasMore
|
|
197
|
-
? `搜索结果:${state.items.length} 项 j/k 选择 Enter 打开 n/Space 继续加载`
|
|
198
|
-
: `搜索结果:${state.items.length} 项 j/k 选择 Enter 打开`;
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
if (isAbortError(error)) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
205
|
-
state.status = "搜索失败;Enter 重试 h 返回左栏";
|
|
206
|
-
}
|
|
207
|
-
finally {
|
|
208
|
-
state.loading = false;
|
|
209
|
-
render();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
export async function loadNextSearchPage(client, state, render, signal) {
|
|
213
|
-
const search = state.currentSearch;
|
|
214
|
-
if (!search || !search.query || state.loading || state.loadingMore || !search.hasMore) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
state.loadingMore = true;
|
|
218
|
-
state.error = undefined;
|
|
219
|
-
state.status = `正在加载 “${search.query}” 的更多结果...`;
|
|
220
|
-
render();
|
|
221
|
-
try {
|
|
222
|
-
const topics = asArray(await client.searchTopics(search.query, search.loaded, search.size, false, signal));
|
|
223
|
-
const nextItems = topics.map((topic) => topicItem(topic));
|
|
224
|
-
state.items = [...state.items, ...nextItems];
|
|
225
|
-
search.loaded += topics.length;
|
|
226
|
-
search.hasMore = topics.length === search.size;
|
|
227
|
-
if (state.items.length > 0) {
|
|
228
|
-
search.focus = "results";
|
|
229
|
-
}
|
|
230
|
-
state.status = search.hasMore
|
|
231
|
-
? `搜索结果:${state.items.length} 项 j/k 选择 Enter 打开 n/Space 继续加载`
|
|
232
|
-
: `搜索结果:${state.items.length} 项 已全部加载`;
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
if (isAbortError(error)) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
239
|
-
state.status = "加载更多搜索结果失败;n/Space 重试";
|
|
240
|
-
}
|
|
241
|
-
finally {
|
|
242
|
-
state.loadingMore = false;
|
|
243
|
-
render();
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
export function restoreParentList(state) {
|
|
247
|
-
if (!state.parentList) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
applyListSnapshot(state, state.parentList);
|
|
251
|
-
state.parentList = undefined;
|
|
252
|
-
}
|
|
253
|
-
export async function loadNextTopicPage(client, state, render, config, signal, advanceAfterLoad = false) {
|
|
254
|
-
if (!state.topic || state.loadingMore || !state.topic.hasMore) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
state.loadingMore = true;
|
|
258
|
-
state.status = "正在加载下一页...";
|
|
259
|
-
render();
|
|
260
|
-
try {
|
|
261
|
-
const posts = asArray(await client.getTopicPosts(state.topic.topicId, state.topic.loaded, state.topic.size, false, signal));
|
|
262
|
-
appendTopicPosts(state.topic, posts, config, state.sidebarWidth);
|
|
263
|
-
void loadTopicImagePreviews(state, render, config, state.sidebarWidth);
|
|
264
|
-
state.topic.loaded += posts.length;
|
|
265
|
-
state.topic.hasMore = posts.length === state.topic.size;
|
|
266
|
-
if (advanceAfterLoad && posts.length > 0) {
|
|
267
|
-
state.scroll = Math.min(Math.max(0, state.topic.lines.length - 1), state.scroll + 1);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
if (isAbortError(error)) {
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
275
|
-
}
|
|
276
|
-
finally {
|
|
277
|
-
state.loadingMore = false;
|
|
278
|
-
if (!state.error && state.mode === "topic" && state.topic) {
|
|
279
|
-
state.status = getStatus(state);
|
|
280
|
-
}
|
|
281
|
-
render();
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
export async function jumpToTopicFloor(client, state, floor, render, config, signal) {
|
|
285
|
-
const topic = state.topic;
|
|
286
|
-
if (!topic) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const loaded = findTopicPostByFloor(topic, floor);
|
|
290
|
-
if (loaded) {
|
|
291
|
-
state.scroll = loaded.lineStart;
|
|
292
|
-
state.status = getStatus(state);
|
|
293
|
-
render();
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const from = Math.floor((floor - 1) / topic.size) * topic.size;
|
|
297
|
-
state.loadingMore = true;
|
|
298
|
-
state.status = `正在读取 ${floor} 楼...`;
|
|
299
|
-
render();
|
|
300
|
-
try {
|
|
301
|
-
const posts = asArray(await client.getTopicPosts(topic.topicId, from, topic.size, false, signal));
|
|
302
|
-
appendTopicPosts(topic, posts, config, state.sidebarWidth);
|
|
303
|
-
topic.posts.sort((left, right) => (left.floor ?? 0) - (right.floor ?? 0));
|
|
304
|
-
void loadTopicImagePreviews(state, render, config, state.sidebarWidth);
|
|
305
|
-
topic.loaded = Math.max(topic.loaded, from + posts.length);
|
|
306
|
-
topic.hasMore = posts.length === topic.size;
|
|
307
|
-
const target = findTopicPostByFloor(topic, floor);
|
|
308
|
-
if (target) {
|
|
309
|
-
state.scroll = target.lineStart;
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
state.status = `未找到 ${floor} 楼`;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
catch (error) {
|
|
316
|
-
if (!isAbortError(error)) {
|
|
317
|
-
state.error = error instanceof Error ? error.message : String(error);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
finally {
|
|
321
|
-
state.loadingMore = false;
|
|
322
|
-
if (!state.error && findTopicPostByFloor(topic, floor)) {
|
|
323
|
-
state.status = getStatus(state);
|
|
324
|
-
}
|
|
325
|
-
render();
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
export function jumpRelativeTopicFloor(state, delta) {
|
|
329
|
-
const topic = state.topic;
|
|
330
|
-
if (!topic || topic.posts.length === 0) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const current = currentTopicPost(topic, state.scroll);
|
|
334
|
-
const currentIndex = current ? topic.posts.indexOf(current) : 0;
|
|
335
|
-
const next = topic.posts[Math.min(topic.posts.length - 1, Math.max(0, currentIndex + delta))];
|
|
336
|
-
if (next) {
|
|
337
|
-
state.scroll = next.lineStart;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
export async function refreshAccounts(state, tokenStore) {
|
|
341
|
-
const accounts = await tokenStore.listAccounts();
|
|
342
|
-
const current = await tokenStore.getCurrentAccountName();
|
|
343
|
-
state.account = current;
|
|
344
|
-
state.accountModal.accounts = accounts.map((account) => ({
|
|
345
|
-
account: account.account,
|
|
346
|
-
detail: account.displayName ?? account.username ?? (account.userId ? `#${account.userId}` : "本地账号"),
|
|
347
|
-
isCurrent: account.account === current
|
|
348
|
-
}));
|
|
349
|
-
state.accountModal.selectedIndex = Math.min(state.accountModal.accounts.findIndex((account) => account.isCurrent), state.accountModal.accounts.length);
|
|
350
|
-
if (state.accountModal.selectedIndex < 0) {
|
|
351
|
-
state.accountModal.selectedIndex = 0;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
export function getDefaultAccountName(me, username) {
|
|
355
|
-
if (typeof me.name === "string" && me.name.trim()) {
|
|
356
|
-
return me.name.trim();
|
|
357
|
-
}
|
|
358
|
-
if (typeof me.id === "number") {
|
|
359
|
-
return String(me.id);
|
|
360
|
-
}
|
|
361
|
-
return username;
|
|
362
|
-
}
|
|
363
|
-
export function normalizeLoginMessage(error) {
|
|
364
|
-
if (error instanceof Error) {
|
|
365
|
-
return error.message.replace(/^login failed:\s*/i, "");
|
|
366
|
-
}
|
|
367
|
-
return String(error);
|
|
368
|
-
}
|
|
369
|
-
export async function loadView(client, view, force, signal) {
|
|
370
|
-
switch (view) {
|
|
371
|
-
case "hot": {
|
|
372
|
-
const [index, unread] = await Promise.all([
|
|
373
|
-
client.getForumIndex(force, signal),
|
|
374
|
-
client.getUnreadCount(force, signal)
|
|
375
|
-
]);
|
|
376
|
-
const indexObject = asObject(index);
|
|
377
|
-
const unreadObject = asObject(unread);
|
|
378
|
-
const hotTopics = asArray(indexObject.hotTopic ?? indexObject.manualHotTopic);
|
|
379
|
-
return {
|
|
380
|
-
title: "十大",
|
|
381
|
-
items: hotTopics.map((topic) => topicItem(topic)),
|
|
382
|
-
overview: overviewStats(indexObject, unreadObject)
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
case "new": {
|
|
386
|
-
const topics = asArray(await client.getNewTopics(0, 12, force, signal));
|
|
387
|
-
return {
|
|
388
|
-
title: "最新",
|
|
389
|
-
items: topics.map((topic) => topicItem(topic))
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
case "search": {
|
|
393
|
-
return {
|
|
394
|
-
title: "搜索",
|
|
395
|
-
items: [],
|
|
396
|
-
status: "搜索:按 Enter 进入输入框并搜索主题"
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
case "boards": {
|
|
400
|
-
const sections = asArray(await client.getAllBoards(force, signal));
|
|
401
|
-
const allBoards = flattenBoards(sections);
|
|
402
|
-
return {
|
|
403
|
-
title: "版面",
|
|
404
|
-
items: allBoards.slice(0, 14),
|
|
405
|
-
status: "版面:j/k 选择 l 进入版面 h 返回 r 刷新"
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
case "following": {
|
|
409
|
-
const topics = asArray(await client.getFolloweeTopics(0, 12, force, signal));
|
|
410
|
-
return {
|
|
411
|
-
title: "关注",
|
|
412
|
-
items: topics.map((topic) => topicItem(topic)),
|
|
413
|
-
status: "关注:j/k 选择 l 打开帖子 h 返回 r 刷新"
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
case "favorite": {
|
|
417
|
-
const [meRaw, sectionsRaw] = await Promise.all([
|
|
418
|
-
client.getMe(force, signal),
|
|
419
|
-
client.getAllBoards(false, signal)
|
|
420
|
-
]);
|
|
421
|
-
const customBoards = asArray(asObject(meRaw).customBoards).filter((id) => typeof id === "number");
|
|
422
|
-
const allBoards = flattenBoards(asArray(sectionsRaw));
|
|
423
|
-
const boardById = new Map(allBoards.filter((board) => board.boardId !== undefined).map((board) => [board.boardId, board]));
|
|
424
|
-
const topicGroups = await mapLimit(customBoards, 3, async (boardId) => {
|
|
425
|
-
const board = boardById.get(boardId);
|
|
426
|
-
const topics = asArray(await client.getBoardTopics(boardId, 0, 3, false, force, signal));
|
|
427
|
-
return topics.map((topic) => topicItem(topic, board));
|
|
428
|
-
});
|
|
429
|
-
const items = topicGroups.flat().sort((left, right) => (right.sortTime ?? 0) - (left.sortTime ?? 0)).slice(0, 18);
|
|
430
|
-
return {
|
|
431
|
-
title: "收藏",
|
|
432
|
-
items,
|
|
433
|
-
status: "收藏:j/k 选择 l 打开帖子 h 返回 r 刷新"
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
case "messages": {
|
|
437
|
-
const [unread, recent] = await Promise.all([
|
|
438
|
-
client.getUnreadCount(force, signal),
|
|
439
|
-
client.getRecentChats(0, 10, force, signal)
|
|
440
|
-
]);
|
|
441
|
-
const unreadObject = asObject(unread);
|
|
442
|
-
const unreadEntries = unreadStats(unreadObject);
|
|
443
|
-
const chats = asArray(recent);
|
|
444
|
-
const userNames = await loadChatUserNames(client, chats, force, signal);
|
|
445
|
-
const unreadItems = unreadEntries
|
|
446
|
-
.filter((entry) => entry.detail !== "0" && entry.detail !== "-")
|
|
447
|
-
.map((entry) => ({
|
|
448
|
-
title: `未读 ${entry.title}`,
|
|
449
|
-
detail: entry.detail
|
|
450
|
-
}));
|
|
451
|
-
const chatItems = chats.length > 0
|
|
452
|
-
? chats.map((chat) => chatItem(chat, userNames))
|
|
453
|
-
: [{ title: "暂无最近私信", meta: "recent-contact-users" }];
|
|
454
|
-
return {
|
|
455
|
-
title: "消息",
|
|
456
|
-
items: [...unreadItems, ...chatItems],
|
|
457
|
-
status: "消息:j/k 选择 l 打开会话 h 返回 r 刷新"
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
case "me": {
|
|
461
|
-
const [me, cacheStats] = await Promise.all([
|
|
462
|
-
client.getMe(force, signal),
|
|
463
|
-
client.getCacheStats()
|
|
464
|
-
]);
|
|
465
|
-
const meObject = asObject(me);
|
|
466
|
-
return {
|
|
467
|
-
title: "我的",
|
|
468
|
-
items: [
|
|
469
|
-
item("昵称", meObject.name),
|
|
470
|
-
item("用户 ID", meObject.id),
|
|
471
|
-
item("等级", meObject.levelTitle ?? meObject.groupName),
|
|
472
|
-
item("发帖数", meObject.postCount),
|
|
473
|
-
item("财富", meObject.wealth),
|
|
474
|
-
item("关注", meObject.followCount),
|
|
475
|
-
item("粉丝", meObject.fanCount),
|
|
476
|
-
item("缓存文件", cacheStats.fileCacheEntries)
|
|
477
|
-
]
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
case "settings": {
|
|
481
|
-
return {
|
|
482
|
-
title: "设置",
|
|
483
|
-
items: settingsItems,
|
|
484
|
-
status: "设置:j/k 选择 l 执行 h 返回"
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
function currentTopicWidthEstimate(sidebarWidthOverride) {
|
|
490
|
-
const width = process.stdout.columns || Number(process.env.COLUMNS) || 80;
|
|
491
|
-
const sidebarWidth = getSidebarWidth(width, sidebarWidthOverride);
|
|
492
|
-
const sidebarRuleWidth = sidebarWidth > 0 ? 1 : 0;
|
|
493
|
-
return Math.max(24, width - sidebarWidth - sidebarRuleWidth);
|
|
494
|
-
}
|
|
495
|
-
function snapshotCurrentList(state) {
|
|
496
|
-
return {
|
|
497
|
-
title: state.viewTitle,
|
|
498
|
-
items: state.items,
|
|
499
|
-
itemIndex: state.itemIndex,
|
|
500
|
-
status: state.status
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
function prepareListView(state, options) {
|
|
504
|
-
if (options.pushParent) {
|
|
505
|
-
state.parentList = snapshotCurrentList(state);
|
|
506
|
-
}
|
|
507
|
-
state.mode = "list";
|
|
508
|
-
state.focus = "content";
|
|
509
|
-
state.loading = true;
|
|
510
|
-
state.loadingMore = false;
|
|
511
|
-
state.error = undefined;
|
|
512
|
-
state.itemIndex = 0;
|
|
513
|
-
state.scroll = 0;
|
|
514
|
-
state.topic = undefined;
|
|
515
|
-
state.imageViewer = undefined;
|
|
516
|
-
state.currentBoard = options.currentBoard;
|
|
517
|
-
state.currentChat = options.currentChat;
|
|
518
|
-
state.currentSearch = undefined;
|
|
519
|
-
state.viewTitle = options.title;
|
|
520
|
-
state.items = [];
|
|
521
|
-
state.status = options.status;
|
|
522
|
-
}
|
|
523
|
-
function applyListSnapshot(state, snapshot) {
|
|
524
|
-
state.mode = "list";
|
|
525
|
-
state.focus = "content";
|
|
526
|
-
state.loading = false;
|
|
527
|
-
state.loadingMore = false;
|
|
528
|
-
state.error = undefined;
|
|
529
|
-
state.topic = undefined;
|
|
530
|
-
state.imageViewer = undefined;
|
|
531
|
-
state.currentBoard = undefined;
|
|
532
|
-
state.currentChat = undefined;
|
|
533
|
-
state.currentSearch = undefined;
|
|
534
|
-
state.viewTitle = snapshot.title;
|
|
535
|
-
state.items = snapshot.items;
|
|
536
|
-
state.itemIndex = snapshot.itemIndex;
|
|
537
|
-
state.status = snapshot.status;
|
|
538
|
-
}
|
|
539
|
-
async function loadTopicImagePreviews(state, render, config, sidebarWidthOverride) {
|
|
540
|
-
const topic = state.topic;
|
|
541
|
-
if (!topic || !config.previewImages) {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
const width = Math.max(12, currentTopicWidthEstimate(sidebarWidthOverride) - 4);
|
|
545
|
-
const imageLines = topic.posts
|
|
546
|
-
.flatMap((post) => post.lines)
|
|
547
|
-
.filter((line) => line.kind === "image" &&
|
|
548
|
-
line.imageUrl);
|
|
549
|
-
const previewEnabled = supportsImagePreview();
|
|
550
|
-
for (const line of imageLines) {
|
|
551
|
-
try {
|
|
552
|
-
if (state.topic !== topic) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
const maxRows = isEmotionAssetPath(line.imageUrl ?? "") ? emotionPreviewRows : imagePreviewRows;
|
|
556
|
-
if (!line.imagePreviewRows) {
|
|
557
|
-
const measured = isEmotionAssetPath(line.imageUrl ?? "")
|
|
558
|
-
? await measureEmotionPreview(line.imageUrl ?? "", width)
|
|
559
|
-
: await measureImagePreview(line.imageUrl ?? "", width, maxRows);
|
|
560
|
-
if (state.topic !== topic) {
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
if (measured) {
|
|
564
|
-
line.imagePreviewRows = measured.rows;
|
|
565
|
-
adjustTopicImageBlockHeight(topic, line, measured.rows, state);
|
|
566
|
-
render();
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
if (!previewEnabled || line.imagePreview) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
const rows = Math.max(1, Math.min(maxRows, line.imagePreviewRows ?? line.imageBlockRows ?? maxRows));
|
|
573
|
-
const preview = isEmotionAssetPath(line.imageUrl ?? "")
|
|
574
|
-
? await loadEmotionPreview(line.imageUrl ?? "", width)
|
|
575
|
-
: await loadImagePreview(line.imageUrl ?? "", width, rows);
|
|
576
|
-
if (state.topic !== topic) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
if (preview) {
|
|
580
|
-
line.imagePreview = preview.token;
|
|
581
|
-
line.imagePreviewRows = preview.size.rows;
|
|
582
|
-
adjustTopicImageBlockHeight(topic, line, preview.size.rows, state);
|
|
583
|
-
render();
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
catch {
|
|
587
|
-
// Keep the textual image placeholder if preview loading fails.
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
function buildTopicReader(topicId, topic, posts, size, config) {
|
|
592
|
-
const title = normalizeInlineText(String(topic.title ?? `#${topicId}`));
|
|
593
|
-
const meta = [
|
|
594
|
-
topic.userName,
|
|
595
|
-
topic.replyCount !== undefined ? `${topic.replyCount} 回复` : undefined,
|
|
596
|
-
topic.hitCount !== undefined ? `${topic.hitCount} 浏览` : undefined
|
|
597
|
-
].filter(Boolean).join(" · ");
|
|
598
|
-
const rendered = renderPosts(posts, currentTopicWidthEstimate(), config);
|
|
599
|
-
return {
|
|
600
|
-
topicId,
|
|
601
|
-
title,
|
|
602
|
-
meta,
|
|
603
|
-
lines: rendered.lines,
|
|
604
|
-
posts: rendered.posts,
|
|
605
|
-
loaded: posts.length,
|
|
606
|
-
size,
|
|
607
|
-
hasMore: posts.length === size,
|
|
608
|
-
imageCount: rendered.imageCount,
|
|
609
|
-
linkCount: rendered.linkCount,
|
|
610
|
-
floorInput: ""
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
function appendTopicPosts(topic, posts, config, sidebarWidthOverride) {
|
|
614
|
-
const next = renderPosts(posts, Math.max(36, currentTopicWidthEstimate(sidebarWidthOverride)), config, topic.lines.length);
|
|
615
|
-
topic.lines.push(...next.lines);
|
|
616
|
-
topic.posts.push(...next.posts);
|
|
617
|
-
topic.imageCount += next.imageCount;
|
|
618
|
-
topic.linkCount += next.linkCount;
|
|
619
|
-
}
|
|
620
|
-
function renderPosts(posts, width, config, lineOffset = 0) {
|
|
621
|
-
const lines = [];
|
|
622
|
-
const entries = [];
|
|
623
|
-
let imageCount = 0;
|
|
624
|
-
let linkCount = 0;
|
|
625
|
-
posts.forEach((postRaw) => {
|
|
626
|
-
const post = asObject(postRaw);
|
|
627
|
-
const lineStart = lineOffset + lines.length;
|
|
628
|
-
const postLines = [];
|
|
629
|
-
const floorNumber = asNumber(post.floor);
|
|
630
|
-
const floor = floorNumber !== undefined ? `#${floorNumber}` : "#?";
|
|
631
|
-
const author = String(post.userName ?? "匿名");
|
|
632
|
-
const time = typeof post.time === "string" ? post.time.replace("T", " ").slice(0, 16) : "";
|
|
633
|
-
const likeCount = asNumber(post.likeCount) ?? 0;
|
|
634
|
-
const dislikeCount = asNumber(post.dislikeCount) ?? 0;
|
|
635
|
-
const likeState = normalizeLikeState(post.likeState);
|
|
636
|
-
const reactions = ` · ${likeCount} 赞 · ${dislikeCount} 踩`;
|
|
637
|
-
const push = (text, kind, extra = {}) => {
|
|
638
|
-
const line = lineOffset + lines.length;
|
|
639
|
-
lines.push(text);
|
|
640
|
-
postLines.push({
|
|
641
|
-
line,
|
|
642
|
-
row: postLines.length,
|
|
643
|
-
floor: floorNumber,
|
|
644
|
-
kind,
|
|
645
|
-
text,
|
|
646
|
-
...extra
|
|
647
|
-
});
|
|
648
|
-
};
|
|
649
|
-
push(`${floor} ${author}${time ? ` · ${time}` : ""}${reactions}`, "header");
|
|
650
|
-
const contentWidth = Math.max(8, width - 2);
|
|
651
|
-
push(theme.border.horizontal.repeat(contentWidth), "divider");
|
|
652
|
-
const content = typeof post.content === "string" ? post.content : "";
|
|
653
|
-
const contentType = asNumber(post.contentType) ?? 0;
|
|
654
|
-
const rendered = contentType === 1
|
|
655
|
-
? renderMarkdownToLines(content, contentWidth, {
|
|
656
|
-
imagePreviewRows: config.previewImages ? imagePreviewRows : 0
|
|
657
|
-
})
|
|
658
|
-
: renderUbbToLines(content, contentWidth, {
|
|
659
|
-
imagePreviewRows: config.previewImages ? imagePreviewRows : 0
|
|
660
|
-
});
|
|
661
|
-
rendered.lines.forEach((renderedLine, index) => {
|
|
662
|
-
const imageIndex = parseBracketIndex(renderedLine, "image");
|
|
663
|
-
const linkIndex = parseBracketIndex(renderedLine, "link");
|
|
664
|
-
const imageBlockRows = imageIndex !== undefined ? imageBlockHeight(rendered.lines, index) : undefined;
|
|
665
|
-
const kind = renderedLine.trim() === ""
|
|
666
|
-
? "blank"
|
|
667
|
-
: imageIndex !== undefined
|
|
668
|
-
? "image"
|
|
669
|
-
: linkIndex !== undefined
|
|
670
|
-
? "link"
|
|
671
|
-
: renderedLine.startsWith(theme.quote.prefix)
|
|
672
|
-
? "quote"
|
|
673
|
-
: "text";
|
|
674
|
-
push(renderedLine, kind, {
|
|
675
|
-
imageIndex,
|
|
676
|
-
imageUrl: imageIndex !== undefined ? rendered.images[imageIndex - 1] : undefined,
|
|
677
|
-
imageBlockRows,
|
|
678
|
-
linkIndex,
|
|
679
|
-
linkUrl: linkIndex !== undefined ? rendered.links[linkIndex - 1] : undefined
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
push("", "blank");
|
|
683
|
-
const preview = rendered.lines.find((value) => value.trim() &&
|
|
684
|
-
!value.startsWith("[image ") &&
|
|
685
|
-
!value.startsWith("[link ")) ?? "";
|
|
686
|
-
entries.push({
|
|
687
|
-
id: asNumber(post.id),
|
|
688
|
-
floor: floorNumber,
|
|
689
|
-
author,
|
|
690
|
-
time,
|
|
691
|
-
likeCount,
|
|
692
|
-
dislikeCount,
|
|
693
|
-
likeState,
|
|
694
|
-
rating: formatRating(post),
|
|
695
|
-
preview,
|
|
696
|
-
lineStart,
|
|
697
|
-
lineEnd: lineOffset + lines.length - 1,
|
|
698
|
-
imageCount: rendered.images.length,
|
|
699
|
-
linkCount: rendered.links.length,
|
|
700
|
-
images: rendered.images,
|
|
701
|
-
links: rendered.links,
|
|
702
|
-
lines: postLines
|
|
703
|
-
});
|
|
704
|
-
imageCount += rendered.images.length;
|
|
705
|
-
linkCount += rendered.links.length;
|
|
706
|
-
});
|
|
707
|
-
return { lines, posts: entries, imageCount, linkCount };
|
|
708
|
-
}
|
|
709
|
-
function normalizeLikeState(value) {
|
|
710
|
-
return value === 1 || value === 2 ? value : 0;
|
|
711
|
-
}
|
|
712
|
-
function findTopicPostByFloor(topic, floor) {
|
|
713
|
-
return topic.posts.find((entry) => entry.floor === floor);
|
|
714
|
-
}
|
|
715
|
-
function imageBlockHeight(lines, start) {
|
|
716
|
-
let height = 1;
|
|
717
|
-
for (let index = start + 1; index < lines.length; index += 1) {
|
|
718
|
-
const line = lines[index] ?? "";
|
|
719
|
-
if (line.startsWith("[image ") || line.trim() !== "") {
|
|
720
|
-
break;
|
|
721
|
-
}
|
|
722
|
-
height += 1;
|
|
723
|
-
}
|
|
724
|
-
return height;
|
|
725
|
-
}
|
|
726
|
-
function adjustTopicImageBlockHeight(topic, line, nextRows, state) {
|
|
727
|
-
const post = topic.posts.find((entry) => entry.lines.includes(line));
|
|
728
|
-
if (!post) {
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
const startRow = line.row;
|
|
732
|
-
const currentRows = Math.max(1, line.imageBlockRows ?? 1);
|
|
733
|
-
const targetRows = Math.max(1, nextRows);
|
|
734
|
-
if (currentRows === targetRows) {
|
|
735
|
-
line.imageBlockRows = targetRows;
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
const originalLine = line.line;
|
|
739
|
-
const removeCount = Math.min(currentRows, Math.max(1, post.lines.length - startRow));
|
|
740
|
-
const delta = targetRows - currentRows;
|
|
741
|
-
const filler = Array.from({ length: targetRows - 1 }, () => ({
|
|
742
|
-
line: 0,
|
|
743
|
-
row: 0,
|
|
744
|
-
floor: line.floor,
|
|
745
|
-
kind: "blank",
|
|
746
|
-
text: ""
|
|
747
|
-
}));
|
|
748
|
-
line.imageBlockRows = targetRows;
|
|
749
|
-
post.lines.splice(startRow, removeCount, line, ...filler);
|
|
750
|
-
rebuildTopicLines(topic);
|
|
751
|
-
if (originalLine < state.scroll) {
|
|
752
|
-
state.scroll = Math.max(0, state.scroll + delta);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
function rebuildTopicLines(topic) {
|
|
756
|
-
const lines = [];
|
|
757
|
-
topic.posts.forEach((post) => {
|
|
758
|
-
post.lineStart = lines.length;
|
|
759
|
-
post.lines.forEach((entry, row) => {
|
|
760
|
-
entry.row = row;
|
|
761
|
-
entry.line = lines.length;
|
|
762
|
-
lines.push(entry.text);
|
|
763
|
-
});
|
|
764
|
-
post.lineEnd = Math.max(post.lineStart, lines.length - 1);
|
|
765
|
-
});
|
|
766
|
-
topic.lines = lines;
|
|
767
|
-
}
|
|
768
|
-
function parseBracketIndex(value, label) {
|
|
769
|
-
const match = new RegExp(`\\[${label} (\\d+)`).exec(value);
|
|
770
|
-
return match ? Number(match[1]) : undefined;
|
|
771
|
-
}
|
|
772
|
-
function formatRating(post) {
|
|
773
|
-
const value = post.rating ?? post.ratingCount ?? post.wealth ?? post.score;
|
|
774
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
775
|
-
return String(value);
|
|
776
|
-
}
|
|
777
|
-
if (typeof value === "string" && value.trim()) {
|
|
778
|
-
return value.trim();
|
|
779
|
-
}
|
|
780
|
-
return undefined;
|
|
781
|
-
}
|
|
782
|
-
function item(title, value, meta) {
|
|
783
|
-
return {
|
|
784
|
-
title,
|
|
785
|
-
meta,
|
|
786
|
-
detail: value === undefined || value === null ? "-" : String(value)
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
function topicItem(value, fallbackBoard) {
|
|
790
|
-
const topic = asObject(value);
|
|
791
|
-
const topicId = asNumber(topic.id ?? topic.Id);
|
|
792
|
-
const boardId = asNumber(topic.boardId ?? topic.BoardId) ?? fallbackBoard?.boardId;
|
|
793
|
-
const boardName = topic.boardName ?? topic.BoardName ?? fallbackBoard?.title;
|
|
794
|
-
const authorName = normalizeInlineText(String(topic.userName ?? topic.authorName ?? "")).trim() || "匿名";
|
|
795
|
-
return {
|
|
796
|
-
title: normalizeInlineText(String(topic.title ?? topic.Title ?? `#${topicId ?? ""}`)),
|
|
797
|
-
meta: [
|
|
798
|
-
boardName,
|
|
799
|
-
authorName,
|
|
800
|
-
topic.replyCount !== undefined ? `${topic.replyCount} 回复` : undefined,
|
|
801
|
-
topic.hitCount !== undefined ? `${topic.hitCount} 浏览` : undefined
|
|
802
|
-
]
|
|
803
|
-
.filter(Boolean)
|
|
804
|
-
.join(" · "),
|
|
805
|
-
detail: typeof topic.lastPostContent === "string" ? topic.lastPostContent.replace(/\s+/g, " ") : undefined,
|
|
806
|
-
topicId,
|
|
807
|
-
boardId,
|
|
808
|
-
sortTime: timestampOf(topic.lastPostTime ?? topic.updateTime ?? topic.time ?? topic.createTime)
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
async function loadChatUserNames(client, chats, force, signal) {
|
|
812
|
-
const ids = chats
|
|
813
|
-
.map((chat) => asNumber(asObject(chat).userId ?? asObject(chat).UserId))
|
|
814
|
-
.filter((id) => id !== undefined);
|
|
815
|
-
const users = asArray(await client.getBasicUsers(ids, force, signal));
|
|
816
|
-
return new Map(users.map((userRaw) => {
|
|
817
|
-
const user = asObject(userRaw);
|
|
818
|
-
const id = asNumber(user.id ?? user.Id);
|
|
819
|
-
const name = String(user.name ?? user.Name ?? (id !== undefined ? `#${id}` : "用户"));
|
|
820
|
-
return [id, name];
|
|
821
|
-
}).filter((entry) => entry[0] !== undefined));
|
|
822
|
-
}
|
|
823
|
-
function chatItem(value, userNames) {
|
|
824
|
-
const chat = asObject(value);
|
|
825
|
-
const userId = asNumber(chat.userId ?? chat.UserId);
|
|
826
|
-
const name = userId !== undefined ? userNames.get(userId) : undefined;
|
|
827
|
-
return {
|
|
828
|
-
title: String(name ?? chat.name ?? chat.userName ?? userId ?? "私信"),
|
|
829
|
-
meta: userId !== undefined ? `user #${userId}` : undefined,
|
|
830
|
-
detail: normalizePreview(String(chat.lastContent ?? chat.lastMessage ?? chat.content ?? "")),
|
|
831
|
-
chatUserId: userId
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
function chatMessageItems(messages, otherName, otherUserId) {
|
|
835
|
-
return [...messages].reverse().map((messageRaw) => {
|
|
836
|
-
const message = asObject(messageRaw);
|
|
837
|
-
const receiverId = asNumber(message.receiverId ?? message.ReceiverId);
|
|
838
|
-
const isMine = receiverId === otherUserId;
|
|
839
|
-
const time = typeof message.time === "string"
|
|
840
|
-
? message.time.replace("T", " ").slice(0, 16)
|
|
841
|
-
: "";
|
|
842
|
-
const content = normalizePreview(String(message.content ?? message.Content ?? ""));
|
|
843
|
-
return {
|
|
844
|
-
title: isMine ? `我 -> ${otherName}` : `${otherName} -> 我`,
|
|
845
|
-
meta: [time, receiverId !== undefined ? `receiver #${receiverId}` : undefined].filter(Boolean).join(" · "),
|
|
846
|
-
detail: content || "(空消息)"
|
|
847
|
-
};
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
function unreadStats(value) {
|
|
851
|
-
return [
|
|
852
|
-
item("系统", value.systemCount),
|
|
853
|
-
item("@", value.atCount),
|
|
854
|
-
item("回复", value.replyCount),
|
|
855
|
-
item("私信", value.messageCount)
|
|
856
|
-
];
|
|
857
|
-
}
|
|
858
|
-
function overviewStats(index, unread) {
|
|
859
|
-
const unreadTotal = ["systemCount", "atCount", "replyCount", "messageCount"].reduce((total, key) => {
|
|
860
|
-
const value = unread[key];
|
|
861
|
-
return total + (typeof value === "number" ? value : 0);
|
|
862
|
-
}, 0);
|
|
863
|
-
return [
|
|
864
|
-
item("今日主题", index.todayTopicCount),
|
|
865
|
-
item("今日回复", index.todayCount),
|
|
866
|
-
item("在线", index.onlineUserCount),
|
|
867
|
-
item("用户", index.userCount),
|
|
868
|
-
item("未读", unreadTotal)
|
|
869
|
-
];
|
|
870
|
-
}
|
|
871
|
-
async function mapLimit(values, limit, mapper) {
|
|
872
|
-
const results = [];
|
|
873
|
-
let nextIndex = 0;
|
|
874
|
-
const workers = Array.from({ length: Math.min(limit, values.length) }, async () => {
|
|
875
|
-
while (nextIndex < values.length) {
|
|
876
|
-
const index = nextIndex;
|
|
877
|
-
nextIndex += 1;
|
|
878
|
-
results[index] = await mapper(values[index]);
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
await Promise.all(workers);
|
|
882
|
-
return results;
|
|
883
|
-
}
|
|
884
|
-
function flattenBoards(sections) {
|
|
885
|
-
const boards = [];
|
|
886
|
-
for (const section of sections) {
|
|
887
|
-
const sectionObject = asObject(section);
|
|
888
|
-
const sectionName = String(sectionObject.name ?? sectionObject.title ?? "分区");
|
|
889
|
-
const candidates = [sectionObject.boards, sectionObject.children, sectionObject.boardList];
|
|
890
|
-
for (const candidate of candidates) {
|
|
891
|
-
if (!Array.isArray(candidate)) {
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
for (const board of candidate) {
|
|
895
|
-
const boardObject = asObject(board);
|
|
896
|
-
boards.push({
|
|
897
|
-
title: String(boardObject.name ?? boardObject.title ?? `#${boardObject.id ?? ""}`),
|
|
898
|
-
meta: `${sectionName}${boardObject.id !== undefined ? ` · #${boardObject.id}` : ""}`,
|
|
899
|
-
detail: typeof boardObject.description === "string" ? boardObject.description : undefined,
|
|
900
|
-
boardId: typeof boardObject.id === "number" ? boardObject.id : undefined
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
return boards;
|
|
906
|
-
}
|
|
907
|
-
function asObject(value) {
|
|
908
|
-
return typeof value === "object" && value !== null ? value : {};
|
|
909
|
-
}
|
|
910
|
-
function asArray(value) {
|
|
911
|
-
return Array.isArray(value) ? value : [];
|
|
912
|
-
}
|
|
913
|
-
function asNumber(value) {
|
|
914
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
915
|
-
}
|
|
916
|
-
function normalizeInline(value) {
|
|
917
|
-
return value.replace(/\s+/g, " ").trim();
|
|
918
|
-
}
|
|
919
|
-
function normalizePreview(value) {
|
|
920
|
-
return normalizeInline(value
|
|
921
|
-
.replace(/\[img\][\s\S]*?\[\/img\]/gi, " [图片] ")
|
|
922
|
-
.replace(/\[upload(?:=[^\]]*)?\][\s\S]*?\[\/upload\]/gi, " [附件] ")
|
|
923
|
-
.replace(/\[url=([^\]]+)\]([\s\S]*?)\[\/url\]/gi, (_match, _url, label) => ` ${label} `)
|
|
924
|
-
.replace(/\[url\][\s\S]*?\[\/url\]/gi, " [链接] ")
|
|
925
|
-
.replace(/<img\b[^>]*>/gi, " [图片] ")
|
|
926
|
-
.replace(/<br\s*\/?>/gi, " ")
|
|
927
|
-
.replace(/<\/?[^>]+>/g, " ")
|
|
928
|
-
.replace(/\[(?:\/)?(?:b|i|u|size|color|align|email|del|s|sub|sup|h\d?|quote|code)(?:=[^\]]*)?\]/gi, "")
|
|
929
|
-
.replace(/\[[a-z0-9]+(?:=[^\]]*)?\]/gi, " ")
|
|
930
|
-
.replace(/\[\/[a-z0-9]+\]/gi, " "));
|
|
931
|
-
}
|
|
932
|
-
function timestampOf(value) {
|
|
933
|
-
if (typeof value !== "string" && typeof value !== "number") {
|
|
934
|
-
return undefined;
|
|
935
|
-
}
|
|
936
|
-
const timestamp = new Date(value).getTime();
|
|
937
|
-
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
938
|
-
}
|
|
939
|
-
function normalizeInlineText(value) {
|
|
940
|
-
return value.replace(/\s+/g, " ").trim();
|
|
941
|
-
}
|
|
942
|
-
function isAbortError(error) {
|
|
943
|
-
return error instanceof Error && error.name === "AbortError";
|
|
944
|
-
}
|
|
1
|
+
export { getDefaultAccountName, normalizeLoginMessage, refreshAccounts } from "./data/accounts.js";
|
|
2
|
+
export { loadNextChatPage, loadNextFeedPage, loadNextUserTopicPage, openBoard, openChat, openUserProfile } from "./data/content.js";
|
|
3
|
+
export { createSearchState, prepareListView, restorePreviousView } from "./data/navigation-state.js";
|
|
4
|
+
export { executeSearch, loadNextSearchPage } from "./data/search.js";
|
|
5
|
+
export { jumpRelativeTopicFloor, jumpToTopicFloor, loadNextTopicPage, openTopic } from "./data/topic.js";
|
|
6
|
+
export { describeUserProfileStatus } from "./data/view-items.js";
|
|
7
|
+
export { loadView } from "./data/view-loader.js";
|
|
945
8
|
//# sourceMappingURL=app-data.js.map
|