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,301 @@
1
+ <!--
2
+ * @FileDescription: 快速打开会话搜索栏
3
+ * @Author: Mr.Lee
4
+ * @Date:
5
+ * 2025/08/19
6
+ * @Version:
7
+ * 1.0 - 初始版本
8
+ * @Description:
9
+ * 该组件用于在全局范围内快速搜索和选择会话。
10
+ * 通过按下 Ctrl + E 快捷键打开搜索栏,用户可以输入关键词来过滤会话列表。
11
+ * 支持上下键选择会话,回车键确认选择。
12
+ * 被VSC的快捷键 Ctrl + E 激励创作的
13
+ -->
14
+ <template>
15
+ <Teleport to="body">
16
+ <Transition name="global-session-search-bar">
17
+ <div v-if="show"
18
+ class="mask-background"
19
+ @click.stop.prevent="close">
20
+ <div ref="content" class="global-session-search-bar ss-card">
21
+ <label for="global-session-search-input" class="sr-only">{{ $t('搜索会话') }}</label>
22
+ <input
23
+ id="global-session-search-input"
24
+ ref="input"
25
+ v-search="searchInfo"
26
+ class="ss-input"
27
+ :placeholder="$t('搜索 ……')"
28
+ @focusout="close">
29
+ <template v-if="showSessions.length > 0">
30
+ <hr>
31
+ <div ref="sessionList">
32
+ <div v-for="(session, key) in showSessions"
33
+ :key="session.user_id ?? session.group_id"
34
+ :class="{
35
+ 'session-item': true,
36
+ 'selected': selectId === key,
37
+ }"
38
+ @click="choiceSession(session)">
39
+ <TinySessionBody
40
+ ref="sessionItems"
41
+ :session="session"
42
+ from="global-search" />
43
+ <span v-if="
44
+ (session.group_id ?? session.user_id) === chatStore.chatInfo.show.id">
45
+ {{ $t('当前会话') }}
46
+ </span>
47
+ </div>
48
+ </div>
49
+ </template>
50
+ </div>
51
+ </div>
52
+ </Transition>
53
+ </Teleport>
54
+ </template>
55
+
56
+ <script setup lang="ts">
57
+ import {
58
+ ref,
59
+ watchEffect,
60
+ computed,
61
+ shallowReactive,
62
+ useTemplateRef,
63
+ nextTick,
64
+ } from 'vue'
65
+ import TinySessionBody from './TinySessionBody.vue'
66
+ import { vSearch, useEventListener } from '../function/utils/appUtil'
67
+ import { UserFriendElem, UserGroupElem } from '../function/elements/information'
68
+ import { login } from '../function/connect'
69
+ import { backend } from '../runtime/backend'
70
+ import { matchPinyin } from '../function/utils/pinyin'
71
+ import { useUIStore } from '../state/ui'
72
+ import { useContactStore } from '../state/contact'
73
+ import { useChatStore } from '../state/chat'
74
+
75
+ //#region == 移植相关 ====================================================================
76
+ /**
77
+ * 会话对象
78
+ */
79
+ type Session = UserGroupElem & UserFriendElem
80
+ /**
81
+ * 切换会话
82
+ * @param session
83
+ */
84
+ function changeSession(session: Session) {
85
+ const id = session.user_id ?? session.group_id
86
+ if (id === chatStore.chatInfo.show.id) return
87
+
88
+ contactStore.baseOnMsgList.set(String(session.user_id ?? session.group_id), session)
89
+ // 切换到这个聊天
90
+ nextTick(() => {
91
+ const item = document.getElementById(
92
+ 'user-' + (session.user_id ?? session.group_id),
93
+ )
94
+ if (item) {
95
+ item.click()
96
+ }
97
+ })
98
+ }
99
+ /**
100
+ * 会话匹配算法
101
+ * @param session
102
+ * @param query
103
+ */
104
+ function sessionSearchMatch(session: Session, query: string): boolean {
105
+ const value = query.toLowerCase()
106
+ const name = (
107
+ session.user_id? session.nickname + session.remark: session.group_name
108
+ ).toLowerCase()
109
+ if (name.includes(value)) return true
110
+ const id = session.user_id? session.user_id: session.group_id
111
+ if (id.toString() === value) return true
112
+ if (session.py_name && matchPinyin(session.py_name, value)) return true
113
+ return false
114
+ }
115
+ /**
116
+ * 判断有没有弹窗
117
+ */
118
+ function hasPopBox() {
119
+ return uiStore.popBoxList.length > 0
120
+ }
121
+ //#endregion
122
+
123
+ //#region == 常量声明 ====================================================================
124
+ const uiStore = useUIStore()
125
+ const contactStore = useContactStore()
126
+ const chatStore = useChatStore()
127
+ const show = ref<boolean>(false)
128
+ const input = useTemplateRef<HTMLInputElement>('input')
129
+ const sessionList = useTemplateRef<HTMLElement>('sessionList')
130
+ const sessionItems = useTemplateRef<InstanceType<typeof TinySessionBody>[]>('sessionItems')
131
+ const selectId = ref<number>(0)
132
+
133
+ const searchInfo = shallowReactive({
134
+ originList: shallowReactive([] as Session[]),
135
+ query: shallowReactive([] as Session[]),
136
+ isSearch: false,
137
+ match: sessionSearchMatch,
138
+ })
139
+
140
+ const showSessions = computed(() => {
141
+ return searchInfo.isSearch ? searchInfo.query : searchInfo.originList
142
+ })
143
+ // 限制selectId的范围
144
+ watchEffect(()=>{
145
+ if (selectId.value < 0)
146
+ selectId.value = showSessions.value.length - 1
147
+ if (selectId.value >= showSessions.value.length)
148
+ selectId.value = 0
149
+
150
+ // 出界滚动
151
+ scrollToSelected()
152
+ })
153
+ //#endregion
154
+
155
+ //#region == 方法函数 ====================================================================
156
+ /**
157
+ * 打开搜索栏
158
+ */
159
+ function open() {
160
+ init()
161
+ show.value = true
162
+ selectId.value = 0
163
+
164
+ // 聚焦输入框
165
+ nextTick(() => {
166
+ input.value?.focus()
167
+ })
168
+ }
169
+
170
+ /**
171
+ * 关闭搜索栏
172
+ */
173
+ function close() {
174
+ show.value = false
175
+ selectId.value = 0
176
+ }
177
+
178
+ /**
179
+ * 初始化搜索框
180
+ */
181
+ function init() {
182
+ // 主要列表
183
+ const mainList: Session[] = []
184
+ for (const session of contactStore.userList) {
185
+ if (contactStore.onMsgList.find(
186
+ s => (
187
+ s.user_id && s.user_id === session.user_id
188
+ ) || (
189
+ s.group_id && s.group_id === session.group_id
190
+ )
191
+ )) continue
192
+ mainList.push(session)
193
+ }
194
+
195
+ searchInfo.originList = shallowReactive([...contactStore.onMsgList, ...mainList])
196
+
197
+ searchInfo.query = shallowReactive([])
198
+ searchInfo.isSearch = false
199
+ selectId.value = 0
200
+ }
201
+
202
+ /**
203
+ * 选择会话
204
+ * @param session 被选择的会话
205
+ */
206
+ function choiceSession(session: Session) {
207
+ uiStore.openSideBar = false
208
+
209
+ changeSession(session)
210
+ close()
211
+ }
212
+
213
+ /**
214
+ * 根据id选择会话
215
+ * @param id
216
+ */
217
+ function choiceSessionById(id: number) {
218
+ if (id < 0 || id >= showSessions.value.length) return
219
+ choiceSession(showSessions.value[id])
220
+ }
221
+
222
+ /**
223
+ * 滚动到选中的会话项
224
+ */
225
+ function scrollToSelected() {
226
+ nextTick(() => {
227
+ if (!sessionItems.value) return
228
+ const selectedElement = sessionItems.value[selectId.value]
229
+ const container = sessionList.value!
230
+
231
+ if (selectedElement && container) {
232
+ const containerRect = container.getBoundingClientRect()
233
+ const elementRect = selectedElement.$el.getBoundingClientRect()
234
+
235
+ // 计算相对于容器的位置
236
+ const elementTop = elementRect.top - containerRect.top + container.scrollTop
237
+ const elementBottom = elementTop + elementRect.height
238
+
239
+ // 如果元素在可见区域上方
240
+ if (elementTop < container.scrollTop) {
241
+ container.scrollTop = elementTop
242
+ }
243
+ // 如果元素在可见区域下方
244
+ else if (elementBottom > container.scrollTop + container.clientHeight) {
245
+ container.scrollTop = elementBottom - container.clientHeight
246
+ }
247
+ }
248
+ })
249
+ }
250
+
251
+ /**
252
+ * 匹配到事件处理
253
+ * 会组织事件冒泡和默认行为
254
+ * @param e 事件
255
+ */
256
+ function handleEvent(e: Event) {
257
+ e.stopPropagation()
258
+ e.preventDefault()
259
+ }
260
+ //#endregion
261
+
262
+ //#region == 开启关闭 ====================================================================
263
+ useEventListener(document, 'keydown', (e: KeyboardEvent) => {
264
+ let isOpen = false;
265
+ if(backend.platform === 'darwin') {
266
+ // macOS 使用 Command + E
267
+ isOpen = e.metaKey && e.key === 'e'
268
+ } else {
269
+ isOpen = e.ctrlKey && e.key === 'e'
270
+ }
271
+ if (isOpen) {
272
+ //开启
273
+ if (show.value) return
274
+ if (!login.status) return
275
+ if (hasPopBox()) return
276
+ handleEvent(e)
277
+ open()
278
+ }else if (e.key === 'Escape') {
279
+ // 关闭
280
+ if (!show.value) return
281
+ handleEvent(e)
282
+ close()
283
+ }else if (e.key === 'ArrowDown') {
284
+ // 下
285
+ if (!show.value) return
286
+ handleEvent(e)
287
+ selectId.value ++
288
+ }else if (e.key === 'ArrowUp') {
289
+ // 上
290
+ if (!show.value) return
291
+ handleEvent(e)
292
+ selectId.value --
293
+ }else if (e.key === 'Enter') {
294
+ // 确认
295
+ if (!show.value) return
296
+ handleEvent(e)
297
+ choiceSessionById(selectId.value)
298
+ }
299
+ })
300
+ //#endregion
301
+ </script>
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <div class="history-box">
3
+ <div v-if="history.record.length === 0" class="space">
4
+ <font-awesome-icon :icon="['fas', 'inbox']" />
5
+ <span>{{ $t('空空如也') }}</span>
6
+ </div>
7
+ <div v-else class="main">
8
+ <tiny-session-body
9
+ v-for="session in history.record"
10
+ :key="session.user_id ?? session.group_id"
11
+ :session="session"
12
+ @click="changeSession(session)" />
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+
18
+ <script setup lang="ts">
19
+ import { Session } from '../function/elements/information';
20
+ import { useChatStore } from '../state/chat';
21
+ import { useSessionHistoryStore } from '../state/sessionHistory';
22
+ import { useContactStore } from '../state/contact';
23
+ import { nextTick } from 'vue';
24
+ import TinySessionBody from './TinySessionBody.vue';
25
+
26
+ const history = useSessionHistoryStore()
27
+ const contactStore = useContactStore()
28
+ const chatStore = useChatStore()
29
+
30
+ /**
31
+ * 切换会话
32
+ * @param session
33
+ */
34
+ function changeSession(session: Session) {
35
+ const id = session.user_id ?? session.group_id
36
+ if (id === chatStore.chatInfo.show.id) return
37
+
38
+ contactStore.baseOnMsgList.set(String(session.user_id ?? session.group_id), session)
39
+ // 切换到这个聊天
40
+ nextTick(() => {
41
+ const item = document.getElementById(
42
+ 'user-' + (session.user_id ?? session.group_id),
43
+ )
44
+ if (item) {
45
+ item.click()
46
+ }
47
+ })
48
+ }
49
+ </script>
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div ref="lottieContainer" :class="['lazy-lottie', {'loaded': loaded }]">
3
+ <Lottie
4
+ ref="lottieRef"
5
+ :animation-link="animationLink"
6
+ :title="title"
7
+ :auto-play="false"
8
+ @on-animation-loaded="loaded = true" />
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { ref, onMounted, onUnmounted } from 'vue'
14
+ import { Vue3Lottie as Lottie } from 'vue3-lottie'
15
+
16
+ defineProps<{
17
+ animationLink: string
18
+ title?: string
19
+ }>()
20
+
21
+ const lottieContainer = ref<HTMLElement | null>(null)
22
+ const lottieRef = ref<any>(null)
23
+ const loaded = ref(false)
24
+ let observer: IntersectionObserver | null = null
25
+
26
+ const playAnimation = () => {
27
+ if (!lottieRef.value) return
28
+ try {
29
+ lottieRef.value.play?.()
30
+ } catch (e) {
31
+ // do nothing
32
+ }
33
+ }
34
+
35
+ const pauseAnimation = () => {
36
+ if (lottieRef.value) {
37
+ lottieRef.value.pause?.()
38
+ }
39
+ }
40
+
41
+ onMounted(() => {
42
+ if (!lottieContainer.value) return
43
+
44
+ observer = new IntersectionObserver(
45
+ (entries) => {
46
+ entries.forEach((entry) => {
47
+ if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
48
+ playAnimation()
49
+ } else if (entry.intersectionRatio < 0.1) {
50
+ pauseAnimation()
51
+ }
52
+ })
53
+ },
54
+ {
55
+ rootMargin: '50px',
56
+ threshold: [0.1, 0.5, 0.9]
57
+ }
58
+ )
59
+
60
+ observer.observe(lottieContainer.value)
61
+ })
62
+
63
+ onUnmounted(() => {
64
+ pauseAnimation()
65
+ if (observer && lottieContainer.value) {
66
+ observer.unobserve(lottieContainer.value)
67
+ observer.disconnect()
68
+ observer = null
69
+ }
70
+ })
71
+ </script>
72
+
73
+ <style scoped>
74
+ .lazy-lottie {
75
+ display: inline-block;
76
+ min-height: 100px;
77
+ min-width: 100px;
78
+ }
79
+ .lazy-lottie.loaded {
80
+ background: transparent !important;
81
+ }
82
+ </style>
@@ -0,0 +1,127 @@
1
+ <template>
2
+ <div :class="'merge-pan' + (chatStore.mergeMsgStack.length > 0 ? ' show' : '')">
3
+ <div @click="closeMergeMsg" />
4
+ <div class="ss-card">
5
+ <div>
6
+ <font-awesome-icon style="margin-top: 5px" :icon="['fas', 'message']" />
7
+ <span>{{ $t('合并消息') }}</span>
8
+ <font-awesome-icon v-if="stack.length <= 1" :icon="['fas', 'xmark']" @click="exitMergeMsg" />
9
+ <font-awesome-icon v-else :icon="['fas', 'angles-left']" @click="exitMergeMsg" />
10
+ </div>
11
+ <div v-if="nowData === undefined">
12
+ <!-- 无内容 -->
13
+ </div>
14
+ <TransitionGroup v-else :name="settingsStore.sysConfig.opt_fast_animation ? '' : 'msglist'" tag="div">
15
+ <template v-for="(msgIndex, index) in nowData.messageList" :key="'merge-' + nowData.forwardMsg.message[0].id + '-' + index">
16
+ <NoticeBody v-if=" isShowTime( nowData.messageList[index - 1] ?
17
+ nowData.messageList[index - 1].time : undefined, msgIndex.time, index == 0)"
18
+ :id="uuid()"
19
+ :key="'notice-time-' + index"
20
+ :data="{ sub_type: 'time', time: msgIndex.time }" />
21
+ <!-- [已删除]消息 -->
22
+ <NoticeBody
23
+ v-else-if="isDeleteMsg(msgIndex)"
24
+ :key="'delete-' + msgIndex.message_id"
25
+ :data="{ sub_type: 'delete' }" />
26
+ <!-- 合并转发消息忽略是不是自己的判定 -->
27
+ <MsgBody
28
+ :data="msgIndex"
29
+ :type="'merge'"
30
+ :image-list-header="mergeImgHead" />
31
+ </template>
32
+ </TransitionGroup>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { v4 as uuid } from 'uuid'
39
+ import { computed, ref, watch, onMounted } from 'vue'
40
+
41
+ import MsgBody from '../components/MsgBody.vue'
42
+ import NoticeBody from '../components/NoticeBody.vue'
43
+
44
+ import { Img } from '../function/model/img'
45
+ import { i18n } from '../main'
46
+ import { useSettingsStore } from '../state/settings'
47
+ import { type MergeStackData } from '../function/elements/information'
48
+ import { isDeleteMsg, isShowTime } from '../function/utils/msgUtil'
49
+ import { useChatStore } from '../state/chat'
50
+
51
+ defineOptions({ name: 'MergePan' })
52
+
53
+ const $t = i18n.global.t
54
+
55
+ const chatStore = useChatStore()
56
+ const settingsStore = useSettingsStore()
57
+ const stack = chatStore.mergeMsgStack
58
+ const nowData = ref<MergeStackData | undefined>()
59
+ const mergeImgHead = computed(() =>
60
+ Img.fromList((nowData.value?.imageList ?? []).map(item => item.img_url))
61
+ )
62
+
63
+ /**
64
+ * 退出一层合并转发弹窗
65
+ */
66
+ function exitMergeMsg() {
67
+ stack.length--
68
+ }
69
+
70
+ /**
71
+ * 关闭合并转发弹窗
72
+ */
73
+ function closeMergeMsg() {
74
+ stack.length = 0
75
+ }
76
+
77
+ function isMergeOpen() {
78
+ return stack.length > 0
79
+ }
80
+
81
+ defineExpose({ closeMergeMsg, isMergeOpen })
82
+
83
+ onMounted(() => {
84
+ watch(
85
+ () => chatStore.mergeMsgStack.length,
86
+ () => {
87
+ // 最后一个保留下来做展开关闭动画
88
+ if (stack.length === 0) {
89
+ // 清理下垃圾
90
+ chatStore.mergeMessageImgList = undefined
91
+ }
92
+ else nowData.value = stack.at(-1)
93
+ }
94
+ )
95
+ watch(
96
+ () => nowData.value?.imageList,
97
+ () => {
98
+ if (chatStore.mergeMsgStack.length === 0 || nowData.value?.imageList === undefined) return
99
+ chatStore.mergeMessageImgList = nowData.value.imageList
100
+ }
101
+ )
102
+ })
103
+ </script>
104
+
105
+ <style scoped>
106
+ /* 消息动画 */
107
+ .msglist-move {
108
+ transition: all 0.3s;
109
+ }
110
+
111
+ .msglist-enter-active {
112
+ transition: all 0.4s;
113
+ }
114
+
115
+ .msglist-leave-active {
116
+ transition: all 0.2s;
117
+ }
118
+
119
+ .msglist-enter-from {
120
+ transform: translateX(-50px);
121
+ opacity: 0;
122
+ }
123
+
124
+ .msglist-leave-to {
125
+ opacity: 0;
126
+ }
127
+ </style>