@koishi-ce/client 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/app/home/home.vue +4 -0
- package/app/home/index.ts +4 -0
- package/app/home/welcome.en-US.yml +4 -0
- package/app/home/welcome.vue +16 -6
- package/app/home/welcome.zh-CN.yml +4 -0
- package/app/index.scss +4 -0
- package/app/index.ts +12 -2
- package/app/layout/header.vue +16 -4
- package/app/layout/index.ts +4 -0
- package/app/layout/layout.vue +8 -1
- package/app/layout/menu-item.vue +7 -1
- package/app/settings/index.ts +4 -0
- package/app/settings/settings.vue +10 -4
- package/app/settings/theme.vue +8 -1
- package/app/shims.d.ts +3 -0
- package/app/status/index.ts +4 -0
- package/app/status/loading.vue +8 -1
- package/app/status/status.vue +4 -0
- package/app/styles/element.scss +4 -0
- package/app/styles/hc.scss +4 -0
- package/app/styles/index.scss +4 -0
- package/app/styles/index.ts +4 -0
- package/app/styles/layout.scss +4 -0
- package/app/theme/activity/button.vue +8 -1
- package/app/theme/activity/index.vue +16 -4
- package/app/theme/activity/item.vue +22 -4
- package/app/theme/activity/separator.vue +48 -12
- package/app/theme/activity/utils.ts +4 -0
- package/app/theme/blank.vue +4 -0
- package/app/theme/index.ts +9 -1
- package/app/theme/index.vue +7 -1
- package/app/theme/menu/index.vue +4 -0
- package/app/theme/menu/menu-item.vue +14 -4
- package/app/theme/menu/menu.vue +13 -4
- package/app/theme/status.vue +4 -0
- package/client/components/chat/image.vue +4 -0
- package/client/components/chat/overlay.vue +34 -8
- package/client/components/chat/utils.ts +4 -0
- package/client/components/common/index.ts +4 -0
- package/client/components/common/k-button.vue +4 -0
- package/client/components/common/k-hint.vue +7 -1
- package/client/components/common/k-tab.vue +4 -0
- package/client/components/dynamic.vue +6 -1
- package/client/components/icons/activity/default.vue +4 -0
- package/client/components/icons/activity/ellipsis.vue +4 -0
- package/client/components/icons/activity/home.vue +4 -0
- package/client/components/icons/activity/moon.vue +4 -0
- package/client/components/icons/activity/settings.vue +4 -0
- package/client/components/icons/activity/sun.vue +4 -0
- package/client/components/icons/index.ts +11 -2
- package/client/components/icons/style.scss +4 -0
- package/client/components/icons/svg/arrow-left.vue +4 -0
- package/client/components/icons/svg/arrow-right.vue +4 -0
- package/client/components/icons/svg/box-open.vue +4 -0
- package/client/components/icons/svg/check-full.vue +4 -0
- package/client/components/icons/svg/chevron-down.vue +4 -0
- package/client/components/icons/svg/chevron-left.vue +4 -0
- package/client/components/icons/svg/chevron-right.vue +4 -0
- package/client/components/icons/svg/chevron-up.vue +4 -0
- package/client/components/icons/svg/clipboard-list.vue +4 -0
- package/client/components/icons/svg/edit.vue +4 -0
- package/client/components/icons/svg/exclamation-full.vue +4 -0
- package/client/components/icons/svg/expand.vue +4 -0
- package/client/components/icons/svg/file-archive.vue +4 -0
- package/client/components/icons/svg/filter.vue +4 -0
- package/client/components/icons/svg/github.vue +4 -0
- package/client/components/icons/svg/gitlab.vue +4 -0
- package/client/components/icons/svg/info-full.vue +4 -0
- package/client/components/icons/svg/koishi.vue +4 -0
- package/client/components/icons/svg/link.vue +4 -0
- package/client/components/icons/svg/paper-plane.vue +4 -0
- package/client/components/icons/svg/question-empty.vue +4 -0
- package/client/components/icons/svg/redo.vue +4 -0
- package/client/components/icons/svg/search-minus.vue +4 -0
- package/client/components/icons/svg/search-plus.vue +4 -0
- package/client/components/icons/svg/search.vue +4 -0
- package/client/components/icons/svg/star-empty.vue +4 -0
- package/client/components/icons/svg/star-full.vue +4 -0
- package/client/components/icons/svg/start.vue +4 -0
- package/client/components/icons/svg/tag.vue +4 -0
- package/client/components/icons/svg/times-full.vue +4 -0
- package/client/components/icons/svg/tools.vue +4 -0
- package/client/components/icons/svg/undo.vue +4 -0
- package/client/components/icons/svg/user.vue +4 -0
- package/client/components/index.ts +12 -2
- package/client/components/layout/card.vue +4 -0
- package/client/components/layout/content.vue +4 -0
- package/client/components/layout/empty.vue +4 -0
- package/client/components/layout/index.ts +4 -0
- package/client/components/layout/tab-group.vue +4 -0
- package/client/components/layout/tab-item.vue +4 -0
- package/client/components/link.ts +9 -2
- package/client/components/perms.vue +26 -5
- package/client/components/slot.ts +23 -4
- package/client/context.ts +16 -4
- package/client/data.ts +61 -26
- package/client/index.ts +6 -1
- package/client/plugins/action.ts +50 -13
- package/client/plugins/i18n.ts +4 -0
- package/client/plugins/loader.ts +71 -45
- package/client/plugins/router.ts +38 -11
- package/client/plugins/setting.ts +49 -12
- package/client/plugins/theme.ts +25 -6
- package/client/shims.d.ts +3 -0
- package/client/utils.ts +11 -2
- package/global.d.ts +4 -0
- package/lib/bin.mjs +7 -3
- package/lib/{client-CaTn_gVG.mjs → client-C97iYPCr.mjs} +1 -1
- package/lib/index.mjs +1 -1
- package/lib/{src-lTfcJ1xA.mjs → src-BOakssV3.mjs} +8 -2
- package/package.json +6 -2
- package/src/bin.ts +13 -3
- package/src/index.ts +72 -14
- package/src/yaml.ts +3 -0
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 实心五角星图标(注册名 "star-full"),用于已收藏/已订阅状态 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 播放(三角)图标(注册名 "start"),用于启动/运行 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 标签图标(注册名 "tag"),用于标签/标记 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 实心圆形叉号图标(注册名 "times-full"),用于失败/关闭状态 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 扳手与螺丝刀工具图标(注册名 "tools"),用于维护/调试功能 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 逆时针旋转箭头图标(注册名 "undo"),用于向左旋转/撤销 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!-- 用户人形图标(注册名 "user"),用于用户/登录相关界面 -->
|
|
2
6
|
<template>
|
|
3
7
|
<svg class="k-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* 内置组件库的总装模块:
|
|
3
7
|
*
|
|
@@ -11,10 +15,16 @@
|
|
|
11
15
|
// 无法解析(无 paths),故直接相对导入工作区内该包的浏览器端入口,
|
|
12
16
|
// 与构建器 collectWorkspaceAliases 的映射目标一致
|
|
13
17
|
|
|
14
|
-
import Element, {
|
|
18
|
+
import Element, {
|
|
19
|
+
ElLoading,
|
|
20
|
+
ElMessage,
|
|
21
|
+
ElMessageBox,
|
|
22
|
+
} from "element-plus";
|
|
15
23
|
import Markdown from "marked-vue";
|
|
16
24
|
import type { App } from "vue";
|
|
17
|
-
import components, {
|
|
25
|
+
import components, {
|
|
26
|
+
SchemaBase,
|
|
27
|
+
} from "../../../components/client/index.ts";
|
|
18
28
|
|
|
19
29
|
import { store } from "../data";
|
|
20
30
|
import ChatImage from "./chat/image.vue";
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
import type { App } from "vue";
|
|
2
6
|
import Card from "./card.vue";
|
|
3
7
|
import Content from "./content.vue";
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
k-tab-item:选项卡条目(配合 k-tab-group 使用)。
|
|
3
7
|
label 为条目标识兼默认文案,点击时 emit update:modelValue;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
import { type App, defineComponent, h } from "vue";
|
|
2
6
|
import { RouterLink } from "vue-router";
|
|
3
7
|
import { useContext } from "../context";
|
|
@@ -17,7 +21,9 @@ const KActivityLink = defineComponent({
|
|
|
17
21
|
setup(props, { slots }) {
|
|
18
22
|
const ctx = useContext();
|
|
19
23
|
return () => {
|
|
20
|
-
const activity = props.id
|
|
24
|
+
const activity = props.id
|
|
25
|
+
? ctx.$router.pages[props.id]
|
|
26
|
+
: undefined;
|
|
21
27
|
return h(
|
|
22
28
|
RouterLink,
|
|
23
29
|
{
|
|
@@ -26,7 +32,8 @@ const KActivityLink = defineComponent({
|
|
|
26
32
|
activity?.path.replace(/:.+/, "")) as string,
|
|
27
33
|
},
|
|
28
34
|
{
|
|
29
|
-
default: () =>
|
|
35
|
+
default: () =>
|
|
36
|
+
slots["default"]?.() ?? activity?.name,
|
|
30
37
|
},
|
|
31
38
|
);
|
|
32
39
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
perms.vue:权限选择控件(array + perms 角色)。
|
|
3
7
|
把服务端下发的扁平权限名(如 "channel.xxx")按 ":" 逐级拆分,
|
|
@@ -28,7 +32,10 @@
|
|
|
28
32
|
// schemastery-vue 链接(它是 components 的依赖,Bun 隔离布局下不跨包可见)
|
|
29
33
|
|
|
30
34
|
import { store } from "@koishi-ce/client";
|
|
31
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
type Schema,
|
|
37
|
+
SchemaBase,
|
|
38
|
+
} from "@koishi-ce/components";
|
|
32
39
|
import type { CascaderOption } from "element-plus";
|
|
33
40
|
import { computed, type PropType } from "vue";
|
|
34
41
|
|
|
@@ -49,15 +56,29 @@ const config = SchemaBase.useModel();
|
|
|
49
56
|
* 逐段查找/创建级联节点;中间段(还有下级时)标记为 disabled,
|
|
50
57
|
* 因为 el-cascader 的父节点勾选会影响子级,而中间段并非真实权限。
|
|
51
58
|
*/
|
|
52
|
-
function addNode(
|
|
59
|
+
function addNode(
|
|
60
|
+
nodes: CascaderOption[],
|
|
61
|
+
path: string[],
|
|
62
|
+
prefix = "",
|
|
63
|
+
) {
|
|
53
64
|
const name = path.shift();
|
|
54
|
-
let node = nodes.find(
|
|
65
|
+
let node = nodes.find(
|
|
66
|
+
(node) => node.value === prefix + name,
|
|
67
|
+
);
|
|
55
68
|
if (!node) {
|
|
56
|
-
node = {
|
|
69
|
+
node = {
|
|
70
|
+
value: prefix + name,
|
|
71
|
+
label: name,
|
|
72
|
+
disabled: !!path.length,
|
|
73
|
+
};
|
|
57
74
|
nodes.push(node);
|
|
58
75
|
}
|
|
59
76
|
if (!path.length) return;
|
|
60
|
-
addNode(
|
|
77
|
+
addNode(
|
|
78
|
+
(node.children ||= []),
|
|
79
|
+
path,
|
|
80
|
+
`${prefix + name}:`,
|
|
81
|
+
);
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
// 由全部权限名构建级联选项树
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type App,
|
|
7
|
+
type Component,
|
|
8
|
+
defineComponent,
|
|
9
|
+
h,
|
|
10
|
+
} from "vue";
|
|
2
11
|
import { useContext } from "../context";
|
|
3
12
|
|
|
4
13
|
/** 插槽条目的基础形状:参与排序的组件 */
|
|
@@ -43,10 +52,15 @@ export const KSlot = defineComponent({
|
|
|
43
52
|
? []
|
|
44
53
|
: [...(slots["default"]?.() || [])]
|
|
45
54
|
.filter((node) => node.type === KSlotItem)
|
|
46
|
-
.map((node) => ({
|
|
55
|
+
.map((node) => ({
|
|
56
|
+
node,
|
|
57
|
+
order: node.props?.["order"] || 0,
|
|
58
|
+
}));
|
|
47
59
|
// external:ctx.slot() 注册的视图,disabled 的不渲染
|
|
48
60
|
const external = [
|
|
49
|
-
...(props.name
|
|
61
|
+
...(props.name
|
|
62
|
+
? ctx.$router.views[props.name] || []
|
|
63
|
+
: []),
|
|
50
64
|
]
|
|
51
65
|
.filter((item) => !item.disabled?.())
|
|
52
66
|
.map((item) => ({
|
|
@@ -81,7 +95,12 @@ function defineSlotComponent(name: string) {
|
|
|
81
95
|
return defineComponent({
|
|
82
96
|
inheritAttrs: false,
|
|
83
97
|
setup(_, { slots, attrs }) {
|
|
84
|
-
return () =>
|
|
98
|
+
return () =>
|
|
99
|
+
h(
|
|
100
|
+
KSlot,
|
|
101
|
+
{ name, data: attrs, single: true },
|
|
102
|
+
slots,
|
|
103
|
+
);
|
|
85
104
|
},
|
|
86
105
|
});
|
|
87
106
|
}
|
package/client/context.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
import * as cordis from "cordis";
|
|
2
6
|
import {
|
|
3
7
|
type App,
|
|
@@ -22,7 +26,8 @@ import ThemeService from "./plugins/theme";
|
|
|
22
26
|
// —— 布局相关的 Context 类型扩展 ——
|
|
23
27
|
|
|
24
28
|
/** 前端侧事件表:在 cordis 事件的基础上扩展本库自定义事件 */
|
|
25
|
-
export interface Events<C extends Context = Context>
|
|
29
|
+
export interface Events<C extends Context = Context>
|
|
30
|
+
extends cordis.Events<C> {}
|
|
26
31
|
|
|
27
32
|
/**
|
|
28
33
|
* 前端 Context 接口:各服务插件通过 `declare module "../context"`
|
|
@@ -80,7 +85,10 @@ export class Context extends cordis.Context {
|
|
|
80
85
|
this.app = createApp(
|
|
81
86
|
defineComponent({
|
|
82
87
|
setup: () => () => [
|
|
83
|
-
h(resolveComponent("k-slot"), {
|
|
88
|
+
h(resolveComponent("k-slot"), {
|
|
89
|
+
name: "root",
|
|
90
|
+
single: true,
|
|
91
|
+
}),
|
|
84
92
|
h(resolveComponent("k-slot"), { name: "global" }),
|
|
85
93
|
],
|
|
86
94
|
}),
|
|
@@ -111,12 +119,16 @@ export class Context extends cordis.Context {
|
|
|
111
119
|
*/
|
|
112
120
|
addEventListener<K extends keyof WindowEventMap>(
|
|
113
121
|
type: K,
|
|
114
|
-
listener: (
|
|
122
|
+
listener: (
|
|
123
|
+
this: Window,
|
|
124
|
+
ev: WindowEventMap[K],
|
|
125
|
+
) => unknown,
|
|
115
126
|
options?: boolean | AddEventListenerOptions,
|
|
116
127
|
) {
|
|
117
128
|
return this.effect(() => {
|
|
118
129
|
window.addEventListener(type, listener, options);
|
|
119
|
-
return () =>
|
|
130
|
+
return () =>
|
|
131
|
+
window.removeEventListener(type, listener, options);
|
|
120
132
|
});
|
|
121
133
|
}
|
|
122
134
|
|
package/client/data.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
/// <reference path="./shims.d.ts" />
|
|
2
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
Promisify,
|
|
8
|
+
Universal,
|
|
9
|
+
} from "@koishi-ce/koishi";
|
|
3
10
|
import type {
|
|
4
11
|
ClientConfig,
|
|
5
12
|
Console,
|
|
@@ -39,7 +46,8 @@ export function withProxy(url: string) {
|
|
|
39
46
|
/** 当前与服务端的 WebSocket 连接(未连接时为 null) */
|
|
40
47
|
export const socket = ref<Universal.WebSocket | null>(null);
|
|
41
48
|
/** 按事件名登记的推送监听器(receive 注册) */
|
|
42
|
-
const listeners: Record<string, (data: unknown) => void> =
|
|
49
|
+
const listeners: Record<string, (data: unknown) => void> =
|
|
50
|
+
{};
|
|
43
51
|
/** 按报文 id 暂存的 RPC 应答钩子 [resolve, reject](send 写入) */
|
|
44
52
|
const responseHooks: Record<
|
|
45
53
|
string,
|
|
@@ -89,19 +97,26 @@ type StoreValue = Store[keyof Store];
|
|
|
89
97
|
|
|
90
98
|
// 服务端整表推送:直接覆盖 store 中对应键
|
|
91
99
|
// (key 为联合类型时直写 store[key] 会被要求交叉类型,故经 Record 视图写入)
|
|
92
|
-
receive<{ key: keyof Store; value: unknown }>(
|
|
93
|
-
|
|
94
|
-
})
|
|
100
|
+
receive<{ key: keyof Store; value: unknown }>(
|
|
101
|
+
"data",
|
|
102
|
+
({ key, value }) => {
|
|
103
|
+
(store as Record<keyof Store, StoreValue>)[key] =
|
|
104
|
+
value as StoreValue;
|
|
105
|
+
},
|
|
106
|
+
);
|
|
95
107
|
|
|
96
108
|
// 服务端增量推送:数组做追加,对象做浅合并
|
|
97
|
-
receive<{ key: keyof Store; value: unknown }>(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
receive<{ key: keyof Store; value: unknown }>(
|
|
110
|
+
"patch",
|
|
111
|
+
({ key, value }) => {
|
|
112
|
+
const current = store[key];
|
|
113
|
+
if (Array.isArray(current)) {
|
|
114
|
+
(current as unknown[]).push(...(value as unknown[]));
|
|
115
|
+
} else if (current) {
|
|
116
|
+
Object.assign(current, value);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
);
|
|
105
120
|
|
|
106
121
|
// RPC 应答分发:按报文 id 找到 send() 留下的 resolve/reject 并结算
|
|
107
122
|
receive<{ id: string; value?: unknown; error?: unknown }>(
|
|
@@ -127,7 +142,10 @@ receive<{ id: string; value?: unknown; error?: unknown }>(
|
|
|
127
142
|
* @param ctx 根 Context,用于把消息转发为 cordis 事件
|
|
128
143
|
* @param callback 创建 WebSocket 实例的工厂函数
|
|
129
144
|
*/
|
|
130
|
-
export function connect(
|
|
145
|
+
export function connect(
|
|
146
|
+
ctx: Context,
|
|
147
|
+
callback: () => Universal.WebSocket,
|
|
148
|
+
) {
|
|
131
149
|
const value = callback();
|
|
132
150
|
|
|
133
151
|
let sendTimer: number;
|
|
@@ -138,8 +156,14 @@ export function connect(ctx: Context, callback: () => Universal.WebSocket) {
|
|
|
138
156
|
if (!global.heartbeat) return;
|
|
139
157
|
clearTimeout(sendTimer);
|
|
140
158
|
clearTimeout(closeTimer);
|
|
141
|
-
sendTimer = +setTimeout(
|
|
142
|
-
|
|
159
|
+
sendTimer = +setTimeout(
|
|
160
|
+
() => send("ping"),
|
|
161
|
+
global.heartbeat.interval,
|
|
162
|
+
);
|
|
163
|
+
closeTimer = +setTimeout(
|
|
164
|
+
() => value?.close(),
|
|
165
|
+
global.heartbeat.timeout,
|
|
166
|
+
);
|
|
143
167
|
};
|
|
144
168
|
|
|
145
169
|
const reconnect = () => {
|
|
@@ -148,10 +172,14 @@ export function connect(ctx: Context, callback: () => Universal.WebSocket) {
|
|
|
148
172
|
for (const key in store) {
|
|
149
173
|
(store as Record<string, unknown>)[key] = undefined;
|
|
150
174
|
}
|
|
151
|
-
console.log(
|
|
175
|
+
console.log(
|
|
176
|
+
"[koishi] websocket disconnected, will retry in 1s...",
|
|
177
|
+
);
|
|
152
178
|
setTimeout(() => {
|
|
153
179
|
connect(ctx, callback).then(location.reload, () => {
|
|
154
|
-
console.log(
|
|
180
|
+
console.log(
|
|
181
|
+
"[koishi] websocket disconnected, will retry in 1s...",
|
|
182
|
+
);
|
|
155
183
|
});
|
|
156
184
|
}, 1000);
|
|
157
185
|
};
|
|
@@ -159,7 +187,12 @@ export function connect(ctx: Context, callback: () => Universal.WebSocket) {
|
|
|
159
187
|
value.addEventListener("message", (ev) => {
|
|
160
188
|
refresh();
|
|
161
189
|
const data = JSON.parse(ev.data);
|
|
162
|
-
console.debug(
|
|
190
|
+
console.debug(
|
|
191
|
+
"↓%c",
|
|
192
|
+
"color:purple",
|
|
193
|
+
data.type,
|
|
194
|
+
data.body,
|
|
195
|
+
);
|
|
163
196
|
if (data.type in listeners) {
|
|
164
197
|
listeners[data.type]?.(data.body);
|
|
165
198
|
}
|
|
@@ -168,11 +201,13 @@ export function connect(ctx: Context, callback: () => Universal.WebSocket) {
|
|
|
168
201
|
|
|
169
202
|
value.addEventListener("close", reconnect);
|
|
170
203
|
|
|
171
|
-
return new Promise<Universal.WebSocket.Event>(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
204
|
+
return new Promise<Universal.WebSocket.Event>(
|
|
205
|
+
(resolve, reject) => {
|
|
206
|
+
value.addEventListener("open", (event) => {
|
|
207
|
+
socket.value = markRaw(value);
|
|
208
|
+
resolve(event);
|
|
209
|
+
});
|
|
210
|
+
value.addEventListener("error", reject);
|
|
211
|
+
},
|
|
212
|
+
);
|
|
178
213
|
}
|
package/client/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* @koishi_ce/client 包的主入口(Koishi 控制台前端组件库)。
|
|
3
7
|
*
|
|
@@ -33,7 +37,8 @@ export const ScopeStatus = {
|
|
|
33
37
|
DISPOSED: 4,
|
|
34
38
|
} as const;
|
|
35
39
|
|
|
36
|
-
export type ScopeStatus =
|
|
40
|
+
export type ScopeStatus =
|
|
41
|
+
(typeof ScopeStatus)[keyof typeof ScopeStatus];
|
|
37
42
|
export * from "./components";
|
|
38
43
|
export * from "./context";
|
|
39
44
|
export * from "./data";
|
package/client/plugins/action.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* 动作(action)与菜单服务:控制台的快捷键、右键菜单、
|
|
3
7
|
* 作用域变量的统一管理。
|
|
@@ -7,7 +11,11 @@
|
|
|
7
11
|
* 绑定右键菜单。作用域变量支持 "a.b" 点分键,经 Flatten 类型
|
|
8
12
|
* 与运行时代理呈现为嵌套对象。
|
|
9
13
|
*/
|
|
10
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type Dict,
|
|
16
|
+
type Intersect,
|
|
17
|
+
remove,
|
|
18
|
+
} from "cosmokit";
|
|
11
19
|
import {
|
|
12
20
|
type MaybeRefOrGetter,
|
|
13
21
|
markRaw,
|
|
@@ -54,7 +62,8 @@ export interface ActionOptions {
|
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/** 旧版菜单项写法(字段与 ActionOptions 部分重叠),仅为兼容保留 */
|
|
57
|
-
export type LegacyMenuItem = Partial<ActionOptions> &
|
|
65
|
+
export type LegacyMenuItem = Partial<ActionOptions> &
|
|
66
|
+
Omit<MenuItem, "id">;
|
|
58
67
|
|
|
59
68
|
/** 菜单项:label / type / icon 均支持写成随作用域变化的 getter */
|
|
60
69
|
export interface MenuItem {
|
|
@@ -66,10 +75,14 @@ export interface MenuItem {
|
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
/** 值或(以作用域为参数的)取值函数 */
|
|
69
|
-
export type MaybeGetter<T> =
|
|
78
|
+
export type MaybeGetter<T> =
|
|
79
|
+
| T
|
|
80
|
+
| ((scope: Flatten<ActionContext>) => T);
|
|
70
81
|
|
|
71
82
|
/** 作用域存储:扁平键到「值或 ref 或 getter」的映射 */
|
|
72
|
-
type Store<S extends {}> = {
|
|
83
|
+
type Store<S extends {}> = {
|
|
84
|
+
[K in keyof S]?: MaybeRefOrGetter<S[K]>;
|
|
85
|
+
};
|
|
73
86
|
|
|
74
87
|
/**
|
|
75
88
|
* 把 "a.b.c" 形式的扁平键名归并为 { a: { b: { c } } } 的嵌套结构,
|
|
@@ -99,9 +112,14 @@ export interface ActiveMenu {
|
|
|
99
112
|
* 触发时把附加值写入作用域,并在鼠标位置弹出自定义菜单(由 global 插槽渲染)。
|
|
100
113
|
* @param id 菜单标识,menu() 注册的条目将展示在该菜单中
|
|
101
114
|
*/
|
|
102
|
-
export function useMenu<K extends keyof ActionContext>(
|
|
115
|
+
export function useMenu<K extends keyof ActionContext>(
|
|
116
|
+
id: K,
|
|
117
|
+
) {
|
|
103
118
|
const ctx = useContext();
|
|
104
|
-
return (
|
|
119
|
+
return (
|
|
120
|
+
event: MouseEvent,
|
|
121
|
+
value: MaybeRefOrGetter<ActionContext[K]>,
|
|
122
|
+
) => {
|
|
105
123
|
ctx.define(id, value);
|
|
106
124
|
event.preventDefault();
|
|
107
125
|
const { clientX, clientY } = event;
|
|
@@ -129,7 +147,9 @@ export default class ActionService extends Service {
|
|
|
129
147
|
super(ctx, "$action", true);
|
|
130
148
|
ctx.mixin("$action", ["action", "menu", "define"]);
|
|
131
149
|
|
|
132
|
-
ctx.internal.scope = shallowReactive(
|
|
150
|
+
ctx.internal.scope = shallowReactive(
|
|
151
|
+
{} as Store<ActionContext>,
|
|
152
|
+
);
|
|
133
153
|
ctx.internal.menus = reactive({});
|
|
134
154
|
ctx.internal.actions = reactive({});
|
|
135
155
|
ctx.internal.activeMenus = reactive([]);
|
|
@@ -138,7 +158,9 @@ export default class ActionService extends Service {
|
|
|
138
158
|
// 与键盘事件逐项比对(ctrl 在 macOS 平台映射为 meta 键)
|
|
139
159
|
ctx.addEventListener("keydown", (event) => {
|
|
140
160
|
const scope = this.createScope();
|
|
141
|
-
for (const action of Object.values(
|
|
161
|
+
for (const action of Object.values(
|
|
162
|
+
ctx.internal.actions,
|
|
163
|
+
)) {
|
|
142
164
|
if (!action.shortcut) continue;
|
|
143
165
|
const keys = action.shortcut
|
|
144
166
|
.split("+")
|
|
@@ -154,7 +176,11 @@ export default class ActionService extends Service {
|
|
|
154
176
|
continue;
|
|
155
177
|
case "ctrl":
|
|
156
178
|
// macOS 没有 ctrl+字母 的快捷键惯例,改用 Cmd(meta)
|
|
157
|
-
if (
|
|
179
|
+
if (
|
|
180
|
+
navigator.platform
|
|
181
|
+
.toLowerCase()
|
|
182
|
+
.includes("mac")
|
|
183
|
+
) {
|
|
158
184
|
metaKey = true;
|
|
159
185
|
} else {
|
|
160
186
|
ctrlKey = true;
|
|
@@ -191,7 +217,8 @@ export default class ActionService extends Service {
|
|
|
191
217
|
items.forEach((item) => insert(list, item));
|
|
192
218
|
return () => {
|
|
193
219
|
items.forEach((item) => remove(list, item));
|
|
194
|
-
if (!list.length)
|
|
220
|
+
if (!list.length)
|
|
221
|
+
delete this.ctx.internal.menus[id];
|
|
195
222
|
};
|
|
196
223
|
});
|
|
197
224
|
}
|
|
@@ -218,7 +245,10 @@ export default class ActionService extends Service {
|
|
|
218
245
|
* 可用 override 临时覆盖若干键值。
|
|
219
246
|
*/
|
|
220
247
|
createScope(override = {}) {
|
|
221
|
-
const scope = {
|
|
248
|
+
const scope = {
|
|
249
|
+
...this.ctx.internal.scope,
|
|
250
|
+
...override,
|
|
251
|
+
};
|
|
222
252
|
return createScope(scope);
|
|
223
253
|
}
|
|
224
254
|
}
|
|
@@ -237,11 +267,18 @@ function createScope(
|
|
|
237
267
|
if (typeof key === "symbol")
|
|
238
268
|
return (target as Record<symbol, unknown>)[key];
|
|
239
269
|
key = prefix + key;
|
|
240
|
-
const source = scope as Record<
|
|
270
|
+
const source = scope as Record<
|
|
271
|
+
string,
|
|
272
|
+
MaybeRefOrGetter<unknown>
|
|
273
|
+
>;
|
|
241
274
|
if (key in scope) return toValue(source[key]);
|
|
242
275
|
// 键本身不存在,但存在以其为前缀的子键:返回子级作用域代理
|
|
243
276
|
const _prefix = `${key}.`;
|
|
244
|
-
if (
|
|
277
|
+
if (
|
|
278
|
+
Object.keys(scope).some((k) =>
|
|
279
|
+
k.startsWith(_prefix),
|
|
280
|
+
)
|
|
281
|
+
) {
|
|
245
282
|
return createScope(scope, `${key}.`);
|
|
246
283
|
}
|
|
247
284
|
// 完全未命中:显式返回 undefined(noImplicitReturns)
|
package/client/plugins/i18n.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
import { watchEffect } from "vue";
|
|
2
6
|
import { createI18n } from "vue-i18n";
|
|
3
7
|
import type { Context } from "../context";
|