@walavave/cc98-cli 0.2.0 → 0.2.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.
Files changed (247) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +11 -3
  3. package/assets/forum-images/ac-dark/01.png +0 -0
  4. package/assets/forum-images/ac-dark/02.png +0 -0
  5. package/assets/forum-images/ac-dark/03.png +0 -0
  6. package/assets/forum-images/ac-dark/04.png +0 -0
  7. package/assets/forum-images/ac-dark/05.png +0 -0
  8. package/assets/forum-images/ac-dark/06.png +0 -0
  9. package/assets/forum-images/ac-dark/07.png +0 -0
  10. package/assets/forum-images/ac-dark/08.png +0 -0
  11. package/assets/forum-images/ac-dark/09.png +0 -0
  12. package/assets/forum-images/ac-dark/10.png +0 -0
  13. package/assets/forum-images/ac-dark/1001.png +0 -0
  14. package/assets/forum-images/ac-dark/1002.png +0 -0
  15. package/assets/forum-images/ac-dark/1003.png +0 -0
  16. package/assets/forum-images/ac-dark/1004.png +0 -0
  17. package/assets/forum-images/ac-dark/1005.png +0 -0
  18. package/assets/forum-images/ac-dark/1006.png +0 -0
  19. package/assets/forum-images/ac-dark/1007.png +0 -0
  20. package/assets/forum-images/ac-dark/1008.png +0 -0
  21. package/assets/forum-images/ac-dark/1009.png +0 -0
  22. package/assets/forum-images/ac-dark/1010.png +0 -0
  23. package/assets/forum-images/ac-dark/1011.png +0 -0
  24. package/assets/forum-images/ac-dark/1012.png +0 -0
  25. package/assets/forum-images/ac-dark/1013.png +0 -0
  26. package/assets/forum-images/ac-dark/1014.png +0 -0
  27. package/assets/forum-images/ac-dark/1015.png +0 -0
  28. package/assets/forum-images/ac-dark/1016.png +0 -0
  29. package/assets/forum-images/ac-dark/1017.png +0 -0
  30. package/assets/forum-images/ac-dark/1018.png +0 -0
  31. package/assets/forum-images/ac-dark/1019.png +0 -0
  32. package/assets/forum-images/ac-dark/1020.png +0 -0
  33. package/assets/forum-images/ac-dark/1021.png +0 -0
  34. package/assets/forum-images/ac-dark/1022.png +0 -0
  35. package/assets/forum-images/ac-dark/1023.png +0 -0
  36. package/assets/forum-images/ac-dark/1024.png +0 -0
  37. package/assets/forum-images/ac-dark/1025.png +0 -0
  38. package/assets/forum-images/ac-dark/1026.png +0 -0
  39. package/assets/forum-images/ac-dark/1027.png +0 -0
  40. package/assets/forum-images/ac-dark/1028.png +0 -0
  41. package/assets/forum-images/ac-dark/1029.png +0 -0
  42. package/assets/forum-images/ac-dark/1030.png +0 -0
  43. package/assets/forum-images/ac-dark/1031.png +0 -0
  44. package/assets/forum-images/ac-dark/1032.png +0 -0
  45. package/assets/forum-images/ac-dark/1033.png +0 -0
  46. package/assets/forum-images/ac-dark/1034.png +0 -0
  47. package/assets/forum-images/ac-dark/1035.png +0 -0
  48. package/assets/forum-images/ac-dark/1036.png +0 -0
  49. package/assets/forum-images/ac-dark/1037.png +0 -0
  50. package/assets/forum-images/ac-dark/1038.png +0 -0
  51. package/assets/forum-images/ac-dark/1039.png +0 -0
  52. package/assets/forum-images/ac-dark/1040.png +0 -0
  53. package/assets/forum-images/ac-dark/11.png +0 -0
  54. package/assets/forum-images/ac-dark/12.png +0 -0
  55. package/assets/forum-images/ac-dark/13.png +0 -0
  56. package/assets/forum-images/ac-dark/14.png +0 -0
  57. package/assets/forum-images/ac-dark/15.png +0 -0
  58. package/assets/forum-images/ac-dark/16.png +0 -0
  59. package/assets/forum-images/ac-dark/17.png +0 -0
  60. package/assets/forum-images/ac-dark/18.png +0 -0
  61. package/assets/forum-images/ac-dark/19.png +0 -0
  62. package/assets/forum-images/ac-dark/20.png +0 -0
  63. package/assets/forum-images/ac-dark/2001.png +0 -0
  64. package/assets/forum-images/ac-dark/2002.png +0 -0
  65. package/assets/forum-images/ac-dark/2003.png +0 -0
  66. package/assets/forum-images/ac-dark/2004.png +0 -0
  67. package/assets/forum-images/ac-dark/2005.png +0 -0
  68. package/assets/forum-images/ac-dark/2006.png +0 -0
  69. package/assets/forum-images/ac-dark/2007.png +0 -0
  70. package/assets/forum-images/ac-dark/2008.png +0 -0
  71. package/assets/forum-images/ac-dark/2009.png +0 -0
  72. package/assets/forum-images/ac-dark/2010.png +0 -0
  73. package/assets/forum-images/ac-dark/2011.png +0 -0
  74. package/assets/forum-images/ac-dark/2012.png +0 -0
  75. package/assets/forum-images/ac-dark/2013.png +0 -0
  76. package/assets/forum-images/ac-dark/2014.png +0 -0
  77. package/assets/forum-images/ac-dark/2015.png +0 -0
  78. package/assets/forum-images/ac-dark/2016.png +0 -0
  79. package/assets/forum-images/ac-dark/2017.png +0 -0
  80. package/assets/forum-images/ac-dark/2018.png +0 -0
  81. package/assets/forum-images/ac-dark/2019.png +0 -0
  82. package/assets/forum-images/ac-dark/2020.png +0 -0
  83. package/assets/forum-images/ac-dark/2021.png +0 -0
  84. package/assets/forum-images/ac-dark/2022.png +0 -0
  85. package/assets/forum-images/ac-dark/2023.png +0 -0
  86. package/assets/forum-images/ac-dark/2024.png +0 -0
  87. package/assets/forum-images/ac-dark/2025.png +0 -0
  88. package/assets/forum-images/ac-dark/2026.png +0 -0
  89. package/assets/forum-images/ac-dark/2027.png +0 -0
  90. package/assets/forum-images/ac-dark/2028.png +0 -0
  91. package/assets/forum-images/ac-dark/2029.png +0 -0
  92. package/assets/forum-images/ac-dark/2030.png +0 -0
  93. package/assets/forum-images/ac-dark/2031.png +0 -0
  94. package/assets/forum-images/ac-dark/2032.png +0 -0
  95. package/assets/forum-images/ac-dark/2033.png +0 -0
  96. package/assets/forum-images/ac-dark/2034.png +0 -0
  97. package/assets/forum-images/ac-dark/2035.png +0 -0
  98. package/assets/forum-images/ac-dark/2036.png +0 -0
  99. package/assets/forum-images/ac-dark/2037.png +0 -0
  100. package/assets/forum-images/ac-dark/2038.png +0 -0
  101. package/assets/forum-images/ac-dark/2039.png +0 -0
  102. package/assets/forum-images/ac-dark/2040.png +0 -0
  103. package/assets/forum-images/ac-dark/2041.png +0 -0
  104. package/assets/forum-images/ac-dark/2042.png +0 -0
  105. package/assets/forum-images/ac-dark/2043.png +0 -0
  106. package/assets/forum-images/ac-dark/2044.png +0 -0
  107. package/assets/forum-images/ac-dark/2045.png +0 -0
  108. package/assets/forum-images/ac-dark/2046.png +0 -0
  109. package/assets/forum-images/ac-dark/2047.png +0 -0
  110. package/assets/forum-images/ac-dark/2048.png +0 -0
  111. package/assets/forum-images/ac-dark/2049.png +0 -0
  112. package/assets/forum-images/ac-dark/2050.png +0 -0
  113. package/assets/forum-images/ac-dark/2051.png +0 -0
  114. package/assets/forum-images/ac-dark/2052.png +0 -0
  115. package/assets/forum-images/ac-dark/2053.png +0 -0
  116. package/assets/forum-images/ac-dark/2054.png +0 -0
  117. package/assets/forum-images/ac-dark/2055.png +0 -0
  118. package/assets/forum-images/ac-dark/21.png +0 -0
  119. package/assets/forum-images/ac-dark/22.png +0 -0
  120. package/assets/forum-images/ac-dark/23.png +0 -0
  121. package/assets/forum-images/ac-dark/24.png +0 -0
  122. package/assets/forum-images/ac-dark/25.png +0 -0
  123. package/assets/forum-images/ac-dark/26.png +0 -0
  124. package/assets/forum-images/ac-dark/27.png +0 -0
  125. package/assets/forum-images/ac-dark/28.png +0 -0
  126. package/assets/forum-images/ac-dark/29.png +0 -0
  127. package/assets/forum-images/ac-dark/30.png +0 -0
  128. package/assets/forum-images/ac-dark/31.png +0 -0
  129. package/assets/forum-images/ac-dark/32.png +0 -0
  130. package/assets/forum-images/ac-dark/33.png +0 -0
  131. package/assets/forum-images/ac-dark/34.png +0 -0
  132. package/assets/forum-images/ac-dark/35.png +0 -0
  133. package/assets/forum-images/ac-dark/36.png +0 -0
  134. package/assets/forum-images/ac-dark/37.png +0 -0
  135. package/assets/forum-images/ac-dark/38.png +0 -0
  136. package/assets/forum-images/ac-dark/39.png +0 -0
  137. package/assets/forum-images/ac-dark/40.png +0 -0
  138. package/assets/forum-images/ac-dark/41.png +0 -0
  139. package/assets/forum-images/ac-dark/42.png +0 -0
  140. package/assets/forum-images/ac-dark/43.png +0 -0
  141. package/assets/forum-images/ac-dark/44.png +0 -0
  142. package/assets/forum-images/ac-dark/45.png +0 -0
  143. package/assets/forum-images/ac-dark/46.png +0 -0
  144. package/assets/forum-images/ac-dark/47.png +0 -0
  145. package/assets/forum-images/ac-dark/48.png +0 -0
  146. package/assets/forum-images/ac-dark/49.png +0 -0
  147. package/assets/forum-images/ac-dark/50.png +0 -0
  148. package/assets/forum-images/ac-dark/51.png +0 -0
  149. package/assets/forum-images/ac-dark/52.png +0 -0
  150. package/assets/forum-images/ac-dark/53.png +0 -0
  151. package/assets/forum-images/ac-dark/54.png +0 -0
  152. package/dist/api/client.d.ts +2 -1
  153. package/dist/api/client.d.ts.map +1 -1
  154. package/dist/api/client.js +19 -2
  155. package/dist/api/client.js.map +1 -1
  156. package/dist/api/endpoints.d.ts +1 -0
  157. package/dist/api/endpoints.d.ts.map +1 -1
  158. package/dist/api/endpoints.js +1 -0
  159. package/dist/api/endpoints.js.map +1 -1
  160. package/dist/config.d.ts +2 -0
  161. package/dist/config.d.ts.map +1 -1
  162. package/dist/config.js +25 -4
  163. package/dist/config.js.map +1 -1
  164. package/dist/tui/app-data.d.ts +4 -1
  165. package/dist/tui/app-data.d.ts.map +1 -1
  166. package/dist/tui/app-data.js +107 -0
  167. package/dist/tui/app-data.js.map +1 -1
  168. package/dist/tui/app-runtime/content.d.ts +9 -0
  169. package/dist/tui/app-runtime/content.d.ts.map +1 -0
  170. package/dist/tui/app-runtime/content.js +275 -0
  171. package/dist/tui/app-runtime/content.js.map +1 -0
  172. package/dist/tui/app-runtime/context.d.ts +24 -0
  173. package/dist/tui/app-runtime/context.d.ts.map +1 -0
  174. package/dist/tui/app-runtime/context.js +2 -0
  175. package/dist/tui/app-runtime/context.js.map +1 -0
  176. package/dist/tui/app-runtime/image-viewer.d.ts +5 -0
  177. package/dist/tui/app-runtime/image-viewer.d.ts.map +1 -0
  178. package/dist/tui/app-runtime/image-viewer.js +112 -0
  179. package/dist/tui/app-runtime/image-viewer.js.map +1 -0
  180. package/dist/tui/app-runtime/interactions.d.ts +6 -0
  181. package/dist/tui/app-runtime/interactions.d.ts.map +1 -0
  182. package/dist/tui/app-runtime/interactions.js +181 -0
  183. package/dist/tui/app-runtime/interactions.js.map +1 -0
  184. package/dist/tui/app-runtime/keyboard.d.ts +3 -0
  185. package/dist/tui/app-runtime/keyboard.d.ts.map +1 -0
  186. package/dist/tui/app-runtime/keyboard.js +284 -0
  187. package/dist/tui/app-runtime/keyboard.js.map +1 -0
  188. package/dist/tui/app-runtime/modals.d.ts +17 -0
  189. package/dist/tui/app-runtime/modals.d.ts.map +1 -0
  190. package/dist/tui/app-runtime/modals.js +343 -0
  191. package/dist/tui/app-runtime/modals.js.map +1 -0
  192. package/dist/tui/app-runtime/mouse.d.ts +5 -0
  193. package/dist/tui/app-runtime/mouse.d.ts.map +1 -0
  194. package/dist/tui/app-runtime/mouse.js +68 -0
  195. package/dist/tui/app-runtime/mouse.js.map +1 -0
  196. package/dist/tui/app-runtime/state.d.ts +6 -0
  197. package/dist/tui/app-runtime/state.d.ts.map +1 -0
  198. package/dist/tui/app-runtime/state.js +41 -0
  199. package/dist/tui/app-runtime/state.js.map +1 -0
  200. package/dist/tui/app-runtime/topic.d.ts +6 -0
  201. package/dist/tui/app-runtime/topic.d.ts.map +1 -0
  202. package/dist/tui/app-runtime/topic.js +184 -0
  203. package/dist/tui/app-runtime/topic.js.map +1 -0
  204. package/dist/tui/app-runtime.d.ts +2 -27
  205. package/dist/tui/app-runtime.d.ts.map +1 -1
  206. package/dist/tui/app-runtime.js +2 -960
  207. package/dist/tui/app-runtime.js.map +1 -1
  208. package/dist/tui/app.d.ts.map +1 -1
  209. package/dist/tui/app.js +5 -2
  210. package/dist/tui/app.js.map +1 -1
  211. package/dist/tui/cached-client.d.ts +2 -0
  212. package/dist/tui/cached-client.d.ts.map +1 -1
  213. package/dist/tui/cached-client.js +6 -0
  214. package/dist/tui/cached-client.js.map +1 -1
  215. package/dist/tui/emotion-catalog.d.ts +23 -0
  216. package/dist/tui/emotion-catalog.d.ts.map +1 -0
  217. package/dist/tui/emotion-catalog.js +150 -0
  218. package/dist/tui/emotion-catalog.js.map +1 -0
  219. package/dist/tui/emotion-preview.js +1 -1
  220. package/dist/tui/emotion-preview.js.map +1 -1
  221. package/dist/tui/image-preview.d.ts.map +1 -1
  222. package/dist/tui/image-preview.js +2 -1
  223. package/dist/tui/image-preview.js.map +1 -1
  224. package/dist/tui/keymap.d.ts +1 -1
  225. package/dist/tui/keymap.d.ts.map +1 -1
  226. package/dist/tui/keymap.js +5 -1
  227. package/dist/tui/keymap.js.map +1 -1
  228. package/dist/tui/renderer.d.ts.map +1 -1
  229. package/dist/tui/renderer.js +268 -4
  230. package/dist/tui/renderer.js.map +1 -1
  231. package/dist/tui/terminal.d.ts +3 -0
  232. package/dist/tui/terminal.d.ts.map +1 -1
  233. package/dist/tui/terminal.js +37 -0
  234. package/dist/tui/terminal.js.map +1 -1
  235. package/dist/tui/theme.d.ts +1 -0
  236. package/dist/tui/theme.d.ts.map +1 -1
  237. package/dist/tui/theme.js +1 -0
  238. package/dist/tui/theme.js.map +1 -1
  239. package/dist/tui/tui-model.d.ts +24 -2
  240. package/dist/tui/tui-model.d.ts.map +1 -1
  241. package/dist/tui/tui-model.js +12 -0
  242. package/dist/tui/tui-model.js.map +1 -1
  243. package/dist/tui/ubb-renderer.js +1 -1
  244. package/dist/tui/ubb-renderer.js.map +1 -1
  245. package/dist/version.d.ts +1 -1
  246. package/dist/version.js +1 -1
  247. package/package.json +1 -1
