koishi-plugin-chat-patch 3.0.2 → 5.0.1

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 (237) hide show
  1. package/client/vue/index.vue +60 -634
  2. package/client/web/dist/assets/AppIcon-DIku6-h0.png +0 -0
  3. package/client/web/dist/assets/AppIconDark-BYymN71t.png +0 -0
  4. package/client/web/dist/assets/CapacitorAppIcon-CSZwNhvi.png +0 -0
  5. package/client/web/dist/assets/CapacitorAppIconDark-BMvMOzsg.png +0 -0
  6. package/client/web/dist/assets/Chat-CTdyeaX2.css +1 -0
  7. package/client/web/dist/assets/Chat-DkZvD7d8.js +28 -0
  8. package/client/web/dist/assets/MealHungryPan-Beos3lRI.js +1 -0
  9. package/client/web/dist/assets/MsgBody-aZ4_aija.js +1 -0
  10. package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CPZjRLp6.js +67 -0
  11. package/client/web/dist/assets/NapcatAppIcon-C7VOAkW9.png +0 -0
  12. package/client/web/dist/assets/Vue-lEhHouxB.png +0 -0
  13. package/client/web/dist/assets/append_darwin-CqBNU62J.css +1 -0
  14. package/client/web/dist/assets/append_full_vibrancy-j0D2LM9m.css +1 -0
  15. package/client/web/dist/assets/append_linux-uhxeBqeC.css +1 -0
  16. package/client/web/dist/assets/append_linux_vibrancy-COiZq8MS.css +1 -0
  17. package/client/web/dist/assets/append_napcat-DV8CAyAd.css +1 -0
  18. package/client/web/dist/assets/append_new-407Lo5_V.css +1 -0
  19. package/client/web/dist/assets/append_vibrancy-6FHctkOY.css +1 -0
  20. package/client/web/dist/assets/append_win32-srmD-78y.css +1 -0
  21. package/client/web/dist/assets/cakes-CxTVKdnK.svg +189 -0
  22. package/client/web/dist/assets/core-DhEqZVGG.js +1 -0
  23. package/client/web/dist/assets/event-Cadp6m6J.js +1 -0
  24. package/client/web/dist/assets/index-CPTXiemn.js +4045 -0
  25. package/client/web/dist/assets/index-Cg_esKB0.js +1 -0
  26. package/client/web/dist/assets/index-Cln012sP.css +1 -0
  27. package/client/web/dist/assets//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223-CJKIKBn3.ttf +0 -0
  28. package/client/web/dist/assets//346/234/237/345/276/205-C90_OY60.png +0 -0
  29. package/client/web/dist/bcui/css/color-dark.css +35 -0
  30. package/client/web/dist/bcui/css/color-light.css +35 -0
  31. package/client/web/dist/bcui/css/style.css +468 -0
  32. package/client/web/dist/bcui/js/main.js +193 -0
  33. package/client/web/dist/css/append-dark.css +13 -0
  34. package/client/web/dist/css/append-light.css +13 -0
  35. package/client/web/dist/css/append-napcat-dark.css +14 -0
  36. package/client/web/dist/css/append-napcat-light.css +14 -0
  37. package/client/web/dist/favicon.ico +0 -0
  38. package/client/web/dist/img/icons/icon-for-fuck-apple.png +0 -0
  39. package/client/web/dist/img/icons/icon-maskable.png +0 -0
  40. package/client/web/dist/img/icons/icon.png +0 -0
  41. package/client/web/dist/img/icons/icon.svg +10 -0
  42. package/client/web/dist/index.html +22 -0
  43. package/client/web/dist/robots.txt +2 -0
  44. package/client/web/dist/sw.js +58 -0
  45. package/client/web/env.d.ts +56 -0
  46. package/client/web/index.html +21 -0
  47. package/client/web/package-lock.json +3419 -0
  48. package/client/web/package.json +62 -0
  49. package/client/web/public/bcui/css/color-dark.css +35 -0
  50. package/client/web/public/bcui/css/color-light.css +35 -0
  51. package/client/web/public/bcui/css/style.css +468 -0
  52. package/client/web/public/bcui/js/main.js +193 -0
  53. package/client/web/public/css/append-dark.css +13 -0
  54. package/client/web/public/css/append-light.css +13 -0
  55. package/client/web/public/css/append-napcat-dark.css +14 -0
  56. package/client/web/public/css/append-napcat-light.css +14 -0
  57. package/client/web/public/favicon.ico +0 -0
  58. package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
  59. package/client/web/public/img/icons/icon-maskable.png +0 -0
  60. package/client/web/public/img/icons/icon.png +0 -0
  61. package/client/web/public/img/icons/icon.svg +10 -0
  62. package/client/web/public/robots.txt +2 -0
  63. package/client/web/public/sw.js +58 -0
  64. package/client/web/scripts/copy-bcui.mjs +17 -0
  65. package/client/web/src/App.vue +1127 -0
  66. package/client/web/src/assets/css/append/append_darwin.css +68 -0
  67. package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
  68. package/client/web/src/assets/css/append/append_linux.css +125 -0
  69. package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
  70. package/client/web/src/assets/css/append/append_napcat.css +35 -0
  71. package/client/web/src/assets/css/append/append_new.css +843 -0
  72. package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
  73. package/client/web/src/assets/css/append/append_win32.css +109 -0
  74. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
  75. package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
  76. package/client/web/src/assets/css/chat.css +2104 -0
  77. package/client/web/src/assets/css/msg.css +941 -0
  78. package/client/web/src/assets/css/options.css +811 -0
  79. package/client/web/src/assets/css/sys_notice.css +62 -0
  80. package/client/web/src/assets/css/view.css +2105 -0
  81. package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
  82. package/client/web/src/assets/img/Vue.png +0 -0
  83. package/client/web/src/assets/img/cakes.svg +189 -0
  84. package/client/web/src/assets/img/icons/AppIcon.png +0 -0
  85. package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
  86. package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
  87. package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
  88. package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
  89. package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
  90. package/client/web/src/assets/img/icons/exit.png +0 -0
  91. package/client/web/src/assets/img/icons/pen.png +0 -0
  92. package/client/web/src/assets/img/stars.svg +89 -0
  93. package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
  94. package/client/web/src/assets/l10n/_About I10n.md +35 -0
  95. package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
  96. package/client/web/src/assets/l10n/zh-CN.po +1941 -0
  97. package/client/web/src/assets/logo.png +0 -0
  98. package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
  99. package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
  100. package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
  101. package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
  102. package/client/web/src/assets/qed.txt +1 -0
  103. package/client/web/src/components/AboutPan.vue +197 -0
  104. package/client/web/src/components/BulletinBody.vue +129 -0
  105. package/client/web/src/components/DepPan.vue +310 -0
  106. package/client/web/src/components/EmojiFace.vue +32 -0
  107. package/client/web/src/components/FacePan.vue +461 -0
  108. package/client/web/src/components/FileBody.vue +129 -0
  109. package/client/web/src/components/FileManager.vue +532 -0
  110. package/client/web/src/components/FriendBody.vue +54 -0
  111. package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
  112. package/client/web/src/components/History.vue +49 -0
  113. package/client/web/src/components/LazyLottie.vue +82 -0
  114. package/client/web/src/components/MergePan.vue +127 -0
  115. package/client/web/src/components/MsgBody.vue +1478 -0
  116. package/client/web/src/components/MusicPlayer.vue +549 -0
  117. package/client/web/src/components/NoticeBody.vue +168 -0
  118. package/client/web/src/components/QzonePermissionPan.vue +150 -0
  119. package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
  120. package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
  121. package/client/web/src/components/TinySessionBody.vue +51 -0
  122. package/client/web/src/components/UmamiInfoPan.vue +1549 -0
  123. package/client/web/src/components/UpdatePan.vue +263 -0
  124. package/client/web/src/components/ViewerCom.vue +1360 -0
  125. package/client/web/src/components/VoiceMsg.vue +626 -0
  126. package/client/web/src/components/WelPan.vue +894 -0
  127. package/client/web/src/components/msg-body/.gitkeep +0 -0
  128. package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
  129. package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
  130. package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
  131. package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
  132. package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
  133. package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
  134. package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
  135. package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
  136. package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
  137. package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
  138. package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
  139. package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
  140. package/client/web/src/components/notice-component/.gitkeep +0 -0
  141. package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
  142. package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
  143. package/client/web/src/components/tooltip/Tooltip.vue +50 -0
  144. package/client/web/src/components/tooltip/Tooltips.vue +14 -0
  145. package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
  146. package/client/web/src/function/base.ts +238 -0
  147. package/client/web/src/function/connect.ts +2042 -0
  148. package/client/web/src/function/debug.ts +10 -0
  149. package/client/web/src/function/elements/information.ts +141 -0
  150. package/client/web/src/function/elements/system.ts +101 -0
  151. package/client/web/src/function/elements/vueComp.ts +62 -0
  152. package/client/web/src/function/favicon.ts +76 -0
  153. package/client/web/src/function/input.ts +69 -0
  154. package/client/web/src/function/model/emoji.ts +127 -0
  155. package/client/web/src/function/model/img.ts +150 -0
  156. package/client/web/src/function/model/msg-body.ts +51 -0
  157. package/client/web/src/function/msg.ts +2674 -0
  158. package/client/web/src/function/notify.ts +225 -0
  159. package/client/web/src/function/option.ts +749 -0
  160. package/client/web/src/function/satori-model.ts +818 -0
  161. package/client/web/src/function/satori.ts +325 -0
  162. package/client/web/src/function/sender.ts +178 -0
  163. package/client/web/src/function/tooltip.ts +49 -0
  164. package/client/web/src/function/utils/appUtil.ts +2288 -0
  165. package/client/web/src/function/utils/avatarUtil.ts +31 -0
  166. package/client/web/src/function/utils/backgroundUtil.ts +110 -0
  167. package/client/web/src/function/utils/linkViewUtil.ts +101 -0
  168. package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
  169. package/client/web/src/function/utils/msgUtil.ts +1242 -0
  170. package/client/web/src/function/utils/pinyin.ts +141 -0
  171. package/client/web/src/function/utils/recordUtil.ts +187 -0
  172. package/client/web/src/function/utils/sessionUtil.ts +114 -0
  173. package/client/web/src/function/utils/systemUtil.ts +664 -0
  174. package/client/web/src/main.ts +91 -0
  175. package/client/web/src/pages/Chat.vue +3453 -0
  176. package/client/web/src/pages/Friends.vue +636 -0
  177. package/client/web/src/pages/Info.vue +717 -0
  178. package/client/web/src/pages/Messages.vue +875 -0
  179. package/client/web/src/pages/Options.vue +82 -0
  180. package/client/web/src/pages/Qzone.vue +1178 -0
  181. package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
  182. package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
  183. package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
  184. package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
  185. package/client/web/src/pages/options/OptAccount.vue +107 -0
  186. package/client/web/src/pages/options/OptAddon.vue +185 -0
  187. package/client/web/src/pages/options/OptDev.vue +704 -0
  188. package/client/web/src/pages/options/OptFunction.vue +403 -0
  189. package/client/web/src/pages/options/OptInfo.vue +165 -0
  190. package/client/web/src/pages/options/OptView.vue +846 -0
  191. package/client/web/src/runtime/backend.ts +310 -0
  192. package/client/web/src/state/auth.ts +14 -0
  193. package/client/web/src/state/chat.ts +36 -0
  194. package/client/web/src/state/connection.ts +20 -0
  195. package/client/web/src/state/contact.ts +37 -0
  196. package/client/web/src/state/qzone.ts +18 -0
  197. package/client/web/src/state/sessionHistory.ts +25 -0
  198. package/client/web/src/state/settings.ts +18 -0
  199. package/client/web/src/state/sticker.ts +10 -0
  200. package/client/web/src/state/ui.ts +56 -0
  201. package/client/web/tsconfig.json +27 -0
  202. package/client/web/tsconfig.tsbuildinfo +1 -0
  203. package/client/web/vite.config.ts +33 -0
  204. package/dist/index.js +1 -12
  205. package/dist/style.css +1 -1
  206. package/lib/index.js +2070 -2077
  207. package/package.json +15 -2
  208. package/readme.md +0 -22
  209. package/src/bootstrap.ts +71 -0
  210. package/src/cache.ts +319 -0
  211. package/src/config.ts +23 -35
  212. package/src/database.ts +650 -0
  213. package/src/index.ts +27 -59
  214. package/src/media.ts +302 -0
  215. package/src/recorder.ts +175 -0
  216. package/src/self-message.ts +456 -0
  217. package/src/server.d.ts +26 -0
  218. package/src/types.ts +108 -48
  219. package/src/web.ts +712 -0
  220. package/client/vue/chat-logic.ts +0 -683
  221. package/client/vue/composables/useChatActions.ts +0 -61
  222. package/client/vue/composables/useChatData.ts +0 -185
  223. package/client/vue/composables/useImageCache.ts +0 -281
  224. package/client/vue/composables/useVideoCache.ts +0 -125
  225. package/client/vue/types.ts +0 -72
  226. package/lib/api-handlers.d.ts +0 -25
  227. package/lib/config.d.ts +0 -14
  228. package/lib/file-manager.d.ts +0 -124
  229. package/lib/index.d.ts +0 -17
  230. package/lib/logger.d.ts +0 -9
  231. package/lib/message-handler.d.ts +0 -35
  232. package/lib/types.d.ts +0 -57
  233. package/lib/utils.d.ts +0 -18
  234. package/src/api-handlers.ts +0 -646
  235. package/src/file-manager.ts +0 -1436
  236. package/src/message-handler.ts +0 -507
  237. package/src/utils.ts +0 -189
