@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
package/client/plugins/loader.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 type { EntryData } from "@koishi-ce/plugin-console";
|
|
2
6
|
import type { EffectScope, Plugin } from "cordis";
|
|
3
7
|
import type { Dict } from "cosmokit";
|
|
@@ -25,12 +29,17 @@ export function defineExtension(callback: Extension) {
|
|
|
25
29
|
|
|
26
30
|
/** 兼容 rollup 与 vite 两种产物:优先取 default 导出 */
|
|
27
31
|
export function unwrapExports(module: unknown): Plugin {
|
|
28
|
-
const record = module as
|
|
32
|
+
const record = module as
|
|
33
|
+
| { default?: unknown }
|
|
34
|
+
| null
|
|
35
|
+
| undefined;
|
|
29
36
|
return (record?.default || module) as Plugin;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
/** 按文件后缀分发的加载器:样式表插入 <link>,其余按动态模块导入 */
|
|
33
|
-
const loaders: Dict<
|
|
40
|
+
const loaders: Dict<
|
|
41
|
+
(ctx: Context, url: string) => Promise<void>
|
|
42
|
+
> = {
|
|
34
43
|
async [`.css`](ctx, url) {
|
|
35
44
|
const link = document.createElement("link");
|
|
36
45
|
link.rel = "stylesheet";
|
|
@@ -49,7 +58,10 @@ const loaders: Dict<(ctx: Context, url: string) => Promise<void>> = {
|
|
|
49
58
|
async [``](ctx, url) {
|
|
50
59
|
const exports = await import(/* @vite-ignore */ url);
|
|
51
60
|
// 扩展入口均为函数式插件;断言到 Function 形状以走 config 可选的重载
|
|
52
|
-
ctx.plugin(
|
|
61
|
+
ctx.plugin(
|
|
62
|
+
unwrapExports(exports) as Plugin.Function,
|
|
63
|
+
ctx.extension?.data,
|
|
64
|
+
);
|
|
53
65
|
},
|
|
54
66
|
};
|
|
55
67
|
|
|
@@ -73,20 +85,25 @@ export default class LoaderService extends Service {
|
|
|
73
85
|
private backendId: string | undefined;
|
|
74
86
|
|
|
75
87
|
/** 当前已加载扩展的 id → LoadResult 映射 */
|
|
76
|
-
public extensions: Dict<LoadResult> = shallowReactive(
|
|
88
|
+
public extensions: Dict<LoadResult> = shallowReactive(
|
|
89
|
+
{} as Dict<LoadResult>,
|
|
90
|
+
);
|
|
77
91
|
|
|
78
92
|
constructor(ctx: Context) {
|
|
79
93
|
super(ctx, "$loader", true);
|
|
80
94
|
|
|
81
95
|
// 服务端推送扩展入口的随附数据(entry-data):
|
|
82
96
|
// 同步到 store.entry 并刷新已加载扩展的 data ref
|
|
83
|
-
receive<{ id: string; data: unknown }>(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
receive<{ id: string; data: unknown }>(
|
|
98
|
+
"entry-data",
|
|
99
|
+
({ id, data }) => {
|
|
100
|
+
const entry = store.entry?.[id];
|
|
101
|
+
if (!entry) return;
|
|
102
|
+
entry.data = data;
|
|
103
|
+
const extension = this.extensions[id];
|
|
104
|
+
if (extension) extension.data.value = data;
|
|
105
|
+
},
|
|
106
|
+
);
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
/**
|
|
@@ -98,10 +115,15 @@ export default class LoaderService extends Service {
|
|
|
98
115
|
() => store.entry,
|
|
99
116
|
async (newValue, oldValue) => {
|
|
100
117
|
// _id 标识后端实例:变化说明服务端已重启,页面状态不再可信
|
|
101
|
-
const { _id, ...rest } = (newValue ||
|
|
118
|
+
const { _id, ...rest } = (newValue ||
|
|
119
|
+
{}) as Dict<EntryData> & {
|
|
102
120
|
_id?: string | undefined;
|
|
103
121
|
};
|
|
104
|
-
if (
|
|
122
|
+
if (
|
|
123
|
+
this.backendId &&
|
|
124
|
+
_id &&
|
|
125
|
+
this.backendId !== _id
|
|
126
|
+
) {
|
|
105
127
|
window.location.reload();
|
|
106
128
|
return;
|
|
107
129
|
}
|
|
@@ -115,38 +137,42 @@ export default class LoaderService extends Service {
|
|
|
115
137
|
}
|
|
116
138
|
|
|
117
139
|
await Promise.all(
|
|
118
|
-
Object.entries(rest).map(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
140
|
+
Object.entries(rest).map(
|
|
141
|
+
([key, { files, paths, data }]) => {
|
|
142
|
+
if (this.extensions[key]) return;
|
|
143
|
+
// 每个扩展在隔离的 "extension" 作用域中运行,
|
|
144
|
+
// 并通过 ctx.extension 拿到自己的加载结果
|
|
145
|
+
const scope = this.ctx
|
|
146
|
+
.isolate("extension")
|
|
147
|
+
.plugin(() => {});
|
|
148
|
+
scope.ctx.extension = this.extensions[key] = {
|
|
149
|
+
done: ref(false),
|
|
150
|
+
scope,
|
|
151
|
+
paths,
|
|
152
|
+
data: ref(data),
|
|
153
|
+
};
|
|
154
|
+
// 依次加载入口声明的全部文件,按后缀分发给对应 loader;
|
|
155
|
+
// task 返回给外层 Promise.all,使 initTask 真正等到全部
|
|
156
|
+
// 入口文件 settle 后才放行(否则 router install 早于扩展
|
|
157
|
+
// 注册路由,初始导航会对当前路径报 no match)。
|
|
158
|
+
// 扩展加载失败不阻塞界面可用性
|
|
159
|
+
const task = Promise.all(
|
|
160
|
+
files.map((url) => {
|
|
161
|
+
for (const ext in loaders) {
|
|
162
|
+
if (!url.endsWith(ext)) continue;
|
|
163
|
+
return loaders[ext]?.(scope.ctx, url);
|
|
164
|
+
}
|
|
165
|
+
// 无匹配后缀的入口文件:显式跳过(noImplicitReturns)
|
|
166
|
+
return undefined;
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
void task.finally(() => {
|
|
170
|
+
const extension = this.extensions[key];
|
|
171
|
+
if (extension) extension.done.value = true;
|
|
172
|
+
});
|
|
173
|
+
return task.catch(() => {});
|
|
174
|
+
},
|
|
175
|
+
),
|
|
150
176
|
);
|
|
151
177
|
|
|
152
178
|
// 首次加载完成(oldValue 为空)时放行 initTask
|
package/client/plugins/router.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
|
* 路由服务:控制台页面(activity)与插槽视图的注册中心。
|
|
3
7
|
*
|
|
@@ -15,7 +19,11 @@ import {
|
|
|
15
19
|
ref,
|
|
16
20
|
toValue,
|
|
17
21
|
} from "vue";
|
|
18
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
createRouter,
|
|
24
|
+
createWebHistory,
|
|
25
|
+
START_LOCATION,
|
|
26
|
+
} from "vue-router";
|
|
19
27
|
import type { SlotOptions } from "../components";
|
|
20
28
|
import Overlay from "../components/chat/overlay.vue";
|
|
21
29
|
import type { Context } from "../context";
|
|
@@ -102,8 +110,15 @@ export class Activity {
|
|
|
102
110
|
this.options = options;
|
|
103
111
|
options.order ??= 0;
|
|
104
112
|
options.position ??= "top";
|
|
105
|
-
Object.assign(
|
|
106
|
-
|
|
113
|
+
Object.assign(
|
|
114
|
+
this,
|
|
115
|
+
omit(options, ["icon", "name", "desc", "disabled"]),
|
|
116
|
+
);
|
|
117
|
+
const {
|
|
118
|
+
path,
|
|
119
|
+
id = getActivityId(path),
|
|
120
|
+
component,
|
|
121
|
+
} = options;
|
|
107
122
|
// 路由挂载时把 activity 实例放进 meta,供导航守卫与标题栏取用
|
|
108
123
|
this._disposables.push(
|
|
109
124
|
ctx.$router.router.addRoute({
|
|
@@ -123,7 +138,9 @@ export class Activity {
|
|
|
123
138
|
/** 若存在待跳转目标且当前路由已能解析,则立即完成该跳转 */
|
|
124
139
|
handleUpdate() {
|
|
125
140
|
if (redirectTo.value) {
|
|
126
|
-
const location = this.ctx.$router.router.resolve(
|
|
141
|
+
const location = this.ctx.$router.router.resolve(
|
|
142
|
+
redirectTo.value,
|
|
143
|
+
);
|
|
127
144
|
if (location.matched.length) {
|
|
128
145
|
redirectTo.value = null;
|
|
129
146
|
this.ctx.$router.router.replace(location);
|
|
@@ -152,7 +169,8 @@ export class Activity {
|
|
|
152
169
|
*/
|
|
153
170
|
disabled() {
|
|
154
171
|
if (this.ctx.bail("activity", this)) return true;
|
|
155
|
-
if (!this.fields?.every((key) => store[key]))
|
|
172
|
+
if (!this.fields?.every((key) => store[key]))
|
|
173
|
+
return true;
|
|
156
174
|
if (this.when && !this.when()) return true;
|
|
157
175
|
if (this.options.disabled?.()) return true;
|
|
158
176
|
return false;
|
|
@@ -161,10 +179,13 @@ export class Activity {
|
|
|
161
179
|
dispose() {
|
|
162
180
|
this._disposables.forEach((dispose) => dispose());
|
|
163
181
|
// 若被卸载的恰是当前展示的页面:暂存目标路由,退回缓存的首页
|
|
164
|
-
const current =
|
|
182
|
+
const current =
|
|
183
|
+
this.ctx.$router.router.currentRoute.value;
|
|
165
184
|
if (current?.meta?.activity === this) {
|
|
166
185
|
redirectTo.value = current.fullPath;
|
|
167
|
-
this.ctx.$router.router.push(
|
|
186
|
+
this.ctx.$router.router.push(
|
|
187
|
+
this.ctx.$router.cache["home"] || "/",
|
|
188
|
+
);
|
|
168
189
|
}
|
|
169
190
|
return delete this.ctx.$router.pages[this.id];
|
|
170
191
|
}
|
|
@@ -199,7 +220,8 @@ export default class RouterService extends Service {
|
|
|
199
220
|
const initialTitle = document.title;
|
|
200
221
|
ctx.effect(() =>
|
|
201
222
|
this.router.afterEach((route) => {
|
|
202
|
-
const { name, fullPath } =
|
|
223
|
+
const { name, fullPath } =
|
|
224
|
+
this.router.currentRoute.value;
|
|
203
225
|
if (name) {
|
|
204
226
|
// 本应用的路由名均为字符串;reactive 收窄了索引签名,不含 symbol
|
|
205
227
|
this.cache[name as string] = fullPath;
|
|
@@ -207,7 +229,8 @@ export default class RouterService extends Service {
|
|
|
207
229
|
// 进入 activity 页面时更新标签页标题为 "页面名 | 原标题"
|
|
208
230
|
if (route.meta.activity) {
|
|
209
231
|
document.title = `${route.meta.activity.name}`;
|
|
210
|
-
if (initialTitle)
|
|
232
|
+
if (initialTitle)
|
|
233
|
+
document.title += ` | ${initialTitle}`;
|
|
211
234
|
}
|
|
212
235
|
}),
|
|
213
236
|
);
|
|
@@ -250,7 +273,9 @@ export default class RouterService extends Service {
|
|
|
250
273
|
*/
|
|
251
274
|
slot(options: SlotOptions) {
|
|
252
275
|
options.order ??= 0;
|
|
253
|
-
const component = this.ctx.wrapComponent(
|
|
276
|
+
const component = this.ctx.wrapComponent(
|
|
277
|
+
options.component,
|
|
278
|
+
);
|
|
254
279
|
if (component) options.component = component;
|
|
255
280
|
if (options.when) {
|
|
256
281
|
const { when } = options;
|
|
@@ -268,7 +293,9 @@ export default class RouterService extends Service {
|
|
|
268
293
|
|
|
269
294
|
/** 注册 activity 页面(自动挂到 vue-router 与侧栏);返回取消注册函数 */
|
|
270
295
|
page(options: Activity.Options) {
|
|
271
|
-
const component = this.ctx.wrapComponent(
|
|
296
|
+
const component = this.ctx.wrapComponent(
|
|
297
|
+
options.component,
|
|
298
|
+
);
|
|
272
299
|
if (component) options.component = component;
|
|
273
300
|
return this.ctx.effect(() => {
|
|
274
301
|
const activity = new Activity(this.ctx, options);
|
|
@@ -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
|
* 设置服务:控制台本地配置(localStorage)与设置面板的管理。
|
|
3
7
|
*
|
|
@@ -5,10 +9,23 @@
|
|
|
5
9
|
* (经所有插件 schema 补全后的完整配置),二者双向同步(见文末类注释)。
|
|
6
10
|
* 插件通过 `ctx.settings()` 注册设置分区、`ctx.schema()` 注册自定义控件。
|
|
7
11
|
*/
|
|
8
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type RemovableRef,
|
|
14
|
+
useLocalStorage,
|
|
15
|
+
} from "@vueuse/core";
|
|
9
16
|
import { type Dict, remove } from "cosmokit";
|
|
10
|
-
import {
|
|
11
|
-
|
|
17
|
+
import {
|
|
18
|
+
type Component,
|
|
19
|
+
computed,
|
|
20
|
+
markRaw,
|
|
21
|
+
reactive,
|
|
22
|
+
ref,
|
|
23
|
+
watch,
|
|
24
|
+
} from "vue";
|
|
25
|
+
import {
|
|
26
|
+
Schema,
|
|
27
|
+
SchemaBase,
|
|
28
|
+
} from "../../../components/client/index.ts";
|
|
12
29
|
import type { Config } from "..";
|
|
13
30
|
import type { Context } from "../context";
|
|
14
31
|
import { insert, type Ordered, Service } from "../utils";
|
|
@@ -52,7 +69,10 @@ export let useStorage = <T extends object>(
|
|
|
52
69
|
__version__?: number | undefined;
|
|
53
70
|
};
|
|
54
71
|
initial.__version__ = version;
|
|
55
|
-
const storage = useLocalStorage(
|
|
72
|
+
const storage = useLocalStorage(
|
|
73
|
+
`koishi.console.${key}`,
|
|
74
|
+
initial,
|
|
75
|
+
);
|
|
56
76
|
if (storage.value.__version__ !== version) {
|
|
57
77
|
storage.value = initial;
|
|
58
78
|
}
|
|
@@ -83,7 +103,10 @@ export function createStorage<T extends object>(
|
|
|
83
103
|
if (storage.value.version !== version) {
|
|
84
104
|
storage.value = { version, data: initial };
|
|
85
105
|
} else if (!Array.isArray(storage.value.data)) {
|
|
86
|
-
storage.value.data = {
|
|
106
|
+
storage.value.data = {
|
|
107
|
+
...initial,
|
|
108
|
+
...storage.value.data,
|
|
109
|
+
};
|
|
87
110
|
}
|
|
88
111
|
return reactive<T>(storage.value["data"]);
|
|
89
112
|
}
|
|
@@ -137,14 +160,19 @@ export default class SettingService extends Service {
|
|
|
137
160
|
title: "通用设置",
|
|
138
161
|
order: 1000,
|
|
139
162
|
schema: Schema.object({
|
|
140
|
-
locale: Schema.union([
|
|
163
|
+
locale: Schema.union([
|
|
164
|
+
"zh-CN",
|
|
165
|
+
"en-US",
|
|
166
|
+
]).description("语言设置。"),
|
|
141
167
|
}).description("通用设置"),
|
|
142
168
|
});
|
|
143
169
|
|
|
144
170
|
// 汇总所有分区 schema 为一个相交对象,作为 resolved 的解释器
|
|
145
171
|
const schema = computed(() => {
|
|
146
172
|
const list: Schema[] = [];
|
|
147
|
-
for (const settings of Object.values(
|
|
173
|
+
for (const settings of Object.values(
|
|
174
|
+
ctx.internal.settings,
|
|
175
|
+
)) {
|
|
148
176
|
for (const options of settings) {
|
|
149
177
|
if (options.schema) {
|
|
150
178
|
list.push(options.schema);
|
|
@@ -182,7 +210,9 @@ export default class SettingService extends Service {
|
|
|
182
210
|
ctx.effect(() => () => stop?.());
|
|
183
211
|
|
|
184
212
|
// 原始存储或 schema 集合任一变化,都重新生成 resolved
|
|
185
|
-
ctx.effect(() =>
|
|
213
|
+
ctx.effect(() =>
|
|
214
|
+
watch(original, update, { deep: true }),
|
|
215
|
+
);
|
|
186
216
|
ctx.effect(() => watch(schema, update));
|
|
187
217
|
}
|
|
188
218
|
|
|
@@ -191,7 +221,9 @@ export default class SettingService extends Service {
|
|
|
191
221
|
* 返回取消注册函数。
|
|
192
222
|
*/
|
|
193
223
|
extendSchema(extension: SchemaBase.Extension) {
|
|
194
|
-
const component = this.ctx.wrapComponent(
|
|
224
|
+
const component = this.ctx.wrapComponent(
|
|
225
|
+
extension.component,
|
|
226
|
+
);
|
|
195
227
|
if (component) extension.component = component;
|
|
196
228
|
return this.ctx.effect(() => {
|
|
197
229
|
SchemaBase.extensions.add(extension);
|
|
@@ -203,14 +235,19 @@ export default class SettingService extends Service {
|
|
|
203
235
|
settings(options: SettingOptions) {
|
|
204
236
|
markRaw(options);
|
|
205
237
|
options.order ??= 0;
|
|
206
|
-
const component = this.ctx.wrapComponent(
|
|
238
|
+
const component = this.ctx.wrapComponent(
|
|
239
|
+
options.component,
|
|
240
|
+
);
|
|
207
241
|
if (component) options.component = component;
|
|
208
242
|
return this.ctx.effect(() => {
|
|
209
|
-
const list = (this.ctx.internal.settings[
|
|
243
|
+
const list = (this.ctx.internal.settings[
|
|
244
|
+
options.id
|
|
245
|
+
] ||= []);
|
|
210
246
|
insert(list, options);
|
|
211
247
|
return () => {
|
|
212
248
|
remove(list, options);
|
|
213
|
-
if (!list.length)
|
|
249
|
+
if (!list.length)
|
|
250
|
+
delete this.ctx.internal.settings[options.id];
|
|
214
251
|
};
|
|
215
252
|
});
|
|
216
253
|
}
|
package/client/plugins/theme.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
|
* 主题服务:管理控制台的深浅色模式与主题注册。
|
|
3
7
|
*
|
|
@@ -7,7 +11,13 @@
|
|
|
7
11
|
*/
|
|
8
12
|
import { usePreferredDark } from "@vueuse/core";
|
|
9
13
|
import type { Dict } from "cosmokit";
|
|
10
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type Component,
|
|
16
|
+
computed,
|
|
17
|
+
markRaw,
|
|
18
|
+
reactive,
|
|
19
|
+
watchEffect,
|
|
20
|
+
} from "vue";
|
|
11
21
|
import { Schema } from "../../../components/client/index.ts";
|
|
12
22
|
import type { Context } from "../context";
|
|
13
23
|
import { Service } from "../utils";
|
|
@@ -96,11 +106,15 @@ export default class ThemeService extends Service {
|
|
|
96
106
|
watchEffect(
|
|
97
107
|
() => {
|
|
98
108
|
if (!config.value.theme) return;
|
|
99
|
-
const root =
|
|
109
|
+
const root =
|
|
110
|
+
window.document.querySelector("html");
|
|
100
111
|
if (!root) return;
|
|
101
112
|
// 把当前主题名写到 <html theme="...">,并同步 dark 类;
|
|
102
113
|
// 主题样式与深色变量均由 CSS 依据这两个标记选择
|
|
103
|
-
root.setAttribute(
|
|
114
|
+
root.setAttribute(
|
|
115
|
+
"theme",
|
|
116
|
+
config.value.theme[colorMode.value],
|
|
117
|
+
);
|
|
104
118
|
if (colorMode.value === "dark") {
|
|
105
119
|
root.classList.add("dark");
|
|
106
120
|
} else {
|
|
@@ -119,16 +133,21 @@ export default class ThemeService extends Service {
|
|
|
119
133
|
*/
|
|
120
134
|
theme(options: ThemeOptions) {
|
|
121
135
|
markRaw(options);
|
|
122
|
-
for (const [type, component] of Object.entries(
|
|
136
|
+
for (const [type, component] of Object.entries(
|
|
137
|
+
options.components || {},
|
|
138
|
+
)) {
|
|
123
139
|
this.ctx.slot({
|
|
124
140
|
type,
|
|
125
|
-
disabled: () =>
|
|
141
|
+
disabled: () =>
|
|
142
|
+
config.value.theme[colorMode.value] !==
|
|
143
|
+
options.id,
|
|
126
144
|
component,
|
|
127
145
|
});
|
|
128
146
|
}
|
|
129
147
|
return this.ctx.effect(() => {
|
|
130
148
|
this.ctx.internal.themes[options.id] = options;
|
|
131
|
-
return () =>
|
|
149
|
+
return () =>
|
|
150
|
+
delete this.ctx.internal.themes[options.id];
|
|
132
151
|
});
|
|
133
152
|
}
|
|
134
153
|
}
|
package/client/shims.d.ts
CHANGED
package/client/utils.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 { markRaw } from "vue";
|
|
3
7
|
import type { Context } from "./context";
|
|
@@ -20,13 +24,18 @@ export interface Ordered {
|
|
|
20
24
|
* 按 order 升序将条目插入有序列表(相同 order 的后者排在后面)。
|
|
21
25
|
* 同时 markRaw 标记条目,避免其被 Vue 深度代理。
|
|
22
26
|
*/
|
|
23
|
-
export function insert<T extends Ordered>(
|
|
27
|
+
export function insert<T extends Ordered>(
|
|
28
|
+
list: T[],
|
|
29
|
+
item: T,
|
|
30
|
+
) {
|
|
24
31
|
markRaw(item);
|
|
25
32
|
// order 为可选属性:任一侧缺失(undefined)时数值比较结果恒为 false,
|
|
26
33
|
// 与原实现(直接比较)在所有输入下的求值结果一致,这里显式判空以通过严格空检查
|
|
27
34
|
const index = list.findIndex(
|
|
28
35
|
(a) =>
|
|
29
|
-
a.order !== undefined &&
|
|
36
|
+
a.order !== undefined &&
|
|
37
|
+
item.order !== undefined &&
|
|
38
|
+
a.order < item.order,
|
|
30
39
|
);
|
|
31
40
|
if (index >= 0) {
|
|
32
41
|
list.splice(index, 0, item);
|
package/global.d.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
|
/// <reference types="vue-i18n" />
|
|
2
6
|
/// <reference types="vite/client" />
|
|
3
7
|
/// <reference types="element-plus/global" />
|
package/lib/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as build } from "./src-
|
|
2
|
+
import { t as build } from "./src-BOakssV3.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
//#endregion
|
|
@@ -16,7 +16,7 @@ import { resolve } from "node:path";
|
|
|
16
16
|
const { version } = {
|
|
17
17
|
name: "@koishi-ce/client",
|
|
18
18
|
description: "Koishi Console Client",
|
|
19
|
-
version: "1.0.
|
|
19
|
+
version: "1.0.4",
|
|
20
20
|
type: "module",
|
|
21
21
|
main: "client/index.ts",
|
|
22
22
|
exports: {
|
|
@@ -70,6 +70,10 @@ const { version } = {
|
|
|
70
70
|
"vue": "^3.5.42",
|
|
71
71
|
"vue-i18n": "^11.4.10",
|
|
72
72
|
"vue-router": "^5.2.0"
|
|
73
|
+
},
|
|
74
|
+
devDependencies: {
|
|
75
|
+
"@koishi-ce/koishi": "workspace:*",
|
|
76
|
+
"@koishi-ce/plugin-console": "workspace:*"
|
|
73
77
|
}
|
|
74
78
|
};
|
|
75
79
|
const help = `koishi-console/${version}
|
|
@@ -100,7 +104,7 @@ async function main() {
|
|
|
100
104
|
await build(resolve(process.cwd(), target));
|
|
101
105
|
return;
|
|
102
106
|
}
|
|
103
|
-
await (await import("./client-
|
|
107
|
+
await (await import("./client-C97iYPCr.mjs")).default();
|
|
104
108
|
return;
|
|
105
109
|
}
|
|
106
110
|
console.error(`Unknown command ${JSON.stringify(command)}.`);
|
package/lib/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as createServer, t as build } from "./src-
|
|
1
|
+
import { n as createServer, t as build } from "./src-BOakssV3.mjs";
|
|
2
2
|
export { build, createServer };
|
|
@@ -2,6 +2,7 @@ import { t as yaml } from "./yaml-UzQbvquH.mjs";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { mkdir, rm } from "node:fs/promises";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
import vue from "@vitejs/plugin-vue";
|
|
6
7
|
import * as vite from "vite";
|
|
7
8
|
//#region src/index.ts
|
|
@@ -43,6 +44,11 @@ const runtimeShimPath = (workspaceAliases["@koishi-ce/components"] ?? "").replac
|
|
|
43
44
|
*/
|
|
44
45
|
async function build(root, config = {}) {
|
|
45
46
|
if (!existsSync(`${root}/client`)) return;
|
|
47
|
+
const overridePath = `${root}/build/client.ts`;
|
|
48
|
+
if (existsSync(overridePath)) {
|
|
49
|
+
const mod = await import(pathToFileURL(overridePath).href);
|
|
50
|
+
config = vite.mergeConfig(config, mod.default ?? mod);
|
|
51
|
+
}
|
|
46
52
|
const outDir = `${root}/dist`;
|
|
47
53
|
if (existsSync(outDir)) await rm(outDir, { recursive: true });
|
|
48
54
|
await mkdir(outDir, { recursive: true });
|
|
@@ -76,7 +82,7 @@ async function build(root, config = {}) {
|
|
|
76
82
|
}
|
|
77
83
|
},
|
|
78
84
|
plugins: [
|
|
79
|
-
vue(),
|
|
85
|
+
vue({ template: { compilerOptions: { comments: false } } }),
|
|
80
86
|
yaml(),
|
|
81
87
|
(await import("unocss/vite")).default({ presets: [(await import("unocss/preset-mini")).default({ preflight: false })] })
|
|
82
88
|
],
|
|
@@ -124,7 +130,7 @@ async function createServer(baseDir, config = {}) {
|
|
|
124
130
|
fs: { allow: [vite.searchForWorkspaceRoot(baseDir)] }
|
|
125
131
|
},
|
|
126
132
|
plugins: [
|
|
127
|
-
vue(),
|
|
133
|
+
vue({ template: { compilerOptions: { comments: false } } }),
|
|
128
134
|
yaml(),
|
|
129
135
|
(await import("unocss/vite")).default({ presets: [(await import("unocss/preset-mini")).default({ preflight: false })] })
|
|
130
136
|
],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koishi-ce/client",
|
|
3
3
|
"description": "Koishi Console Client",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "client/index.ts",
|
|
7
7
|
"exports": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"build"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@koishi-ce/components": "^1.0.
|
|
51
|
+
"@koishi-ce/components": "^1.0.1",
|
|
52
52
|
"@satorijs/protocol": "^1.7.0",
|
|
53
53
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
54
54
|
"@vueuse/core": "^14.4.0",
|
|
@@ -62,5 +62,9 @@
|
|
|
62
62
|
"vue": "^3.5.42",
|
|
63
63
|
"vue-i18n": "^11.4.10",
|
|
64
64
|
"vue-router": "^5.2.0"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@koishi-ce/koishi": "^1.0.6",
|
|
68
|
+
"@koishi-ce/plugin-console": "^1.0.4"
|
|
65
69
|
}
|
|
66
70
|
}
|
package/src/bin.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
4
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* `koishi-console` CLI 入口(构建产物 lib/bin.mjs 由 package.json 的
|
|
@@ -33,7 +36,11 @@ Options:
|
|
|
33
36
|
|
|
34
37
|
async function main() {
|
|
35
38
|
const [command, ...args] = process.argv.slice(2);
|
|
36
|
-
if (
|
|
39
|
+
if (
|
|
40
|
+
command === undefined ||
|
|
41
|
+
command === "-h" ||
|
|
42
|
+
command === "--help"
|
|
43
|
+
) {
|
|
37
44
|
console.log(help);
|
|
38
45
|
return;
|
|
39
46
|
}
|
|
@@ -44,7 +51,8 @@ async function main() {
|
|
|
44
51
|
if (command === "build") {
|
|
45
52
|
// root 缺省时:cwd 是插件目录则构建它,否则视为宿主总装
|
|
46
53
|
const root = args.find((arg) => !arg.startsWith("-"));
|
|
47
|
-
const target =
|
|
54
|
+
const target =
|
|
55
|
+
root ?? (existsSync("client") ? "." : undefined);
|
|
48
56
|
if (target !== undefined) {
|
|
49
57
|
await build(resolve(process.cwd(), target));
|
|
50
58
|
return;
|
|
@@ -53,7 +61,9 @@ async function main() {
|
|
|
53
61
|
await host.default();
|
|
54
62
|
return;
|
|
55
63
|
}
|
|
56
|
-
console.error(
|
|
64
|
+
console.error(
|
|
65
|
+
`Unknown command ${JSON.stringify(command)}.`,
|
|
66
|
+
);
|
|
57
67
|
console.log(help);
|
|
58
68
|
process.exitCode = 1;
|
|
59
69
|
}
|