@@ -1,961 +1,3 @@
1
- import { checkForUpdate } from "../update.js";
2
- import { createLoginForm, isPrintableInput, updateLoginField } from "./account-modal.js";
3
- import { getDefaultAccountName, jumpRelativeTopicFloor, jumpToTopicFloor, loadNextChatPage, loadNextTopicPage, normalizeLoginMessage, openBoard, openChat, openTopic, refreshAccounts, restoreParentList } from "./app-data.js";
4
- import { isEmotionAssetPath } from "./emotion-preview.js";
5
- import { loadModalImagePreview, supportsImagePreview } from "./image-preview.js";
6
- import { fill, length, pad, rect, split } from "./layout.js";
7
- import { getSidebarWidth } from "./renderer.js";
8
- import { downloadUrlToDownloads } from "./downloads.js";
9
- import { currentTopicLine, currentTopicPost, getStatus, navItems, settingsItems } from "./tui-model.js";
10
- export function createMouseHandler(context, handleScroll, clampSidebarWidth, getDividerColumn) {
11
- let pendingScrollRender;
12
- const scheduleScrollRender = () => {
13
- if (pendingScrollRender) {
14
- clearTimeout(pendingScrollRender);
15
- }
16
- pendingScrollRender = setTimeout(() => {
17
- pendingScrollRender = undefined;
18
- context.render();
19
- }, 80);
20
- };
21
- return (event) => {
22
- const { state, render } = context;
23
- if (state.modal) {
24
- if (event.kind === "up") {
25
- state.draggingSidebarDivider = false;
26
- }
27
- return;
28
- }
29
- const size = context.getSize();
30
- const dividerColumn = getDividerColumn();
31
- const withinFrame = event.row >= 2 && event.row < size.rows - 1;
32
- if (event.kind === "down" && event.button === "wheel-up") {
33
- handleScroll(state, -3);
34
- scheduleScrollRender();
35
- return;
36
- }
37
- if (event.kind === "down" && event.button === "wheel-down") {
38
- const wasAtTopicEnd = isAtTopicEnd(state, context.config, size.rows);
39
- handleScroll(state, 3);
40
- scheduleScrollRender();
41
- if (wasAtTopicEnd && state.mode === "topic" && state.topic?.hasMore && !state.loadingMore) {
42
- void loadNextTopicPage(context.client, state, render, context.config, context.nextSignal(), true);
43
- }
44
- return;
45
- }
46
- if (event.kind === "down" && event.button === "left" && withinFrame && event.column === dividerColumn) {
47
- state.draggingSidebarDivider = true;
48
- return;
49
- }
50
- if (event.kind === "drag" && state.draggingSidebarDivider) {
51
- state.sidebarWidth = clampSidebarWidth(event.column - 1, size.columns);
52
- render();
53
- return;
54
- }
55
- if (event.kind === "up") {
56
- state.draggingSidebarDivider = false;
57
- if (event.button === "left") {
58
- if (handleSidebarClick(context, event, size.columns, size.rows)) {
59
- return;
60
- }
61
- if (handleContentClick(context, event, size.columns, size.rows)) {
62
- return;
63
- }
64
- void handleTopicClick(context, event, size.columns, size.rows);
65
- }
66
- }
67
- };
68
- }
69
- export function createKeyHandler(context) {
70
- return (key) => {
71
- const { keymap, state, close, render } = context;
72
- const keyAction = keymap.feed(key);
73
- if (key === "\u0003" || key === "q") {
74
- close();
75
- return;
76
- }
77
- if (key === "?") {
78
- state.modal = state.modal === "help" ? null : "help";
79
- render();
80
- return;
81
- }
82
- if (state.modal === "help") {
83
- if (key === "h" || key === "\x1b[D" || key === "\x1b" || key === "?" || key === "\r") {
84
- state.modal = null;
85
- render();
86
- }
87
- return;
88
- }
89
- if (state.modal === "account") {
90
- handleAccountModal(context, key);
91
- return;
92
- }
93
- if (state.modal === "login") {
94
- handleLoginModal(context, key);
95
- return;
96
- }
97
- if (state.modal === "confirm") {
98
- handleConfirmModal(context, key);
99
- return;
100
- }
101
- if (state.modal === "image") {
102
- handleImageModal(context, key);
103
- return;
104
- }
105
- if (state.mode === "topic") {
106
- handleTopicMode(context, key, keyAction);
107
- return;
108
- }
109
- if (state.mode === "settings") {
110
- handleSettingsMode(context, key);
111
- return;
112
- }
113
- if (state.focus === "nav") {
114
- handleNavFocus(context, key);
115
- return;
116
- }
117
- handleContentFocus(context, key);
118
- };
119
- }
120
- function showNotification(state, message, durationMs = 3200) {
121
- state.notification = {
122
- message,
123
- expiresAt: Date.now() + durationMs
124
- };
125
- }
126
- function handleAccountModal(context, key) {
127
- const { state, render, tokenStore, load } = context;
128
- if (key === "j" || key === "\x1b[B") {
129
- state.accountModal.selectedIndex = Math.min(state.accountModal.accounts.length, state.accountModal.selectedIndex + 1);
130
- render();
131
- return;
132
- }
133
- if (key === "k" || key === "\x1b[A") {
134
- state.accountModal.selectedIndex = Math.max(0, state.accountModal.selectedIndex - 1);
135
- render();
136
- return;
137
- }
138
- if (key === "h" || key === "\x1b[D" || key === "\x1b") {
139
- state.modal = null;
140
- state.status = getStatus(state);
141
- render();
142
- return;
143
- }
144
- if (key === "\r" || key === "l" || key === "\x1b[C") {
145
- if (state.accountModal.selectedIndex === state.accountModal.accounts.length) {
146
- state.loginForm = createLoginForm();
147
- state.modal = "login";
148
- render();
149
- return;
150
- }
151
- const selected = state.accountModal.accounts[state.accountModal.selectedIndex];
152
- if (!selected) {
153
- return;
154
- }
155
- state.status = `正在切换到 @${selected.account}...`;
156
- state.modal = null;
157
- render();
158
- void tokenStore.useAccount(selected.account).then(() => {
159
- state.account = selected.account;
160
- showNotification(state, `已切换到 @${selected.account}`);
161
- void load(true);
162
- }).catch((error) => {
163
- state.error = error instanceof Error ? error.message : String(error);
164
- state.status = "账号切换失败";
165
- render();
166
- });
167
- }
168
- }
169
- function handleLoginModal(context, key) {
170
- const { state, render, rawClient, tokenStore, load } = context;
171
- if (state.loginForm.submitting) {
172
- return;
173
- }
174
- if (key === "\t" || key === "j" || key === "\x1b[B") {
175
- state.loginForm.fieldIndex = (state.loginForm.fieldIndex + 1) % 3;
176
- render();
177
- return;
178
- }
179
- if (key === "k" || key === "\x1b[A") {
180
- state.loginForm.fieldIndex = (state.loginForm.fieldIndex + 2) % 3;
181
- render();
182
- return;
183
- }
184
- if (key === "h" || key === "\x1b[D" || key === "\x1b") {
185
- state.modal = "account";
186
- state.loginForm.error = undefined;
187
- state.status = getStatus(state);
188
- render();
189
- return;
190
- }
191
- if (key === "l" || key === "\x1b[C") {
192
- if (state.loginForm.fieldIndex < 2) {
193
- state.loginForm.fieldIndex += 1;
194
- render();
195
- }
196
- return;
197
- }
198
- if (key === "\x7f") {
199
- updateLoginField(state.loginForm, (value) => value.slice(0, -1));
200
- render();
201
- return;
202
- }
203
- if (key === "\r") {
204
- if (state.loginForm.fieldIndex < 2) {
205
- state.loginForm.fieldIndex += 1;
206
- render();
207
- return;
208
- }
209
- const username = state.loginForm.username.trim();
210
- const password = state.loginForm.password;
211
- if (!username || !password) {
212
- state.loginForm.error = "用户名和密码不能为空";
213
- render();
214
- return;
215
- }
216
- state.loginForm.submitting = true;
217
- state.loginForm.error = undefined;
218
- state.status = `正在登录 ${username}...`;
219
- render();
220
- void rawClient.loginWithPassword(username, password).then(async (token) => {
221
- const me = await rawClient.getMeWithAccessToken(token.accessToken);
222
- const resolvedAccount = getDefaultAccountName(me, username);
223
- await tokenStore.saveAccount(resolvedAccount, {
224
- accessToken: token.accessToken,
225
- refreshToken: token.refreshToken,
226
- userId: typeof me.id === "number" ? me.id : undefined,
227
- username,
228
- displayName: typeof me.name === "string" ? me.name : undefined
229
- });
230
- state.account = resolvedAccount;
231
- await refreshAccounts(state, tokenStore);
232
- state.loginForm = createLoginForm();
233
- state.modal = null;
234
- showNotification(state, `已登录为 ${typeof me.name === "string" ? me.name : username}`);
235
- await load(true);
236
- }).catch((error) => {
237
- state.loginForm.submitting = false;
238
- state.loginForm.error = normalizeLoginMessage(error);
239
- state.status = "登录失败";
240
- render();
241
- });
242
- return;
243
- }
244
- if (isPrintableInput(key)) {
245
- updateLoginField(state.loginForm, (value) => `${value}${key}`);
246
- render();
247
- }
248
- }
249
- function handleConfirmModal(context, key) {
250
- const { state, render, client, tokenStore, load } = context;
251
- if (!state.confirmDialog) {
252
- state.modal = null;
253
- render();
254
- return;
255
- }
256
- if (key === "j" || key === "\x1b[B" || key === "\t") {
257
- state.confirmDialog.selectedIndex = Math.min(1, state.confirmDialog.selectedIndex + 1);
258
- render();
259
- return;
260
- }
261
- if (key === "k" || key === "\x1b[A") {
262
- state.confirmDialog.selectedIndex = Math.max(0, state.confirmDialog.selectedIndex - 1);
263
- render();
264
- return;
265
- }
266
- if (key === "h" || key === "\x1b[D" || key === "\x1b") {
267
- state.modal = null;
268
- state.confirmDialog = undefined;
269
- state.status = getStatus(state);
270
- render();
271
- return;
272
- }
273
- if (key !== "\r") {
274
- return;
275
- }
276
- if (state.confirmDialog.selectedIndex === 1) {
277
- state.modal = null;
278
- state.confirmDialog = undefined;
279
- state.status = getStatus(state);
280
- render();
281
- return;
282
- }
283
- const action = state.confirmDialog.action;
284
- state.modal = null;
285
- if (action === "cache-cleanup") {
286
- state.status = "正在清理缓存...";
287
- render();
288
- void client.clearCache().then(() => {
289
- showNotification(state, "缓存已清理");
290
- void load(true);
291
- }).catch((error) => {
292
- state.error = error instanceof Error ? error.message : String(error);
293
- state.status = "缓存清理失败";
294
- render();
295
- }).finally(() => {
296
- state.confirmDialog = undefined;
297
- });
298
- return;
299
- }
300
- const account = state.account;
301
- state.status = account ? `正在退出 @${account}...` : "正在清除登录信息...";
302
- render();
303
- void (async () => {
304
- if (account) {
305
- await tokenStore.removeAccount(account);
306
- }
307
- else {
308
- await tokenStore.clear();
309
- }
310
- state.account = await tokenStore.getCurrentAccountName();
311
- await refreshAccounts(state, tokenStore);
312
- showNotification(state, "已退出登录");
313
- await load(true);
314
- })().catch((error) => {
315
- state.error = error instanceof Error ? error.message : String(error);
316
- state.status = "退出登录失败";
317
- render();
318
- }).finally(() => {
319
- state.confirmDialog = undefined;
320
- });
321
- }
322
- function handleTopicMode(context, key, keyAction) {
323
- const { state, render, client, config, nextSignal, abortCurrent } = context;
324
- if (key === ":" && state.topic && !state.topic.floorInput) {
325
- state.topic.floorInput = ":";
326
- state.status = "跳转到楼层:输入数字后 Enter 确认 Esc 取消";
327
- render();
328
- return;
329
- }
330
- if (/^\d$/.test(key) && state.topic?.floorInput.startsWith(":")) {
331
- if (state.topic.floorInput === ":" && key === "0") {
332
- return;
333
- }
334
- state.topic.floorInput = `${state.topic.floorInput}${key}`.slice(0, 7);
335
- state.status = `跳转到 ${state.topic.floorInput.slice(1)} 楼:Enter 确认 Esc 取消`;
336
- render();
337
- return;
338
- }
339
- if (key === "\x7f" && state.topic?.floorInput) {
340
- state.topic.floorInput = state.topic.floorInput.slice(0, -1);
341
- state.status = state.topic.floorInput
342
- ? state.topic.floorInput === ":"
343
- ? "跳转到楼层:输入数字后 Enter 确认 Esc 取消"
344
- : `跳转到 ${state.topic.floorInput.slice(1)} 楼:Enter 确认 Esc 取消`
345
- : getStatus(state);
346
- render();
347
- return;
348
- }
349
- if (key === "\r" && state.topic?.floorInput) {
350
- const floor = Number(state.topic.floorInput.slice(1));
351
- state.topic.floorInput = "";
352
- if (Number.isInteger(floor) && floor > 0) {
353
- void jumpToTopicFloor(client, state, floor, render, config, nextSignal());
354
- }
355
- else {
356
- state.status = getStatus(state);
357
- render();
358
- }
359
- return;
360
- }
361
- if ((key === "]" || key === "】" || keyAction === "topic.next-reply") && state.topic) {
362
- jumpRelativeTopicFloor(state, 1);
363
- state.status = getStatus(state);
364
- render();
365
- return;
366
- }
367
- if ((key === "[" || key === "【" || keyAction === "topic.previous-reply") && state.topic) {
368
- jumpRelativeTopicFloor(state, -1);
369
- state.status = getStatus(state);
370
- render();
371
- return;
372
- }
373
- if (key === "a" || keyAction === "topic.like-post") {
374
- void reactToCurrentTopicPost(context, true);
375
- return;
376
- }
377
- if (key === "s" || keyAction === "topic.dislike-post") {
378
- void reactToCurrentTopicPost(context, false);
379
- return;
380
- }
381
- if (key === "h" || key === "\x1b[D") {
382
- abortCurrent();
383
- leaveTopicMode(state);
384
- render();
385
- return;
386
- }
387
- if (key === "\x1b" && state.topic?.floorInput) {
388
- state.topic.floorInput = "";
389
- state.status = getStatus(state);
390
- render();
391
- return;
392
- }
393
- if (key === "j" || key === "\x1b[B") {
394
- const maxScroll = Math.max(0, (state.topic?.lines.length ?? 0) - 1);
395
- const wasAtEnd = isAtTopicEnd(state, config, context.getSize().rows);
396
- state.scroll = Math.min(maxScroll, state.scroll + 1);
397
- state.status = getStatus(state);
398
- render();
399
- if (wasAtEnd && state.topic?.hasMore && !state.loadingMore) {
400
- void loadNextTopicPage(client, state, render, config, nextSignal(), true);
401
- }
402
- return;
403
- }
404
- if (key === "k" || key === "\x1b[A") {
405
- state.scroll = Math.max(0, state.scroll - 1);
406
- state.status = getStatus(state);
407
- render();
408
- return;
409
- }
410
- if (key === " ") {
411
- void openTopicImageViewer(context);
412
- return;
413
- }
414
- if (key === "n") {
415
- void loadNextTopicPage(client, state, render, config, nextSignal());
416
- return;
417
- }
418
- if (key === "\x1b[C") {
419
- void stepTopicImageViewer(context, 1);
420
- return;
421
- }
422
- if (key === "r") {
423
- if (state.topic) {
424
- void openTopic(client, state, state.topic.topicId, render, config, true, nextSignal());
425
- }
426
- return;
427
- }
428
- }
429
- function isAtTopicEnd(state, config, totalRows) {
430
- if (!state.topic) {
431
- return false;
432
- }
433
- const viewport = getTopicViewportHeight(config, totalRows);
434
- if (viewport <= 0) {
435
- return state.scroll >= Math.max(0, state.topic.lines.length - 1);
436
- }
437
- return state.scroll + viewport >= state.topic.lines.length;
438
- }
439
- function getTopicViewportHeight(config, totalRows) {
440
- const mainHeight = config.hideTopChrome
441
- ? Math.max(1, totalRows - 3)
442
- : Math.max(1, totalRows - 7);
443
- return Math.max(0, mainHeight - 4);
444
- }
445
- async function reactToCurrentTopicPost(context, isLike) {
446
- const { state, client, render } = context;
447
- const topic = state.topic;
448
- if (!topic || state.loadingMore) {
449
- return;
450
- }
451
- const post = currentTopicPost(topic, state.scroll);
452
- if (!post?.id) {
453
- state.status = "当前楼层不可赞踩";
454
- render();
455
- return;
456
- }
457
- state.status = isLike ? "正在点赞..." : "正在点踩...";
458
- render();
459
- try {
460
- await client.reactToPost(post.id, isLike);
461
- const latest = await client.getPostReactionState(post.id, true);
462
- if (typeof latest === "object" && latest !== null) {
463
- const reaction = latest;
464
- if (typeof reaction.likeCount === "number" && Number.isFinite(reaction.likeCount)) {
465
- post.likeCount = reaction.likeCount;
466
- }
467
- if (typeof reaction.dislikeCount === "number" && Number.isFinite(reaction.dislikeCount)) {
468
- post.dislikeCount = reaction.dislikeCount;
469
- }
470
- post.likeState = reaction.likeState === 1 || reaction.likeState === 2 ? reaction.likeState : 0;
471
- }
472
- updateTopicPostHeader(post, topic);
473
- showNotification(state, post.likeState === 1 ? `已赞 ${post.floor ?? "?"} 楼` :
474
- post.likeState === 2 ? `已踩 ${post.floor ?? "?"} 楼` :
475
- `已取消 ${post.floor ?? "?"} 楼的赞踩`);
476
- state.status = getStatus(state);
477
- }
478
- catch (error) {
479
- state.error = error instanceof Error ? error.message : String(error);
480
- state.status = isLike ? "点赞失败" : "点踩失败";
481
- }
482
- finally {
483
- render();
484
- }
485
- }
486
- function updateTopicPostHeader(post, topic) {
487
- const floor = post.floor !== undefined ? `#${post.floor}` : "#?";
488
- const reaction = ` · ${post.likeCount} 赞 · ${post.dislikeCount} 踩`;
489
- const header = `${floor} ${post.author}${post.time ? ` · ${post.time}` : ""}${reaction}`;
490
- const headerLine = post.lines.find((entry) => entry.kind === "header");
491
- if (!headerLine) {
492
- return;
493
- }
494
- headerLine.text = header;
495
- if (headerLine.line >= 0 && headerLine.line < topic.lines.length) {
496
- topic.lines[headerLine.line] = header;
497
- }
498
- }
499
- function handleImageModal(context, key) {
500
- const { state, render } = context;
501
- if (!state.imageViewer) {
502
- state.modal = null;
503
- render();
504
- return;
505
- }
506
- if (key === " " || key === "\x1b" || key === "h" || key === "\r") {
507
- state.modal = null;
508
- state.status = getStatus(state);
509
- render();
510
- return;
511
- }
512
- if (key === "\x1b[C" || key === "l") {
513
- void stepTopicImageViewer(context, 1);
514
- return;
515
- }
516
- if (key === "\x1b[D" || key === "k") {
517
- void stepTopicImageViewer(context, -1);
518
- return;
519
- }
520
- }
521
- async function openTopicImageViewer(context) {
522
- const { state, render } = context;
523
- const topic = state.topic;
524
- if (!topic) {
525
- return;
526
- }
527
- if (!supportsImagePreview()) {
528
- state.status = "当前终端不支持图片大图预览";
529
- render();
530
- return;
531
- }
532
- const images = topic.posts
533
- .flatMap((post) => post.images)
534
- .filter((url) => !isEmotionAssetPath(url));
535
- if (images.length === 0) {
536
- state.status = "当前帖子没有可预览的大图";
537
- render();
538
- return;
539
- }
540
- const currentLine = currentTopicLine(topic, state.scroll);
541
- const currentPost = currentTopicPost(topic, state.scroll);
542
- const targetUrl = !isEmotionAssetPath(currentLine?.imageUrl ?? "")
543
- ? currentLine?.imageUrl
544
- : currentPost?.images.find((url) => !isEmotionAssetPath(url)) ?? images[0];
545
- const index = Math.max(0, images.findIndex((url) => url === targetUrl));
546
- state.imageViewer = {
547
- images,
548
- index,
549
- loading: true
550
- };
551
- state.modal = "image";
552
- render();
553
- await refreshTopicImageViewer(context, index);
554
- }
555
- async function stepTopicImageViewer(context, delta) {
556
- const viewer = context.state.imageViewer;
557
- if (!viewer || viewer.images.length === 0) {
558
- return;
559
- }
560
- const nextIndex = Math.min(viewer.images.length - 1, Math.max(0, viewer.index + delta));
561
- if (nextIndex === viewer.index && viewer.token) {
562
- return;
563
- }
564
- viewer.index = nextIndex;
565
- viewer.loading = true;
566
- viewer.error = undefined;
567
- viewer.token = undefined;
568
- viewer.renderSize = undefined;
569
- context.state.modal = "image";
570
- context.render();
571
- await refreshTopicImageViewer(context, nextIndex);
572
- }
573
- async function refreshTopicImageViewer(context, index) {
574
- const { state, render } = context;
575
- const viewer = state.imageViewer;
576
- if (!viewer) {
577
- return;
578
- }
579
- const terminalSize = process.stdout.isTTY
580
- ? { columns: process.stdout.columns || 80, rows: process.stdout.rows || 24 }
581
- : { columns: 80, rows: 24 };
582
- const modalWidth = Math.max(24, Math.min(terminalSize.columns - 4, Math.floor(terminalSize.columns * 0.92)));
583
- const modalHeight = Math.max(10, Math.min(terminalSize.rows - 2, Math.floor(terminalSize.rows * 0.9)));
584
- const maxColumns = Math.max(1, modalWidth - 2);
585
- const maxRows = Math.max(1, modalHeight - 2);
586
- const url = viewer.images[index];
587
- try {
588
- const loadedImage = await loadModalImagePreview(url ?? "", maxColumns, maxRows);
589
- if (!state.imageViewer || state.imageViewer.index !== index) {
590
- return;
591
- }
592
- state.imageViewer.loading = false;
593
- state.imageViewer.token = loadedImage?.token;
594
- state.imageViewer.renderSize = loadedImage?.size;
595
- state.imageViewer.error = loadedImage ? undefined : "当前终端无法显示这张图片";
596
- }
597
- catch (error) {
598
- if (!state.imageViewer || state.imageViewer.index !== index) {
599
- return;
600
- }
601
- state.imageViewer.loading = false;
602
- state.imageViewer.token = undefined;
603
- state.imageViewer.renderSize = undefined;
604
- state.imageViewer.error = error instanceof Error ? error.message : "图片加载失败";
605
- }
606
- render();
607
- }
608
- function leaveTopicMode(state) {
609
- state.mode = "list";
610
- state.focus = "content";
611
- state.viewTitle = state.currentBoard?.title ?? state.currentChat?.title ?? navItems[state.navIndex]?.label ?? state.viewTitle;
612
- state.status = getStatus(state);
613
- }
614
- function enterContentMode(state, resetIndex = false) {
615
- if (navItems[state.navIndex]?.id === "settings") {
616
- state.mode = "settings";
617
- }
618
- state.focus = "content";
619
- if (resetIndex) {
620
- state.itemIndex = 0;
621
- }
622
- state.status = getStatus(state);
623
- }
624
- function leaveContentMode(state) {
625
- if (state.parentList) {
626
- restoreParentList(state);
627
- return;
628
- }
629
- state.mode = "list";
630
- state.focus = "nav";
631
- state.status = getStatus(state);
632
- }
633
- function openSelectedItem(context) {
634
- const { state, render, client, config, nextSignal } = context;
635
- const selected = state.items[state.itemIndex];
636
- if (selected?.topicId !== undefined) {
637
- void openTopic(client, state, selected.topicId, render, config, false, nextSignal());
638
- return true;
639
- }
640
- if (selected?.boardId !== undefined) {
641
- void openBoard(client, state, selected.boardId, selected.title, render, false, nextSignal());
642
- return true;
643
- }
644
- if (selected?.chatUserId !== undefined) {
645
- void openChat(client, state, selected.chatUserId, selected.title, render, false, nextSignal());
646
- return true;
647
- }
648
- return false;
649
- }
650
- async function handleTopicClick(context, event, columns, rows) {
651
- const { state, render } = context;
652
- if (state.mode !== "topic" || !state.topic || state.loading || state.error) {
653
- return;
654
- }
655
- const mainArea = getMainAreaRect(columns, rows, context.config, state.sidebarWidth);
656
- if (!withinRect(event.column, event.row, mainArea)) {
657
- return;
658
- }
659
- const bodyRow = event.row - (mainArea.y + 1);
660
- const bodyLineIndex = bodyRow - 3;
661
- if (bodyLineIndex < 0) {
662
- return;
663
- }
664
- const absoluteLine = state.scroll + bodyLineIndex;
665
- const lineEntry = state.topic.posts
666
- .flatMap((post) => post.lines)
667
- .find((entry) => entry.line === absoluteLine);
668
- const url = lineEntry?.linkUrl ?? lineEntry?.imageUrl;
669
- if (!url) {
670
- return;
671
- }
672
- state.status = `正在下载 ${shortUrl(url)}...`;
673
- render();
674
- try {
675
- const savedPath = await downloadUrlToDownloads(url);
676
- showNotification(state, `已下载到 ${savedPath}`);
677
- }
678
- catch (error) {
679
- state.status = error instanceof Error ? error.message : "下载失败";
680
- }
681
- finally {
682
- render();
683
- }
684
- }
685
- function handleSidebarClick(context, event, columns, rows) {
686
- const { state, render, load, abortCurrent } = context;
687
- if (state.mode === "topic") {
688
- return false;
689
- }
690
- const sidebarArea = getSidebarAreaRect(columns, rows, context.config, state.sidebarWidth);
691
- if (sidebarArea.width <= 0 || !withinRect(event.column, event.row, sidebarArea)) {
692
- return false;
693
- }
694
- const rowIndex = event.row - (sidebarArea.y + 1);
695
- if (rowIndex < 0 || rowIndex >= navItems.length) {
696
- return true;
697
- }
698
- const nextNavIndex = Math.max(0, Math.min(navItems.length - 1, rowIndex));
699
- if (state.navIndex === nextNavIndex && state.focus === "nav" && state.mode !== "settings") {
700
- return true;
701
- }
702
- abortCurrent();
703
- state.navIndex = nextNavIndex;
704
- state.focus = "nav";
705
- if (state.mode === "settings") {
706
- state.mode = "list";
707
- }
708
- state.status = getStatus(state);
709
- render();
710
- void load();
711
- return true;
712
- }
713
- function handleContentClick(context, event, columns, rows) {
714
- const { state, render } = context;
715
- if (state.mode === "topic" || state.loading || state.error) {
716
- return false;
717
- }
718
- const mainArea = getMainAreaRect(columns, rows, context.config, state.sidebarWidth);
719
- if (!withinRect(event.column, event.row, mainArea)) {
720
- return false;
721
- }
722
- if (navItems[state.navIndex]?.id === "settings" && state.mode !== "settings") {
723
- state.mode = "settings";
724
- }
725
- if (state.mode === "settings") {
726
- const rowIndex = event.row - (mainArea.y + 1) - 2;
727
- if (rowIndex < 0 || rowIndex >= settingsItems.length) {
728
- return true;
729
- }
730
- state.itemIndex = rowIndex;
731
- enterContentMode(state);
732
- render();
733
- return true;
734
- }
735
- const rowIndex = event.row - (mainArea.y + 1) - 2;
736
- if (rowIndex < 0) {
737
- return true;
738
- }
739
- const itemHeight = 2;
740
- if (rowIndex % itemHeight !== 0 && rowIndex % itemHeight !== 1) {
741
- return true;
742
- }
743
- const visibleCapacity = Math.max(1, Math.floor(Math.max(1, mainArea.height - 2) / itemHeight));
744
- const scroll = getContentListScroll(state, visibleCapacity);
745
- const itemOffset = Math.floor(rowIndex / itemHeight);
746
- const itemIndex = scroll + itemOffset;
747
- if (itemIndex < 0 || itemIndex >= state.items.length) {
748
- return true;
749
- }
750
- state.itemIndex = itemIndex;
751
- enterContentMode(state);
752
- render();
753
- return true;
754
- }
755
- function getMainAreaRect(columns, rows, config, sidebarWidthOverride) {
756
- const { mainArea } = getBodyColumnRects(columns, rows, config, sidebarWidthOverride);
757
- return mainArea;
758
- }
759
- function getSidebarAreaRect(columns, rows, config, sidebarWidthOverride) {
760
- const { sidebarArea } = getBodyColumnRects(columns, rows, config, sidebarWidthOverride);
761
- return sidebarArea;
762
- }
763
- function getBodyColumnRects(columns, rows, config, sidebarWidthOverride) {
764
- const width = Math.max(1, columns);
765
- const height = Math.max(1, rows);
766
- const outer = rect(width, Math.max(0, height - 1));
767
- const root = pad(outer, 1);
768
- const verticalLayout = config.hideTopChrome
769
- ? [fill()]
770
- : [length(1), length(1), length(1), length(1), fill()];
771
- const areas = split(root, "vertical", verticalLayout);
772
- const bodyArea = config.hideTopChrome ? areas[0] : areas[4];
773
- const sidebarWidth = getSidebarWidth(width, sidebarWidthOverride);
774
- const bodyColumns = split(bodyArea, "horizontal", [
775
- length(sidebarWidth),
776
- length(sidebarWidth > 0 ? 1 : 0),
777
- fill()
778
- ]);
779
- return {
780
- sidebarArea: bodyColumns[0],
781
- mainArea: bodyColumns[2]
782
- };
783
- }
784
- function getContentListScroll(state, visibleCapacity) {
785
- const maxScroll = Math.max(0, state.items.length - visibleCapacity);
786
- const current = Math.min(Math.max(0, state.scroll), maxScroll);
787
- if (state.itemIndex < current) {
788
- return state.itemIndex;
789
- }
790
- if (state.itemIndex >= current + visibleCapacity) {
791
- return Math.min(maxScroll, state.itemIndex - visibleCapacity + 1);
792
- }
793
- return current;
794
- }
795
- function withinRect(column, row, area) {
796
- const x = column - 1;
797
- const y = row - 1;
798
- return x >= area.x && x < area.x + area.width && y >= area.y && y < area.y + area.height;
799
- }
800
- function shortUrl(value) {
801
- try {
802
- const url = new URL(value);
803
- const fileName = url.pathname.split("/").filter(Boolean).at(-1) ?? url.host;
804
- return `${url.host}/${fileName}`;
805
- }
806
- catch {
807
- return value;
808
- }
809
- }
810
- function handleSettingsMode(context, key) {
811
- const { state, render, tokenStore, load } = context;
812
- if (key === "j" || key === "\x1b[B") {
813
- state.itemIndex = Math.min(settingsItems.length - 1, state.itemIndex + 1);
814
- render();
815
- return;
816
- }
817
- if (key === "k" || key === "\x1b[A") {
818
- state.itemIndex = Math.max(0, state.itemIndex - 1);
819
- render();
820
- return;
821
- }
822
- if (key === "h" || key === "\x1b[D") {
823
- leaveContentMode(state);
824
- render();
825
- return;
826
- }
827
- if (key !== "l" && key !== "\x1b[C" && key !== "\r") {
828
- return;
829
- }
830
- const selected = settingsItems[state.itemIndex];
831
- if (selected?.meta === "help") {
832
- state.modal = "help";
833
- render();
834
- return;
835
- }
836
- if (selected?.meta === "cache") {
837
- state.confirmDialog = {
838
- title: "缓存清理",
839
- detail: "清理过期文件缓存,并清空当前会话中的内存缓存?",
840
- confirmLabel: "确认清理",
841
- cancelLabel: "取消",
842
- selectedIndex: 1,
843
- action: "cache-cleanup"
844
- };
845
- state.modal = "confirm";
846
- render();
847
- return;
848
- }
849
- if (selected?.meta === "logout") {
850
- state.confirmDialog = {
851
- title: "退出登录",
852
- detail: state.account
853
- ? `删除本地账号 @${state.account} 的登录信息?`
854
- : "清除本地登录信息?",
855
- confirmLabel: "确认退出",
856
- cancelLabel: "取消",
857
- selectedIndex: 1,
858
- action: "logout"
859
- };
860
- state.modal = "confirm";
861
- render();
862
- return;
863
- }
864
- if (selected?.meta === "account") {
865
- void refreshAccounts(state, tokenStore).then(() => {
866
- if (state.accountModal.accounts.length === 0) {
867
- state.loginForm = createLoginForm();
868
- state.modal = "login";
869
- }
870
- else {
871
- state.modal = "account";
872
- }
873
- render();
874
- }).catch((error) => {
875
- state.error = error instanceof Error ? error.message : String(error);
876
- state.status = "读取账号列表失败";
877
- render();
878
- });
879
- return;
880
- }
881
- if (selected?.meta === "update") {
882
- state.status = "正在检查 GitHub Release...";
883
- render();
884
- void checkForUpdate().then((result) => {
885
- showNotification(state, result.message);
886
- render();
887
- }).catch((error) => {
888
- state.status = error instanceof Error ? error.message : "检查更新失败";
889
- render();
890
- });
891
- return;
892
- }
893
- void load(true);
894
- }
895
- function handleNavFocus(context, key) {
896
- const { state, render, load } = context;
897
- if (key === "j" || key === "\x1b[B") {
898
- state.navIndex = Math.min(navItems.length - 1, state.navIndex + 1);
899
- void load();
900
- return;
901
- }
902
- if (key === "k" || key === "\x1b[A") {
903
- state.navIndex = Math.max(0, state.navIndex - 1);
904
- void load();
905
- return;
906
- }
907
- if (key === "l" || key === "\x1b[C" || key === "\r") {
908
- if (!state.loading && state.items.length > 0) {
909
- enterContentMode(state, key === "\r");
910
- render();
911
- }
912
- return;
913
- }
914
- if (key === "r") {
915
- void load(true);
916
- }
917
- }
918
- function handleContentFocus(context, key) {
919
- const { state, render, client, config, nextSignal, abortCurrent, load } = context;
920
- if (key === "j" || key === "\x1b[B") {
921
- state.itemIndex = Math.min(Math.max(0, state.items.length - 1), state.itemIndex + 1);
922
- render();
923
- return;
924
- }
925
- if (key === "k" || key === "\x1b[A") {
926
- state.itemIndex = Math.max(0, state.itemIndex - 1);
927
- render();
928
- return;
929
- }
930
- if (key === "h" || key === "\x1b[D" || key === "\x1b") {
931
- abortCurrent();
932
- leaveContentMode(state);
933
- render();
934
- return;
935
- }
936
- if (key === "l" || key === "\x1b[C" || key === "\r") {
937
- if (openSelectedItem(context)) {
938
- return;
939
- }
940
- state.status = "当前条目不可进入";
941
- render();
942
- return;
943
- }
944
- if ((key === "n" || key === " ") && state.currentChat) {
945
- void loadNextChatPage(client, state, render, nextSignal());
946
- return;
947
- }
948
- if (key === "r") {
949
- if (state.currentBoard) {
950
- void openBoard(client, state, state.currentBoard.boardId, state.currentBoard.title, render, true, nextSignal(), false);
951
- return;
952
- }
953
- if (state.currentChat) {
954
- void openChat(client, state, state.currentChat.userId, state.currentChat.title, render, true, nextSignal(), false);
955
- return;
956
- }
957
- void load(true);
958
- return;
959
- }
960
- }
1
+ export { createKeyHandler } from "./app-runtime/keyboard.js";
2
+ export { createMouseHandler } from "./app-runtime/mouse.js";
961
3
  //# sourceMappingURL=app-runtime.js.map