@@ -0,0 +1,1127 @@
1
+ <template>
2
+ <div v-if="dev && !backend.isWeb()" id="dev-bar" :class="['dev-bar', 'onloading', {
3
+ 'win': backend.platform == 'win32'
4
+ }]">
5
+ Koishi Satori Chat Development Mode
6
+ {{ backend.platform ? ' / platform: ' + backend.platform : '' }}
7
+ {{ ' / client: ' + appClient.type }}
8
+ {{ ' / backend: ' + (authStore.jsonMap?.name ?? 'Not Connected') }}
9
+ {{ ' / fps: ' + fps.value }}
10
+ </div>
11
+ <div v-if="tags.musicLyric != ''" class="lyric-bar">
12
+ {{ tags.musicLyric }}
13
+ </div>
14
+ <div v-if="['linux', 'win32'].includes(backend.platform ?? '')"
15
+ :class="['top-bar', {
16
+ 'win': backend.platform == 'win32' && dev
17
+ }]"
18
+ name="appbar"
19
+ data-tauri-drag-region="true"
20
+ @mousedown="handleAppbarMouseDown">
21
+ <div class="bar-button" @click="barMainClick()" />
22
+ <div class="space" />
23
+ <div class="controller">
24
+ <div class="min" @click="controllWin('minimize')">
25
+ <font-awesome-icon :icon="['fas', 'minus']" />
26
+ </div>
27
+ <div class="close" @click="controllWin('close')">
28
+ <font-awesome-icon :icon="['fas', 'xmark']" />
29
+ </div>
30
+ </div>
31
+ </div>
32
+ <div v-if="backend.platform == 'darwin'" class="controller mac-controller"
33
+ data-tauri-drag-region="true" />
34
+ <div id="load-view" class="load-view">
35
+ <font-awesome-icon :icon="['fas', 'circle-notch']" />
36
+ </div>
37
+ <div id="base-app">
38
+ <div class="main-body onloading">
39
+ <ul id="side-bar" class="onloading" :style="{ 'padding-bottom': get('fs_adaptation') > 0 ? `${get('fs_adaptation')}px` : '' }">
40
+ <li id="bar-msg" :class="tags.page == 'Messages' ? 'active' : ''"
41
+ @click="changeTab('信息', 'Messages', true)">
42
+ <font-awesome-icon :icon="['fas', 'envelope']" />
43
+ <span>{{ $t('信息') }}</span>
44
+ </li>
45
+ <li id="bar-friends" :class="tags.page == 'Friends' ? 'active' : ''"
46
+ @click="changeTab('列表', 'Friends', true)">
47
+ <font-awesome-icon :icon="['fas', 'user']" />
48
+ <span>{{ $t('列表') }}</span>
49
+ </li>
50
+ <li v-if="useQzoneStore().qzoneFeedList.length > 0"
51
+ id="bar-qzone"
52
+ :class="tags.page == 'Qzone' ? 'active' : ''"
53
+ @click="changeTab('空间', 'Qzone', false)">
54
+ <font-awesome-icon :icon="['fas', 'star']" />
55
+ <span>{{ $t('空间') }}</span>
56
+ </li>
57
+ <div class="side-bar-space" />
58
+ <li v-if="tags.currentMusic"
59
+ :class="['music-entry', {
60
+ 'active': tags.showMusicPlayer,
61
+ 'music-entry-always-hide': backend.isMobile()
62
+ }]"
63
+ @click="toggleMusicPlayer(undefined)">
64
+ <img class="music-entry-cover"
65
+ :src="tags.currentMusic.cover"
66
+ :alt="tags.currentMusic.title">
67
+ <font-awesome-icon v-if="tags.musicPlaying" :icon="['fas', 'play']"
68
+ :class="['music-entry-status', { light: tags.currentMusic.coverLight }]" />
69
+ <font-awesome-icon v-else :icon="['fas', 'pause']"
70
+ :class="['music-entry-status', { light: tags.currentMusic.coverLight }]" />
71
+ </li>
72
+ <li v-if="showFileManagerEntry" :class="{ 'active': tags.showFileManager }"
73
+ @click="toggleFileManager(undefined)">
74
+ <font-awesome-icon :icon="['fas', 'arrow-down']" />
75
+ <span>{{ $t('传输') }}</span>
76
+ </li>
77
+ <li :class="tags.page == 'Options' ? 'active' : ''" @click="changeTab('设置', 'Options', false)">
78
+ <font-awesome-icon :icon="['fas', 'gear']" />
79
+ <span>{{ $t('设置') }}</span>
80
+ </li>
81
+ <li v-if="tags.currentMusic"
82
+ :class="['music-entry-small', {
83
+ 'active': tags.showMusicPlayer,
84
+ 'music-entry-small-always-show': backend.isMobile()
85
+ }]"
86
+ @click="toggleMusicPlayer(undefined)">
87
+ <img class="music-entry-cover"
88
+ :src="tags.currentMusic.cover"
89
+ :alt="tags.currentMusic.title">
90
+ <font-awesome-icon v-if="tags.musicPlaying" :icon="['fas', 'play']"
91
+ :class="['music-entry-status', { light: tags.currentMusic.coverLight }]" />
92
+ <font-awesome-icon v-else :icon="['fas', 'pause']"
93
+ :class="['music-entry-status', { light: tags.currentMusic.coverLight }]" />
94
+ </li>
95
+ </ul>
96
+ <div :style="{ 'height': get('fs_adaptation') > 0 ? `calc(100% - ${75 + Number(get('fs_adaptation'))}px)` : '' }">
97
+ <div v-if="tags.page == 'Messages'" id="messageTab">
98
+ <Messages :chat="chatStore.chatInfo" @user-click="changeChat" @load-history="loadHistory" />
99
+ </div>
100
+ <div v-if="tags.page == 'Friends'" id="friendTab">
101
+ <Friends :list="contactStore.userList" @load-history="loadHistory" @user-click="changeChat" />
102
+ </div>
103
+ <div v-if="tags.page == 'Qzone'" id="qzoneTab">
104
+ <Qzone />
105
+ </div>
106
+ <div class="opt-main-tab" style="opacity: 0">
107
+ <Options :show="tags.page == 'Options'" :class="tags.page == 'Options' ? 'active' : ''"
108
+ :config="settingsStore.sysConfig" />
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <component :is="uiStore.pageView.chatView" v-if="
113
+ loginInfo.status &&
114
+ chatStore.chatInfo &&
115
+ chatStore.chatInfo.show.id != 0"
116
+ v-show="tags.showChat"
117
+ ref="chat" :mumber-info="chatStore.chatInfo.info.now_member_info == undefined ?
118
+ {} : chatStore.chatInfo.info.now_member_info"
119
+ :merge-list="chatStore.mergeMessageList"
120
+ :list="chatStore.messageList" :chat="chatStore.chatInfo"
121
+ @user-click="changeChat" />
122
+ <TransitionGroup class="app-msg" name="appmsg" tag="div">
123
+ <div v-for="msg in appMsgs" :key="'appmsg-' + msg.id">
124
+ <div><font-awesome-icon :icon="['fas', msg.svg]" /></div>
125
+ <a>{{ msg.text }}</a>
126
+ <div v-if="!msg.autoClose" @click="popInfo.remove(msg.id)">
127
+ <font-awesome-icon :icon="['fas', 'xmark']" />
128
+ </div>
129
+ </div>
130
+ </TransitionGroup>
131
+ <Transition name="music-player-float">
132
+ <div v-show="tags.showMusicPlayer" class="global-music-player ss-card">
133
+ <MusicPlayer
134
+ @open-panel="toggleMusicPlayer"
135
+ @update-lyric="updateMusicLyric"
136
+ @update-status="updateMusicStatus" />
137
+ </div>
138
+ </Transition>
139
+ <Transition name="music-player-float">
140
+ <div v-show="tags.showFileManager" class="global-music-player ss-card">
141
+ <FileManager @open-panel="toggleFileManager" />
142
+ </div>
143
+ </Transition>
144
+ <Transition name="modal">
145
+ <div v-if="uiStore.popBoxList.length > 0" id="pop-box" class="pop-box">
146
+ <div :class="'pop-box-body ss-card' +
147
+ (uiStore.popBoxList[0].full ? ' full' : '') +
148
+ (get('option_view_no_window') == true ? '' : ' window')"
149
+ :style="{ 'margin-bottom': get('fs_adaptation') > 0 ? `${40 + Number(get('fs_adaptation'))}px` : '' }">
150
+ <header v-show="uiStore.popBoxList[0].title != undefined">
151
+ <div v-if="uiStore.popBoxList[0].svg != undefined">
152
+ <font-awesome-icon :icon="['fas', uiStore.popBoxList[0].svg]" />
153
+ </div>
154
+ <a>{{ uiStore.popBoxList[0].title }}</a>
155
+ <font-awesome-icon v-if="uiStore.popBoxList[0].allowClose != false"
156
+ :icon="['fas', 'xmark']" @click="removePopBox" />
157
+ </header>
158
+ <div v-if="uiStore.popBoxList[0].html" v-html="uiStore.popBoxList[0].html" />
159
+ <component :is="uiStore.popBoxList[0].template" v-else :data="uiStore.popBoxList[0].data"
160
+ v-bind="uiStore.popBoxList[0].templateValue" />
161
+ <div v-show="uiStore.popBoxList[0].button" class="button">
162
+ <button v-for="(button, index) in uiStore.popBoxList[0].button"
163
+ :key="'pop-box-btn' + index" :class="'ss-button' + (button.master == true ? ' master' : '')"
164
+ @click="button.fun">
165
+ {{ button.text }}
166
+ </button>
167
+ </div>
168
+ <div class="pop-box-more">
169
+ <div v-for="index in uiStore.popBoxList.length" :key="'pop-more-' + index" :data-id="index"
170
+ :class="index > uiStore.popBoxList.length - 1 ? 'hid' : '' "
171
+ :style="{ 'margin': `-${2 * (index - 1)}px ${(20 * index - 1 - 2 * (index - 1))}px 0 ${(20 * index - 1 - 2 * (index - 1))}px` }" />
172
+ </div>
173
+ </div>
174
+ <div @click="popQuickClose(uiStore.popBoxList[0].allowQuickClose != false && uiStore.popBoxList[0].allowClose != false)" />
175
+ </div>
176
+ </Transition>
177
+ <!-- 全局搜索栏 -->
178
+ <GlobalSessionSearchBar />
179
+ <NtViewer ref="ntViewer" />
180
+ <!-- 提示工具 -->
181
+ <Tooltips />
182
+ <div id="mobile-css" />
183
+ </div>
184
+ <div id="main-bg" class="main-bg onloading"
185
+ :style="{
186
+ 'background-image': toBackgroundImageStyle(settingsStore.sysConfig.chat_more_blur ? settingsStore.sysConfig.chat_background : ''),
187
+ 'background-position': settingsStore.sysConfig.chat_background_align ?? 'center',
188
+ 'background-size': settingsStore.sysConfig.chat_background_fit ?? 'cover',
189
+ 'opacity': 1 - Number(settingsStore.sysConfig.chat_background_blur) / 100 }" />
190
+ </template>
191
+
192
+ <script setup lang="ts">
193
+ import Option from './function/option'
194
+ import * as App from './function/utils/appUtil'
195
+ import anime from 'animejs'
196
+ import packageInfo from '../package.json'
197
+
198
+ import { computed, watch, onMounted, onUnmounted, shallowReactive, shallowRef, provide } from 'vue'
199
+ import { Connector, login as loginInfo, loadConnectionHistory, loadConnectionFromHistory, deleteConnectionHistory, decodeStoredToken, loadContactsFromCache, loadGroupMembersFromCache } from './function/connect'
200
+ import { Logger, popList, PopInfo, LogType } from './function/base'
201
+ import { isDebugMode } from './function/debug'
202
+ import { setLoginWaveTimer } from './function/msg'
203
+ import { BaseChatInfoElem } from './function/elements/information'
204
+ import { useConnectionStore } from './state/connection'
205
+ import { useUIStore } from './state/ui'
206
+ import { useSettingsStore } from './state/settings'
207
+ import { useChatStore } from './state/chat'
208
+ import { useContactStore } from './state/contact'
209
+ import { useAuthStore } from './state/auth'
210
+ import { Notify } from './function/notify'
211
+ import { updateBaseOnMsgList } from './function/utils/msgUtil'
212
+ import { getDeviceType, getForegroundToneFromImageUrl } from './function/utils/systemUtil'
213
+ import { uptime, i18n } from './main'
214
+ import { backend } from './runtime/backend'
215
+ import {
216
+ hydrateBackgroundImage,
217
+ migrateInlineBackgroundImage,
218
+ toBackgroundImageStyle,
219
+ } from './function/utils/backgroundUtil'
220
+
221
+ import Options from './pages/Options.vue'
222
+ import Friends from './pages/Friends.vue'
223
+ import Messages from './pages/Messages.vue'
224
+ import Qzone from './pages/Qzone.vue'
225
+ import MusicPlayer, { getCurrentMusic } from './components/MusicPlayer.vue'
226
+ import FileManager, { panelVisible, closePanel, getDownloadTasks, getUploadTasks } from './components/FileManager.vue'
227
+ import GlobalSessionSearchBar from './components/GlobalSessionSearchBar.vue'
228
+ import NtViewer from './components/ViewerCom.vue'
229
+ import Tooltips from './components/tooltip/Tooltips.vue'
230
+ import { useQzoneStore } from './state/qzone'
231
+
232
+ // 注册组件实例
233
+ const ntViewer = shallowRef<InstanceType<typeof NtViewer> | null>(null)
234
+ provide('viewer', { viewer: ntViewer })
235
+
236
+ defineOptions({ name: 'App' })
237
+
238
+ const $t = i18n.global.t
239
+
240
+ // 静态值
241
+ const repoName = import.meta.env.VITE_APP_REPO_NAME
242
+ const appClient = backend
243
+ const dev = import.meta.env.DEV
244
+ const napcat = import.meta.env.VITE_NAPCAT
245
+ const sse = import.meta.env.VITE_APP_SSE_MODE == 'true'
246
+ const get = Option.get
247
+ const popInfo = new PopInfo()
248
+ const appMsgs = popList
249
+ const loadHistory = App.loadHistory
250
+ const isNarrowLayout = shallowRef(window.innerWidth <= 500)
251
+
252
+ // 响应式状态
253
+ const connectionStore = useConnectionStore()
254
+ const uiStore = useUIStore()
255
+ const settingsStore = useSettingsStore()
256
+ const authStore = useAuthStore()
257
+ const contactStore = useContactStore()
258
+ const chatStore = useChatStore()
259
+ let musicSyncTimer = -1
260
+ const tags = shallowReactive({
261
+ page: 'Messages',
262
+ showChat: true,
263
+ isSavePwdClick: false,
264
+ savePassword: false,
265
+ quickLoginSelect: '',
266
+ selectedHistoryIndex: -1,
267
+ showHistoryDropdown: false,
268
+ showMusicPlayer: false,
269
+ showFileManager: false,
270
+ currentMusic: null as null | { title: string, cover: string, coverLight: boolean },
271
+ musicPlaying: false,
272
+ musicLyric: '',
273
+ })
274
+ const fps = shallowRef({
275
+ last: Date.now(),
276
+ ticks: 0,
277
+ value: 0,
278
+ })
279
+ const hasTransferTasks = computed(() => getDownloadTasks().length > 0 || getUploadTasks().length > 0)
280
+ const hasActiveTransferTasks = computed(() => {
281
+ return [...getDownloadTasks(), ...getUploadTasks()].some((task) => {
282
+ return ['pending', 'downloading', 'uploading'].includes(task.status)
283
+ })
284
+ })
285
+ const showFileManagerEntry = computed(() => {
286
+ if (isNarrowLayout.value) {
287
+ return hasActiveTransferTasks.value
288
+ }
289
+ return hasTransferTasks.value
290
+ })
291
+
292
+ //#region == 方法函数 ====================================================================
293
+
294
+ function updateLayoutState() {
295
+ isNarrowLayout.value = window.innerWidth <= 500
296
+ }
297
+
298
+ function toggleMusicPlayer(open: boolean | undefined) {
299
+ if(open != undefined ) {
300
+ tags.showMusicPlayer = open
301
+ } else {
302
+ tags.showMusicPlayer = !tags.showMusicPlayer
303
+ }
304
+ refreshCurrentMusic()
305
+ }
306
+ function toggleFileManager(open: boolean | undefined) {
307
+ if(open != undefined) {
308
+ tags.showFileManager = open
309
+ } else {
310
+ tags.showFileManager = !tags.showFileManager
311
+ }
312
+ // 同步状态到 FileManager 模块
313
+ if (tags.showFileManager) {
314
+ panelVisible.value = true
315
+ } else {
316
+ closePanel()
317
+ }
318
+ }
319
+ function updateMusicLyric(lyric: string) {
320
+ tags.musicLyric = lyric
321
+ }
322
+ function updateMusicStatus(isPlaying: boolean) {
323
+ tags.musicPlaying = isPlaying
324
+ }
325
+
326
+ async function refreshCurrentMusic() {
327
+ const nowMusic = getCurrentMusic()
328
+ if (!nowMusic) {
329
+ tags.currentMusic = null
330
+ return
331
+ }
332
+ let coverLight = tags.currentMusic?.coverLight ?? true
333
+ const cover = await backend.proxyImageUrl(nowMusic.cover)
334
+ if(nowMusic.cover != tags.currentMusic?.cover) {
335
+ coverLight = await getForegroundToneFromImageUrl(cover, 0.4) == 'light'
336
+ }
337
+ tags.currentMusic = {
338
+ title: nowMusic.title,
339
+ cover,
340
+ coverLight
341
+ }
342
+ }
343
+
344
+ function updateNapcatColor(token: string) {
345
+ const logger = new Logger()
346
+ // api/base/Theme 获取主题配置信息
347
+ fetch('/api/Base/Theme', {
348
+ method: 'GET',
349
+ headers: {
350
+ 'Content-Type': 'application/json',
351
+ 'Authorization': 'Bearer ' + token
352
+ }
353
+ }).then(async (response) => {
354
+ if(response.ok) {
355
+ const data = await response.json()
356
+ const media = window.matchMedia('(prefers-color-scheme: dark)')
357
+ if(media.matches) {
358
+ const colorHsl = data.data.dark['--heroui-primary']
359
+ document.documentElement.style.setProperty('--color-main', `hsl(${colorHsl} / .3)`)
360
+ document.documentElement.style.setProperty('--color-main-0', `hsl(${colorHsl} / .3)`)
361
+ } else {
362
+ const colorHsl = data.data.light['--heroui-primary']
363
+ document.documentElement.style.setProperty('--color-main', `hsl(${colorHsl} / .1)`)
364
+ document.documentElement.style.setProperty('--color-main-0', `hsl(${colorHsl} / .1)`)
365
+ }
366
+ } else {
367
+ logger.error(null, 'Napcat 主题获取失败,状态码:' + response.status)
368
+ }
369
+ }).catch((error) => {
370
+ logger.error(null, 'Napcat 主题请求失败:' + error)
371
+ })
372
+ }
373
+
374
+ /**
375
+ * electron 窗口操作
376
+ */
377
+ function controllWin(name: string) {
378
+ backend.call(undefined, 'win:' + name, false)
379
+ }
380
+
381
+ /**
382
+ * 处理 appbar 鼠标按下事件(Linux 平台窗口拖拽)
383
+ */
384
+ function handleAppbarMouseDown(event: MouseEvent) {
385
+ // 只在 Linux + Tauri 平台生效
386
+ if (backend.platform === 'linux' && backend.type === 'tauri') {
387
+ // 检查是否点击了按钮或控制器
388
+ const target = event.target as HTMLElement
389
+ if (target.closest('.bar-button') || target.closest('.controller')) {
390
+ return
391
+ }
392
+ // 调用 Tauri 拖拽命令
393
+ backend.call(undefined, 'win:startDrag', false)
394
+ }
395
+ }
396
+
397
+ /**
398
+ * 发起连接
399
+ */
400
+ function connect() {
401
+ if(tags.quickLoginSelect != '') {
402
+ // PS:快速连接的地址只会是局域网,所以默认 ws 协议
403
+ loginInfo.address = 'ws://' + tags.quickLoginSelect
404
+ }
405
+ Connector.create(loginInfo.address, loginInfo.token)
406
+ }
407
+
408
+ function selectQuickLogin(address: string) {
409
+ tags.quickLoginSelect = address
410
+ }
411
+ function cancelQUickLogin() {
412
+ loginInfo.quickLogin = null
413
+ }
414
+
415
+ /**
416
+ * 地址输入框变化时
417
+ */
418
+ function onAddressInput() {
419
+ // 用户手动修改地址时,取消历史选择
420
+ if (tags.selectedHistoryIndex >= 0) {
421
+ tags.selectedHistoryIndex = -1
422
+ }
423
+ }
424
+
425
+ /**
426
+ * 切换历史下拉显示
427
+ */
428
+ function toggleHistoryDropdown(event: Event) {
429
+ event.stopPropagation()
430
+ event.preventDefault()
431
+ tags.showHistoryDropdown = !tags.showHistoryDropdown
432
+ }
433
+
434
+ /**
435
+ * 点击外部关闭下拉菜单
436
+ */
437
+ function handleClickOutside(event: Event) {
438
+ if (!tags.showHistoryDropdown) return
439
+
440
+ const target = event.target as HTMLElement
441
+ const container = document.querySelector('.address-input-container')
442
+
443
+ if (container && !container.contains(target)) {
444
+ tags.showHistoryDropdown = false
445
+ }
446
+ }
447
+
448
+ /**
449
+ * 选择历史连接
450
+ */
451
+ function selectHistoryItem(index: number) {
452
+ tags.selectedHistoryIndex = index
453
+
454
+ // 选中了历史连接
455
+ if (loginInfo.connectionHistory && loginInfo.connectionHistory[index]) {
456
+ const item = loginInfo.connectionHistory[index]
457
+ loadConnectionFromHistory(item)
458
+ }
459
+
460
+ tags.showHistoryDropdown = false
461
+ }
462
+
463
+ /**
464
+ * 删除历史连接
465
+ */
466
+ function deleteHistoryConnection(index: number, event?: Event) {
467
+ if (event) {
468
+ event.preventDefault()
469
+ event.stopPropagation()
470
+ }
471
+ deleteConnectionHistory(index)
472
+ // 如果删除的是当前选中的,取消选择
473
+ if (tags.selectedHistoryIndex === index) {
474
+ tags.selectedHistoryIndex = -1
475
+ } else if (tags.selectedHistoryIndex > index) {
476
+ // 如果删除的在当前选中之前,索引需要减1
477
+ tags.selectedHistoryIndex--
478
+ }
479
+ }
480
+
481
+ /**
482
+ * 切换主标签卡判定
483
+ * @param name 页面名称
484
+ * @param view 虚拟路径名称
485
+ * @param show 是否显示聊天面板
486
+ */
487
+ function changeTab(_: string, view: string, show: boolean) {
488
+ // UM:发送页面路由分析
489
+ if (
490
+ !Option.get('close_ga') &&
491
+ !dev
492
+ ) {
493
+ }
494
+ tags.showChat = show
495
+ tags.page = view
496
+ if (view === 'Messages' || view === 'Friends') {
497
+ void loadContactsFromCache()
498
+ }
499
+ // 附加操作
500
+ const optTab = document.getElementsByClassName('opt-main-tab')[0] as HTMLDivElement
501
+ switch (view) {
502
+ case 'Options': {
503
+ Connector.send('get_version_info', {}, 'getVersionInfo')
504
+ if (optTab) {
505
+ optTab.style.opacity = '1'
506
+ }
507
+ break
508
+ }
509
+ }
510
+ }
511
+ function barMainClick() {
512
+ changeTab('信息', 'Messages', true)
513
+ }
514
+
515
+ /**
516
+ * 水波动画启动器
517
+ * @param wave HTML 对象
518
+ * @returns 动画循环器对象
519
+ */
520
+ function waveAnimation(wave: HTMLElement | null) {
521
+ if (wave) {
522
+ const waves = wave.children[1].children
523
+ const min = 20
524
+ const max = 195
525
+ const add = 1
526
+ const timer = setInterval(() => {
527
+ // 遍历波浪体
528
+ for (let i = 0; i < waves.length; i++) {
529
+ const now = waves[i].getAttribute('x')
530
+ if (Number(now) + add > max) {
531
+ waves[i].setAttribute('x', min.toString())
532
+ } else {
533
+ waves[i].setAttribute(
534
+ 'x',
535
+ (Number(now) + add).toString(),
536
+ )
537
+ }
538
+ }
539
+ }, 50)
540
+ return timer
541
+ }
542
+ return -1
543
+ }
544
+
545
+ /**
546
+ * 刷新页面 fps 数据
547
+ * @param timestamp 时间戳
548
+ */
549
+ function rafLoop() {
550
+ fps.value.ticks += 1
551
+ //每30帧统计一次帧率
552
+ if (fps.value.ticks >= 30) {
553
+ const now = Date.now()
554
+ const diff = now - fps.value.last
555
+ const value = Math.round(1000 / (diff / fps.value.ticks))
556
+ fps.value.last = now
557
+ fps.value.ticks = 0
558
+ fps.value.value = value
559
+ }
560
+ requestAnimationFrame(rafLoop)
561
+ }
562
+
563
+ /**
564
+ * 切换聊天对象状态
565
+ * @param data 切换信息
566
+ */
567
+ function changeChat(data: BaseChatInfoElem) {
568
+ if (
569
+ chatStore.chatInfo.show.id === data.id &&
570
+ chatStore.chatInfo.show.type === data.type
571
+ ) {
572
+ return
573
+ }
574
+ if (
575
+ chatStore.chatInfo.show.id !== data.id ||
576
+ chatStore.chatInfo.show.type !== data.type
577
+ ) {
578
+ chatStore.messageList = []
579
+ }
580
+ uiStore.nowGetHistory = false
581
+ uiStore.historyBeforeTime = undefined
582
+ void loadContactsFromCache()
583
+ // 设置聊天信息
584
+ chatStore.chatInfo = {
585
+ show: data,
586
+ info: {
587
+ group_info: {},
588
+ user_info: {},
589
+ me_info: {},
590
+ group_members: [],
591
+ group_files: {},
592
+ group_sub_files: {},
593
+ jin_info: {
594
+ list: [] as { [key: string]: any }[],
595
+ pages: 0,
596
+ },
597
+ },
598
+ }
599
+ chatStore.mergeMessageList = undefined // 清空合并转发缓存
600
+ uiStore.canLoadHistory = true // 重置终止加载标志
601
+ uiStore.loadHistoryFail = false // 重置加载失败标志
602
+ if (data.type == 'group' && String(data.id ?? '') && String(data.id) !== '0') {
603
+ // 获取群成员列表改为读取本地缓存,不再请求 guild.member.list
604
+ void loadGroupMembersFromCache(String(data.id))
605
+ }
606
+
607
+ // 清理通知
608
+ backend.call(undefined, 'sys:closeAllNotice', false, String(data.id))
609
+ }
610
+
611
+ /**
612
+ * 移除当前的全局弹窗
613
+ */
614
+ function removePopBox() {
615
+ uiStore.popBoxList[0]?.onClose?.()
616
+ uiStore.popBoxList.shift()
617
+ }
618
+
619
+ /**
620
+ * 保存密码
621
+ * @param event 事件
622
+ */
623
+ function savePassword(event: Event) {
624
+ const sender = event.target as HTMLInputElement
625
+ const value = sender.checked
626
+ if (value) {
627
+ Option.save('save_password', true)
628
+ // 创建提示弹窗
629
+ const popInfo = {
630
+ title: $t('提醒'),
631
+ html: `<span>${$t('连接密钥将以明文存储在浏览器 Cookie 中,请确保设备安全以防止密钥泄漏。')}</span>`,
632
+ button: [
633
+ {
634
+ text: $t('知道了'),
635
+ master: true,
636
+ fun: () => {
637
+ uiStore.popBoxList.shift()
638
+ },
639
+ },
640
+ ],
641
+ }
642
+ uiStore.popBoxList.push(popInfo)
643
+ } else {
644
+ Option.remove('save_password')
645
+ }
646
+ }
647
+
648
+ /**
649
+ * 保存自动连接
650
+ * @param event 事件
651
+ */
652
+ function saveAutoConnect(event: Event) {
653
+ Option.runASWEvent(event)
654
+ // 如果自动保存密码没开,那也需要开
655
+ if (!settingsStore.sysConfig.save_password) {
656
+ savePassword(event)
657
+ }
658
+ }
659
+
660
+ /**
661
+ * 快速关闭弹窗(点击空白处关闭)
662
+ * @param allow 是否允许快速关闭
663
+ */
664
+ function popQuickClose(allow: boolean | undefined) {
665
+ if (allow != false) {
666
+ uiStore.popBoxList[0]?.onClose?.()
667
+ uiStore.popBoxList.shift()
668
+ } else {
669
+ const animeBody = document.getElementById('pop-box')
670
+ const timeLine = anime.timeline({ targets: animeBody })
671
+ // 使用 animejs 实现一个沿中心左右摇晃的动画,摇晃三次
672
+ timeLine.add({
673
+ rotate: [
674
+ { value: -1, duration: 75, easing: 'easeInOutSine' },
675
+ { value: 1, duration: 150, easing: 'easeInOutSine' },
676
+ { value: 0, duration: 75, easing: 'easeInOutSine' },
677
+ ],
678
+ duration: 200,
679
+ easing: 'easeInOutSine',
680
+ loop: 3,
681
+ })
682
+ }
683
+ }
684
+
685
+ function afd(event: MouseEvent) {
686
+ // 只在愚人节时生效
687
+ if (new Date().getMonth() == 3 && new Date().getDate() == 1) {
688
+ const sender = event.target as HTMLButtonElement
689
+ // 获取文档整体宽高
690
+ const docWidth = document.documentElement.clientWidth
691
+ const docHeight = document.documentElement.clientHeight
692
+ // 获取按钮宽高
693
+ const senderWidth = sender.offsetWidth
694
+ const senderHeight = sender.offsetHeight
695
+ // 获取鼠标位置
696
+ const mouseX = event.clientX
697
+ const mouseY = event.clientY
698
+ // 在宽高里随机抽一个位置,不能超出文档,不能让按钮在鼠标下
699
+ let x, y
700
+ do {
701
+ x = Math.floor(Math.random() * docWidth)
702
+ y = Math.floor(Math.random() * docHeight)
703
+ } while (
704
+ x + senderWidth > docWidth ||
705
+ y + senderHeight > docHeight ||
706
+ (x < mouseX &&
707
+ x + senderWidth > mouseX &&
708
+ y < mouseY &&
709
+ y + senderHeight > mouseY)
710
+ )
711
+ // 设置按钮位置
712
+ sender.style.left = x + 'px'
713
+ sender.style.top = y + 'px'
714
+ }
715
+ }
716
+
717
+ //#endregion
718
+
719
+ function preventGlobalSelectAll(event: KeyboardEvent) {
720
+ const key = event.key.toLowerCase()
721
+ if (!(event.ctrlKey || event.metaKey) || key !== 'a') return
722
+ const target = event.target as HTMLElement | null
723
+ const tag = target?.tagName.toLowerCase() ?? ''
724
+ if (tag === 'input' || tag === 'textarea' || target?.isContentEditable) return
725
+ event.preventDefault()
726
+ }
727
+
728
+ //#region == 生命周期 ====================================================================
729
+
730
+ onMounted(() => {
731
+ const logger = new Logger()
732
+ window.moYu = () => { return '\x75\x6e\x64\x65\x66\x69\x6e\x65\x64' }
733
+
734
+ // 添加全局点击事件监听,用于关闭下拉菜单
735
+ document.addEventListener('click', handleClickOutside)
736
+ window.addEventListener('keydown', preventGlobalSelectAll, true)
737
+ window.addEventListener('resize', updateLayoutState)
738
+ updateLayoutState()
739
+ refreshCurrentMusic()
740
+ musicSyncTimer = window.setInterval(() => {
741
+ refreshCurrentMusic()
742
+ }, 1000)
743
+
744
+ // 监听 FileManager 面板状态
745
+ watch(() => panelVisible.value, (val) => {
746
+ tags.showFileManager = val
747
+ })
748
+ watch(() => loginInfo.status, (status) => {
749
+ if (status) changeTab('信息', 'Messages', true)
750
+ })
751
+ watch(showFileManagerEntry, (val) => {
752
+ if (!val && tags.showFileManager) {
753
+ toggleFileManager(false)
754
+ }
755
+ }, { immediate: true })
756
+
757
+ // 页面加载完成后
758
+ window.onload = async () => {
759
+ await backend.init() // Desktop:初始化客户端功能
760
+
761
+ // 初始化波浪动画
762
+ setLoginWaveTimer(waveAnimation(
763
+ document.getElementById('login-wave'),
764
+ ))
765
+ // =============================================================
766
+ // 初始化功能
767
+ App.createMenu() // Electron:创建菜单
768
+ App.createIpc() // Electron:创建 IPC 通信
769
+ // 加载开发者相关功能
770
+ if (dev) {
771
+ document.title = 'Koishi Satori Chat (Dev)'
772
+ // FPS 检查
773
+ rafLoop()
774
+ }
775
+ // 加载设置项
776
+ const loadedConfig = await Option.load()
777
+ const migratedBackground = await migrateInlineBackgroundImage(loadedConfig.chat_background)
778
+ if (migratedBackground) {
779
+ loadedConfig.chat_background = migratedBackground
780
+ Option.runAS('chat_background', migratedBackground)
781
+ }
782
+ settingsStore.sysConfig = loadedConfig
783
+ if (!migratedBackground) hydrateBackgroundImage(settingsStore.sysConfig.chat_background)
784
+ logger.add(LogType.DEBUG, '系统配置', settingsStore.sysConfig)
785
+ // PS:重新再应用部分需要加载完成后才能应用的设置
786
+ Option.run('opt_dark', Option.get('opt_dark'))
787
+ Option.run('theme_color', Option.get('theme_color'))
788
+ // 流体玻璃样式附加设置
789
+ if (Option.get('glass_effect')) {
790
+ const app = document.getElementById('app')
791
+ const body = document.body
792
+ if(app && body) {
793
+ body.style.setProperty('background', 'rgba(var(--color-bg-rgb), 0.5)', 'important')
794
+ app.style.borderRadius = '25px'
795
+ }
796
+ }
797
+ if (['linux', 'win32'].includes(backend.platform ?? '')) {
798
+ const app = document.getElementById('base-app')
799
+ if (app) app.classList.add('withBar')
800
+ }
801
+ // 基础初始化完成
802
+ logger.system('欢迎回来,开发者。Koishi Satori 聊天室正处于 ' + (dev ? 'development' : 'production') + ' 模式。正在为您加载更多功能。')
803
+ const loadView = document.getElementById('load-view')
804
+ loadView?.remove()
805
+ setTimeout(() => {
806
+ const mainBody = document.getElementById('base-app')?.children[0]
807
+ const mainBg = document.getElementById('main-bg')
808
+ mainBody?.classList.remove('onloading')
809
+ mainBg?.classList.remove('onloading')
810
+ setTimeout(() => {
811
+ const sideBar = document.getElementById('side-bar')
812
+ sideBar?.classList.remove('onloading')
813
+ setTimeout(() => {
814
+ const devBar = document.getElementById('dev-bar')
815
+ devBar?.classList.remove('onloading')
816
+ }, 400)
817
+ }, 400)
818
+ }, 100)
819
+ // 加载移动平台特性
820
+ App.loadMobile()
821
+ // 加载额外样式
822
+ App.loadAppendStyle()
823
+ document.body.style.setProperty('--safe-area-bottom',
824
+ (Option.get('fs_adaptation') > 0 ? Option.get('fs_adaptation') : 0) + 'px')
825
+ document.body.style.setProperty('--safe-area-top', '0')
826
+ document.body.style.setProperty('--safe-area-left', '0')
827
+ document.body.style.setProperty('--safe-area-right', '0')
828
+ // Capacitor:移动端初始化安全区域
829
+ if (backend.isMobile()) {
830
+ // 我把 viewer 挂在 body 上,所以css也得改到 body 上
831
+ const safeArea = await backend.call('SafeArea', 'getSafeArea', true)
832
+ if (safeArea) {
833
+ logger.add(LogType.DEBUG, '安全区域:', safeArea)
834
+ document.body.style.setProperty('--safe-area-top', safeArea.top + 'px')
835
+ document.body.style.setProperty('--safe-area-bottom', safeArea.bottom + 'px')
836
+ document.body.style.setProperty('--safe-area-left', safeArea.left + 'px')
837
+ document.body.style.setProperty('--safe-area-right', safeArea.right + 'px')
838
+ // 图片查看器安全区域
839
+ document.body.style.setProperty('--safe-area--viewer-top', safeArea.top + 'px')
840
+ }
841
+ }
842
+ // 加载密码保存和自动连接
843
+ loginInfo.address = backend.isWeb() ? '/satori' : settingsStore.sysConfig.address
844
+ // 加载连接历史
845
+ loginInfo.connectionHistory = loadConnectionHistory()
846
+ if (
847
+ settingsStore.sysConfig.save_password !== undefined &&
848
+ settingsStore.sysConfig.save_password !== true
849
+ ) {
850
+ loginInfo.token = decodeStoredToken(settingsStore.sysConfig.save_password) ?? ''
851
+ tags.savePassword = true
852
+ }
853
+ if (settingsStore.sysConfig.auto_connect == true || backend.isWeb()) {
854
+ connect()
855
+ }
856
+
857
+ // 服务发现
858
+ backend.call('Onebot', 'sys:findService', false)
859
+ backend.call('OneBot', 'sys:frontLoaded', false)
860
+ // =============================================================
861
+ // 初始化完成
862
+ // 创建 popstate
863
+ if(backend.platform == 'web' && (getDeviceType() === 'Android' || getDeviceType() === 'iOS')) {
864
+ window.addEventListener('popstate', () => {
865
+ if(!loginInfo.status || uiStore.openSideBar) {
866
+ // 离开提醒
867
+ const popInfo = {
868
+ title: $t('提醒'),
869
+ html: `<span>${$t('离开 Koishi Satori 聊天室?')}</span>`,
870
+ button: [
871
+ {
872
+ text: $t('取消'),
873
+ fun: () => {
874
+ uiStore.popBoxList.shift()
875
+ history.pushState('ssqqweb', '', location.href)
876
+ },
877
+ },
878
+ {
879
+ text: $t('离开'),
880
+ master: true,
881
+ fun: () => {
882
+ uiStore.popBoxList.shift()
883
+ history.back()
884
+ },
885
+ },
886
+ ],
887
+ }
888
+ uiStore.popBoxList.push(popInfo)
889
+ } else {
890
+ // 内部的页面返回处理,此处使用 watch backTimes 监听
891
+ connectionStore.backTimes += 1
892
+ history.pushState('ssqqweb', '', location.href)
893
+ }
894
+ });
895
+ if (history.state != 'ssqqweb') {
896
+ history.pushState('ssqqweb', '', location.href)
897
+ }
898
+ }
899
+ if (dev) {
900
+ logger.system('开发者,由于 Koishi Satori 聊天室运行在调试模式下,分析组件并未初始化 …… 系统将无法捕获开发者阁下的访问状态,请悉知。')
901
+ }
902
+ App.checkNotice() // 检查公告
903
+ // 加载愚人节附加
904
+ if (new Date().getMonth() == 3 && new Date().getDate() == 1) {
905
+ document.getElementById('connect_btn')?.classList.add('afd')
906
+ }
907
+ // 其他状态监听
908
+ watch(() => contactStore.baseOnMsgList, () => {
909
+ // macOS:刷新 Touch Bar 列表
910
+ if (backend.isDesktop()) {
911
+ const list = [] as
912
+ { id: number | string, name: string, image?: string }[]
913
+ contactStore.baseOnMsgList.forEach((item) => {
914
+ list.push({
915
+ id: item.user_id ? item.user_id : item.group_id,
916
+ name: item.group_name ? item.group_name : item.remark === item.nickname ? item.nickname : item.remark + '(' + item.nickname + ')',
917
+ image: item.avatar || '/img/icons/icon.svg'
918
+ })
919
+ })
920
+ backend.call(undefined, 'sys:flushOnMessage', false, list)
921
+ }
922
+
923
+ // 刷新列表
924
+ updateBaseOnMsgList()
925
+ }, { deep: true })
926
+ // 更新标题
927
+ const titleList = [
928
+ '也试试 Icalingua Plus Plus 吧!',
929
+ '点击阅读《社交功能限制提醒》',
930
+ '登录失败,Code 45',
931
+ '你好世界!',
932
+ '这只是个普通的彩蛋!'
933
+ ]
934
+ const title = titleList[Math.floor(Math.random() * titleList.length)]
935
+ if(backend.platform == 'web') {
936
+ document.title = title + '- Koishi Satori Chat'
937
+ } else {
938
+ document.title = title
939
+ backend.call(undefined, 'win:setTitle', false, title)
940
+ }
941
+ }
942
+ // 页面关闭前
943
+ window.onbeforeunload = () => {
944
+ logger.system('开发者阁下—— 唔,阁下离开的太匆忙了!让我来帮开发者阁下收拾下东西吧。')
945
+ new Notify().clear()
946
+ if(import.meta.env.DEV) {
947
+ Connector.close()
948
+ }
949
+ }
950
+ })
951
+
952
+ onUnmounted(() => {
953
+ // 移除全局点击事件监听器
954
+ document.removeEventListener('click', handleClickOutside)
955
+ window.removeEventListener('keydown', preventGlobalSelectAll, true)
956
+ window.removeEventListener('resize', updateLayoutState)
957
+ if (musicSyncTimer > 0) {
958
+ clearInterval(musicSyncTimer)
959
+ musicSyncTimer = -1
960
+ }
961
+ })
962
+
963
+ //#endregion
964
+ </script>
965
+
966
+ <style scoped>
967
+ /* 应用通知动画 */
968
+ .appmsg-move,
969
+ .appmsg-enter-active,
970
+ .appmsg-leave-active {
971
+ transition: all 0.2s;
972
+ }
973
+
974
+ .appmsg-leave-active {
975
+ position: absolute;
976
+ }
977
+
978
+ .appmsg-enter-from,
979
+ .appmsg-leave-to {
980
+ transform: translateX(-20px);
981
+ opacity: 0;
982
+ }
983
+
984
+ /* 标题栏变更动画 */
985
+ .appbar-enter-active,
986
+ .appbar-leave-active {
987
+ transition: all 0.2s;
988
+ }
989
+
990
+ .appbar-enter-from,
991
+ .appbar-leave-to {
992
+ transform: translateY(-60px);
993
+ }
994
+
995
+ /* 弹窗动画 */
996
+ .modal-enter-active {
997
+ transition: opacity 0.2s ease-out;
998
+ }
999
+
1000
+ .modal-leave-active {
1001
+ transition: opacity 0.2s ease-in;
1002
+ }
1003
+
1004
+ .modal-leave-to {
1005
+ opacity: 0;
1006
+ }
1007
+
1008
+ .modal-enter-active .pop-box-body {
1009
+ animation: panelSlideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1010
+ }
1011
+
1012
+ .modal-leave-active .pop-box-body {
1013
+ animation: panelSlideDown 0.2s cubic-bezier(0.4, 0, 0.6, 1);
1014
+ }
1015
+
1016
+ .music-entry-small {
1017
+ display: none;
1018
+ }
1019
+ .music-entry-cover {
1020
+ width: 25px;
1021
+ height: 25px;
1022
+ border-radius: 6px;
1023
+ object-fit: cover;
1024
+ margin: 10px;
1025
+ }
1026
+ .music-entry-status {
1027
+ transform: translateY(calc(-100% - 3px));
1028
+ margin-bottom: calc(-100% + 10px);
1029
+ background-color: transparent !important;
1030
+ color: #545454 !important;
1031
+ }
1032
+ .music-entry-status.light {
1033
+ color: #e5e5e5 !important;
1034
+ }
1035
+ .music-entry-always-hide {
1036
+ display: none !important;
1037
+ }
1038
+ .music-entry-small-always-show {
1039
+ display: flex !important;
1040
+ margin-bottom: 10px !important;
1041
+ }
1042
+ .music-entry-small-always-show img {
1043
+ width: 35px;
1044
+ height: 35px;
1045
+ }
1046
+ .music-entry-small-always-show svg {
1047
+ transform: translateY(calc(-100% - 10px));
1048
+ }
1049
+
1050
+ .global-music-player {
1051
+ position: fixed;
1052
+ left: 90px;
1053
+ bottom: 20px;
1054
+ z-index: 33;
1055
+ width: 350px;
1056
+ max-height: min(72vh, 560px);
1057
+ box-shadow: 0 0 10px var(--color-shader);
1058
+ overflow: auto;
1059
+ }
1060
+
1061
+ .music-player-float-enter-active,
1062
+ .music-player-float-leave-active {
1063
+ transition: opacity 0.2s ease, transform 0.2s ease;
1064
+ }
1065
+
1066
+ .music-player-float-enter-from,
1067
+ .music-player-float-leave-to {
1068
+ opacity: 0;
1069
+ transform: translateY(12px);
1070
+ }
1071
+
1072
+ @media (max-width: 700px) {
1073
+ .global-music-player {
1074
+ left: 100px;
1075
+ bottom: 60px;
1076
+ width: calc(100vw - 200px);
1077
+ max-height: min(66vh, 460px);
1078
+ }
1079
+ }
1080
+
1081
+ @media (max-width: 500px) {
1082
+ .global-music-player {
1083
+ left: 20px !important;
1084
+ bottom: 60px;
1085
+ width: calc(100vw - 70px);
1086
+ max-height: min(66vh, 460px);
1087
+ }
1088
+ .music-entry-small {
1089
+ margin-bottom: 10px !important;
1090
+ display: flex;
1091
+ }
1092
+ .music-entry-small img {
1093
+ width: 35px;
1094
+ height: 35px;
1095
+ }
1096
+ .music-entry-small svg {
1097
+ transform: translateY(calc(-100% - 10px));
1098
+ }
1099
+ .music-entry {
1100
+ display: none;
1101
+ }
1102
+ }
1103
+
1104
+ @keyframes panelSlideUp {
1105
+ from {
1106
+ transform: translate(-50%, -20%) scale(0.95);
1107
+ opacity: 0;
1108
+ }
1109
+
1110
+ to {
1111
+ transform: translate(-50%, -50%) scale(1);
1112
+ opacity: 1;
1113
+ }
1114
+ }
1115
+
1116
+ @keyframes panelSlideDown {
1117
+ from {
1118
+ transform: translate(-50%, -50%) scale(1);
1119
+ opacity: 1;
1120
+ }
1121
+
1122
+ to {
1123
+ transform: translate(-50%, -5%) scale(0.98);
1124
+ opacity: 0;
1125
+ }
1126
+ }
1127
+ </style>