koishi-plugin-chat-patch 6.1.0 → 6.1.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.
- package/client/web/dist/assets/Chat-BduEIlJM.js +34 -0
- package/client/web/dist/assets/{Chat-Cee07eDL.css → Chat-DgkvjRIh.css} +1 -1
- package/client/web/dist/assets/{MsgBody-krZlLzRL.js → MsgBody-CVtXorvl.js} +1 -1
- package/client/web/dist/assets/{MsgBody.vue_vue_type_script_setup_true_lang-CLmUjKws.js → MsgBody.vue_vue_type_script_setup_true_lang-D_70Txdf.js} +1 -1
- package/client/web/dist/assets/{index-X9hFOAza.css → index-BW47RWcA.css} +1 -1
- package/client/web/dist/assets/{index-DOJoilEy.js → index-N2_GDY3v.js} +3 -3
- package/client/web/dist/index.html +2 -2
- package/client/web/src/assets/css/chat.css +24 -2
- package/client/web/src/function/custom-element.ts +31 -0
- package/client/web/src/function/utils/msgUtil.ts +39 -23
- package/client/web/src/pages/Chat.vue +136 -7
- package/lib/index.js +28 -0
- package/package.json +1 -1
- package/src/web-error-filter.ts +30 -0
- package/src/web.ts +3 -1
- package/client/web/dist/assets/Chat-BLcuHxzh.js +0 -28
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import h from '@satorijs/element'
|
|
2
|
+
|
|
3
|
+
// 自定义消息元素:使用 Satori 元素工厂,保留命名空间扩展类型。
|
|
4
|
+
const ELEMENT_TYPE_PATTERN = /^[a-z][a-z0-9_:-]*$/i
|
|
5
|
+
|
|
6
|
+
type CustomElement = ReturnType<typeof h>
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 将输入的类型和内容转换为 Satori 元素。
|
|
10
|
+
* 空内容不生成元素,避免发送无意义的空消息。
|
|
11
|
+
*/
|
|
12
|
+
export function buildCustomElement(type: string, content: string): CustomElement | null {
|
|
13
|
+
const elementType = type.trim()
|
|
14
|
+
const elementContent = content.trim()
|
|
15
|
+
if (!ELEMENT_TYPE_PATTERN.test(elementType) || elementContent.length === 0) return null
|
|
16
|
+
|
|
17
|
+
const normalizedType = elementType.toLowerCase()
|
|
18
|
+
if (normalizedType === 'text') return h.text(content)
|
|
19
|
+
if (normalizedType === 'img' || normalizedType === 'image') return h.img(elementContent)
|
|
20
|
+
if (normalizedType === 'file') return h.file(elementContent)
|
|
21
|
+
if (normalizedType === 'video') return h.video(elementContent)
|
|
22
|
+
if (normalizedType === 'audio' || normalizedType === 'record') return h.audio(elementContent)
|
|
23
|
+
if (normalizedType === 'json' || normalizedType === 'xml') {
|
|
24
|
+
return h(normalizedType, { data: elementContent })
|
|
25
|
+
}
|
|
26
|
+
if (normalizedType === 'markdown') {
|
|
27
|
+
return h('markdown', elementContent)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return h(elementType, elementContent)
|
|
31
|
+
}
|
|
@@ -459,12 +459,12 @@ export function parseCQ(data: any) {
|
|
|
459
459
|
* @param preShow 是否消息预显
|
|
460
460
|
* @param echo 回显的事件名
|
|
461
461
|
*/
|
|
462
|
-
function parsePreviewMarkup(source: string): MsgItemElem[] {
|
|
463
|
-
const result: MsgItemElem[] = []
|
|
464
|
-
const tagPattern = /<(
|
|
465
|
-
let last = 0
|
|
466
|
-
let match: RegExpExecArray | null
|
|
467
|
-
while ((match = tagPattern.exec(source)) !== null) {
|
|
462
|
+
function parsePreviewMarkup(source: string): MsgItemElem[] {
|
|
463
|
+
const result: MsgItemElem[] = []
|
|
464
|
+
const tagPattern = /<([a-z][\w:-]*)\s+([^>]*?)\/?>/gi
|
|
465
|
+
let last = 0
|
|
466
|
+
let match: RegExpExecArray | null
|
|
467
|
+
while ((match = tagPattern.exec(source)) !== null) {
|
|
468
468
|
if (match.index > last) {
|
|
469
469
|
result.push({ type: 'text', text: source.slice(last, match.index) })
|
|
470
470
|
}
|
|
@@ -475,20 +475,28 @@ function parsePreviewMarkup(source: string): MsgItemElem[] {
|
|
|
475
475
|
const name = attrs.match(/name\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[1]
|
|
476
476
|
?? attrs.match(/name\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[2]
|
|
477
477
|
?? ''
|
|
478
|
-
const id = attrs.match(/id\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[1]
|
|
479
|
-
?? attrs.match(/id\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[2]
|
|
480
|
-
?? ''
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
478
|
+
const id = attrs.match(/id\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[1]
|
|
479
|
+
?? attrs.match(/id\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[2]
|
|
480
|
+
?? ''
|
|
481
|
+
const content = attrs.match(/content\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[1]
|
|
482
|
+
?? attrs.match(/content\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[2]
|
|
483
|
+
?? ''
|
|
484
|
+
const data = attrs.match(/data\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[1]
|
|
485
|
+
?? attrs.match(/data\s*=\s*(?:"([^"]*)"|'([^']*)')/i)?.[2]
|
|
486
|
+
?? ''
|
|
487
|
+
const tag = String(match[1] ?? '').toLowerCase()
|
|
488
|
+
if (tag === 'at') {
|
|
489
|
+
result.push({ type: 'at', qq: id, text: name || id })
|
|
490
|
+
} else if (tag === 'quote') {
|
|
491
|
+
result.push({ type: 'reply', id })
|
|
492
|
+
} else if (tag === 'sharp') {
|
|
493
|
+
result.push({ type: 'text', text: name ? `#${name}` : `#${id}` })
|
|
494
|
+
} else if (tag === 'text') {
|
|
495
|
+
result.push({ type: 'text', text: content })
|
|
496
|
+
} else if (['img', 'image', 'face', 'mface', 'file', 'audio', 'video'].includes(tag)) {
|
|
497
|
+
const type = tag === 'file'
|
|
498
|
+
? 'file'
|
|
499
|
+
: tag === 'audio'
|
|
492
500
|
? 'record'
|
|
493
501
|
: tag === 'video'
|
|
494
502
|
? 'video'
|
|
@@ -497,9 +505,17 @@ function parsePreviewMarkup(source: string): MsgItemElem[] {
|
|
|
497
505
|
type,
|
|
498
506
|
file: src,
|
|
499
507
|
url: src,
|
|
500
|
-
...(name ? { name } : {}),
|
|
501
|
-
})
|
|
502
|
-
}
|
|
508
|
+
...(name ? { name } : {}),
|
|
509
|
+
})
|
|
510
|
+
} else {
|
|
511
|
+
result.push({
|
|
512
|
+
type: tag,
|
|
513
|
+
...(src ? { src, file: src, url: src } : {}),
|
|
514
|
+
...(name ? { name } : {}),
|
|
515
|
+
...(content ? { content } : {}),
|
|
516
|
+
...(data ? { data } : {}),
|
|
517
|
+
})
|
|
518
|
+
}
|
|
503
519
|
last = match.index + match[0].length
|
|
504
520
|
}
|
|
505
521
|
if (last < source.length) {
|
|
@@ -376,15 +376,29 @@
|
|
|
376
376
|
</div>
|
|
377
377
|
<!-- 消息发送框 -->
|
|
378
378
|
<div>
|
|
379
|
-
<div
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
<div class="send-tool-buttons">
|
|
380
|
+
<div class="send-tool-button"
|
|
381
|
+
v-menu.prevent="_=>moreFunClick()"
|
|
382
|
+
@click="moreFunClick(settingsStore.sysConfig.quick_send)">
|
|
383
|
+
<font-awesome-icon v-if="tags.showMoreDetail || details.find(item => item.open)" :icon="['fas', 'minus']" />
|
|
384
|
+
<font-awesome-icon v-else-if="settingsStore.sysConfig.quick_send == 'default'" :icon="['fas', 'plus']" />
|
|
385
|
+
<font-awesome-icon v-else-if="settingsStore.sysConfig.quick_send == 'img'" :icon="['fas', 'image']" />
|
|
386
|
+
<font-awesome-icon v-else-if="settingsStore.sysConfig.quick_send == 'file'" :icon="['fas', 'folder']" />
|
|
387
|
+
</div>
|
|
388
|
+
<div
|
|
389
|
+
class="send-tool-button custom-element-toggle"
|
|
390
|
+
:class="{ active: customElementMode }"
|
|
391
|
+
:title="$t('自定义元素')"
|
|
392
|
+
:aria-pressed="customElementMode"
|
|
393
|
+
role="button"
|
|
394
|
+
@click="toggleCustomElementMode">
|
|
395
|
+
<font-awesome-icon :icon="['fas', 'code']" />
|
|
396
|
+
</div>
|
|
385
397
|
</div>
|
|
386
398
|
<div>
|
|
387
|
-
<form
|
|
399
|
+
<form
|
|
400
|
+
:class="{ 'custom-element-form': customElementMode }"
|
|
401
|
+
@submit="mainSubmit">
|
|
388
402
|
<template v-if="pendingVoice">
|
|
389
403
|
<div id="voice-pending-input" class="voice-pending" role="button"
|
|
390
404
|
@click="cancelPendingVoice">
|
|
@@ -392,6 +406,51 @@
|
|
|
392
406
|
<font-awesome-icon :icon="['fas', 'xmark']" />
|
|
393
407
|
</div>
|
|
394
408
|
</template>
|
|
409
|
+
<template v-else-if="customElementMode">
|
|
410
|
+
<label for="custom-element-type" class="sr-only">{{ $t('元素类型') }}</label>
|
|
411
|
+
<input
|
|
412
|
+
id="custom-element-type"
|
|
413
|
+
ref="customElementTypeInput"
|
|
414
|
+
v-model="customElementType"
|
|
415
|
+
class="custom-element-type-input"
|
|
416
|
+
type="text"
|
|
417
|
+
autocomplete="off"
|
|
418
|
+
placeholder="text / img / file"
|
|
419
|
+
:disabled="uiStore.openSideBar || chat.info.me_info.shut_up_timestamp > 0">
|
|
420
|
+
<template v-if="settingsStore.sysConfig.send_key === 'none'">
|
|
421
|
+
<label for="main-input" class="sr-only">{{ $t('消息输入框') }}</label>
|
|
422
|
+
<input
|
|
423
|
+
id="main-input"
|
|
424
|
+
ref="mainInput"
|
|
425
|
+
v-model="msg"
|
|
426
|
+
class="custom-element-content-input"
|
|
427
|
+
type="text"
|
|
428
|
+
autocomplete="off"
|
|
429
|
+
placeholder="URL / content"
|
|
430
|
+
:disabled="uiStore.openSideBar || chat.info.me_info.shut_up_timestamp > 0"
|
|
431
|
+
@keydown="mainAtKey"
|
|
432
|
+
@keyup="mainKeyUp"
|
|
433
|
+
@click="selectSQIn"
|
|
434
|
+
@input="handleInput">
|
|
435
|
+
</template>
|
|
436
|
+
<template v-else>
|
|
437
|
+
<label for="main-input-ex" class="sr-only">{{ $t('消息输入框') }}</label>
|
|
438
|
+
<textarea
|
|
439
|
+
id="main-input-ex"
|
|
440
|
+
ref="mainInput"
|
|
441
|
+
v-model="msg"
|
|
442
|
+
class="custom-element-content-input"
|
|
443
|
+
type="text"
|
|
444
|
+
placeholder="URL / content"
|
|
445
|
+
:disabled="uiStore.openSideBar"
|
|
446
|
+
@keydown="mainKey"
|
|
447
|
+
@keyup="mainKeyUp"
|
|
448
|
+
@click="selectSQIn"
|
|
449
|
+
@input="handleInput"
|
|
450
|
+
@compositionstart="handleCompositionStart"
|
|
451
|
+
@compositionend="handleCompositionEnd" />
|
|
452
|
+
</template>
|
|
453
|
+
</template>
|
|
395
454
|
<template v-else>
|
|
396
455
|
<template v-if="settingsStore.sysConfig.send_key === 'none'">
|
|
397
456
|
<label for="main-input" class="sr-only">{{ $t('消息输入框') }}</label>
|
|
@@ -557,6 +616,7 @@
|
|
|
557
616
|
import app from '../main'
|
|
558
617
|
import { i18n } from '../main'
|
|
559
618
|
import SendUtil from '../function/sender'
|
|
619
|
+
import { buildCustomElement } from '../function/custom-element'
|
|
560
620
|
import Option, { get } from '../function/option'
|
|
561
621
|
import Info from '../pages/Info.vue'
|
|
562
622
|
import MsgBody from '../components/MsgBody.vue'
|
|
@@ -650,6 +710,7 @@ const msgPan = useTemplateRef<HTMLDivElement>('msgPan')
|
|
|
650
710
|
const chatPadding = useTemplateRef<HTMLSpanElement>('chatPadding')
|
|
651
711
|
const sendMore = useTemplateRef<HTMLDivElement>('sendMore')
|
|
652
712
|
const mainInput = useTemplateRef<HTMLInputElement | HTMLTextAreaElement>('mainInput')
|
|
713
|
+
const customElementTypeInput = useTemplateRef<HTMLInputElement>('customElementTypeInput')
|
|
653
714
|
|
|
654
715
|
type ForwardAction = 'single-message' | 'individual-messages' | 'merged-messages'
|
|
655
716
|
|
|
@@ -706,6 +767,8 @@ const details = ref([
|
|
|
706
767
|
const msgMenus = ref<any[]>([])
|
|
707
768
|
const NewMsgNum = ref(0)
|
|
708
769
|
const msg = ref('')
|
|
770
|
+
const customElementMode = ref(false)
|
|
771
|
+
const customElementType = ref('')
|
|
709
772
|
const oldMsg = ref('')
|
|
710
773
|
const imgCache = ref(new Map<number, string>())
|
|
711
774
|
const voiceRecording = ref(false)
|
|
@@ -842,6 +905,8 @@ const chatMoveOptions: VMoveOptions<HTMLDivElement> = {
|
|
|
842
905
|
|
|
843
906
|
function resetState() {
|
|
844
907
|
NewMsgNum.value = 0
|
|
908
|
+
customElementMode.value = false
|
|
909
|
+
customElementType.value = ''
|
|
845
910
|
tags.value = {
|
|
846
911
|
sendTag: 'REFUSE',
|
|
847
912
|
showBottomButton: false,
|
|
@@ -2871,6 +2936,44 @@ function sendMsg(echo = 'sendMsgBack') {
|
|
|
2871
2936
|
return
|
|
2872
2937
|
}
|
|
2873
2938
|
|
|
2939
|
+
if (customElementMode.value) {
|
|
2940
|
+
const customElement = buildCustomElement(
|
|
2941
|
+
customElementType.value,
|
|
2942
|
+
msg.value,
|
|
2943
|
+
)
|
|
2944
|
+
if (!customElement) {
|
|
2945
|
+
new PopInfo().add(PopType.ERR, $t('请输入元素类型和内容'))
|
|
2946
|
+
return
|
|
2947
|
+
}
|
|
2948
|
+
const parsedMsg = customElement.toString()
|
|
2949
|
+
if (chat.show.temp) {
|
|
2950
|
+
sendMsgRaw(
|
|
2951
|
+
chat.show.id + '/' + chat.show.temp,
|
|
2952
|
+
chat.show.type,
|
|
2953
|
+
parsedMsg,
|
|
2954
|
+
true,
|
|
2955
|
+
echo,
|
|
2956
|
+
)
|
|
2957
|
+
} else {
|
|
2958
|
+
sendMsgRaw(
|
|
2959
|
+
chat.show.id,
|
|
2960
|
+
chat.show.type,
|
|
2961
|
+
parsedMsg,
|
|
2962
|
+
true,
|
|
2963
|
+
echo,
|
|
2964
|
+
)
|
|
2965
|
+
}
|
|
2966
|
+
customElementType.value = ''
|
|
2967
|
+
msg.value = ''
|
|
2968
|
+
sendCache.value = []
|
|
2969
|
+
imgCache.value.clear()
|
|
2970
|
+
scrollBottom()
|
|
2971
|
+
cancelReply()
|
|
2972
|
+
scheduleResizeMainInput(undefined, true)
|
|
2973
|
+
nextTick(() => customElementTypeInput.value?.focus())
|
|
2974
|
+
return
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2874
2977
|
for (const [key, base64data] of imgCache.value) {
|
|
2875
2978
|
sendCache.value[key] = {
|
|
2876
2979
|
type: 'image',
|
|
@@ -3289,6 +3392,24 @@ function viewerEssImg(url: string) {
|
|
|
3289
3392
|
viewerRef.value.open(new Img(url))
|
|
3290
3393
|
}
|
|
3291
3394
|
|
|
3395
|
+
function toggleCustomElementMode() {
|
|
3396
|
+
if (pendingVoice.value) return
|
|
3397
|
+
customElementMode.value = !customElementMode.value
|
|
3398
|
+
details.value.forEach((item) => {
|
|
3399
|
+
item.open = false
|
|
3400
|
+
})
|
|
3401
|
+
tags.value.showMoreDetail = false
|
|
3402
|
+
voiceMenuShow.value = false
|
|
3403
|
+
tags.value.onAtFind = false
|
|
3404
|
+
nextTick(() => {
|
|
3405
|
+
const input = customElementMode.value
|
|
3406
|
+
? customElementTypeInput.value
|
|
3407
|
+
: mainInput.value
|
|
3408
|
+
input?.focus()
|
|
3409
|
+
scheduleResizeMainInput()
|
|
3410
|
+
})
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3292
3413
|
function moreFunClick(type = 'default') {
|
|
3293
3414
|
let hasOpen = false
|
|
3294
3415
|
details.value.forEach((item) => {
|
|
@@ -3386,6 +3507,14 @@ function exitWin() {
|
|
|
3386
3507
|
overflow: visible;
|
|
3387
3508
|
}
|
|
3388
3509
|
|
|
3510
|
+
.send-tool-button.custom-element-toggle.active {
|
|
3511
|
+
background: var(--color-main);
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
.send-tool-button.custom-element-toggle.active > svg {
|
|
3515
|
+
color: #fff;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3389
3518
|
.voice-menu-wrap {
|
|
3390
3519
|
position: relative;
|
|
3391
3520
|
width: 50px;
|
package/lib/index.js
CHANGED
|
@@ -7938,7 +7938,35 @@ var import_node_path3 = __toESM(require("node:path"));
|
|
|
7938
7938
|
var import_node_url2 = require("node:url");
|
|
7939
7939
|
var import_koa_send = __toESM(require_koa_send());
|
|
7940
7940
|
var import_file_type2 = __toESM(require_file_type());
|
|
7941
|
+
|
|
7942
|
+
// src/web-error-filter.ts
|
|
7943
|
+
function isIgnoredWebError(error) {
|
|
7944
|
+
if (error.name === "RangeError" && error.message === "Invalid string length") return true;
|
|
7945
|
+
return error.code === "ECONNRESET";
|
|
7946
|
+
}
|
|
7947
|
+
__name(isIgnoredWebError, "isIgnoredWebError");
|
|
7948
|
+
function registerWebErrorFilter(ctx) {
|
|
7949
|
+
const app = ctx.server._koa;
|
|
7950
|
+
const defaultHandler = app.onerror;
|
|
7951
|
+
const hasDefaultHandler = app.listeners("error").includes(defaultHandler);
|
|
7952
|
+
if (hasDefaultHandler) app.off("error", defaultHandler);
|
|
7953
|
+
const errorHandler = /* @__PURE__ */ __name((error) => {
|
|
7954
|
+
if (isIgnoredWebError(error)) return;
|
|
7955
|
+
if (hasDefaultHandler) defaultHandler.call(app, error);
|
|
7956
|
+
}, "errorHandler");
|
|
7957
|
+
app.on("error", errorHandler);
|
|
7958
|
+
ctx.on("dispose", () => {
|
|
7959
|
+
app.off("error", errorHandler);
|
|
7960
|
+
if (hasDefaultHandler && !app.listeners("error").includes(defaultHandler)) {
|
|
7961
|
+
app.on("error", defaultHandler);
|
|
7962
|
+
}
|
|
7963
|
+
});
|
|
7964
|
+
}
|
|
7965
|
+
__name(registerWebErrorFilter, "registerWebErrorFilter");
|
|
7966
|
+
|
|
7967
|
+
// src/web.ts
|
|
7941
7968
|
function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
7969
|
+
registerWebErrorFilter(ctx);
|
|
7942
7970
|
const webRoot = import_node_path3.default.resolve(__dirname, "..", "client", "web", "dist");
|
|
7943
7971
|
const webSource = import_node_path3.default.resolve(__dirname, "..", "client", "web", "src");
|
|
7944
7972
|
const webPublic = import_node_path3.default.resolve(__dirname, "..", "client", "web", "public");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-chat-patch",
|
|
3
3
|
"description": "[<ruby>chat-patch<rp>(</rp><rt>点我预览效果</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/openplatform/71074dfc9e5256fc3333d8bd8478bec1af874046.png) 视奸小插件((bushi( (低性能警告)。手机端适配。灵感来自 chat 插件。",
|
|
4
|
-
"version": "6.1.
|
|
4
|
+
"version": "6.1.1",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build:web": "npm --prefix client/web run build",
|
|
7
7
|
"prepack": "npm run build:web",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context } from 'koishi'
|
|
2
|
+
import {} from '@koishijs/plugin-server'
|
|
3
|
+
|
|
4
|
+
type KoaErrorListener = (error: Error) => void
|
|
5
|
+
|
|
6
|
+
// 仅过滤客户端断开和超大历史响应导致的已知错误。
|
|
7
|
+
function isIgnoredWebError(error: Error): boolean {
|
|
8
|
+
if (error.name === 'RangeError' && error.message === 'Invalid string length') return true
|
|
9
|
+
return (error as NodeJS.ErrnoException).code === 'ECONNRESET'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerWebErrorFilter(ctx: Context) {
|
|
13
|
+
const app = ctx.server._koa
|
|
14
|
+
const defaultHandler = app.onerror
|
|
15
|
+
const hasDefaultHandler = app.listeners('error').includes(defaultHandler)
|
|
16
|
+
if (hasDefaultHandler) app.off('error', defaultHandler)
|
|
17
|
+
|
|
18
|
+
const errorHandler: KoaErrorListener = (error) => {
|
|
19
|
+
if (isIgnoredWebError(error)) return
|
|
20
|
+
if (hasDefaultHandler) defaultHandler.call(app, error)
|
|
21
|
+
}
|
|
22
|
+
app.on('error', errorHandler)
|
|
23
|
+
|
|
24
|
+
ctx.on('dispose', () => {
|
|
25
|
+
app.off('error', errorHandler)
|
|
26
|
+
if (hasDefaultHandler && !app.listeners('error').includes(defaultHandler)) {
|
|
27
|
+
app.on('error', defaultHandler)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
package/src/web.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { ChatDatabase } from './database'
|
|
|
15
15
|
import { MediaManager } from './media'
|
|
16
16
|
import { PluginLogger } from './logger'
|
|
17
17
|
import { ContactCacheItem, MessageRecord, SelfMessagePayload, SelfMessageRecord } from './types'
|
|
18
|
+
import { registerWebErrorFilter } from './web-error-filter'
|
|
18
19
|
|
|
19
20
|
interface ViteConsoleServer {
|
|
20
21
|
config?: {
|
|
@@ -37,7 +38,8 @@ export function registerWeb(
|
|
|
37
38
|
media: MediaManager,
|
|
38
39
|
logger: PluginLogger,
|
|
39
40
|
) {
|
|
40
|
-
|
|
41
|
+
registerWebErrorFilter(ctx)
|
|
42
|
+
const webRoot = path.resolve(__dirname, '..', 'client', 'web', 'dist')
|
|
41
43
|
const webSource = path.resolve(__dirname, '..', 'client', 'web', 'src')
|
|
42
44
|
const webPublic = path.resolve(__dirname, '..', 'client', 'web', 'public')
|
|
43
45
|
const webIndex = path.resolve(__dirname, '..', 'client', 'web', 'index.html')
|