@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.
Files changed (115) hide show
  1. package/README.md +56 -0
  2. package/app/home/home.vue +4 -0
  3. package/app/home/index.ts +4 -0
  4. package/app/home/welcome.en-US.yml +4 -0
  5. package/app/home/welcome.vue +16 -6
  6. package/app/home/welcome.zh-CN.yml +4 -0
  7. package/app/index.scss +4 -0
  8. package/app/index.ts +12 -2
  9. package/app/layout/header.vue +16 -4
  10. package/app/layout/index.ts +4 -0
  11. package/app/layout/layout.vue +8 -1
  12. package/app/layout/menu-item.vue +7 -1
  13. package/app/settings/index.ts +4 -0
  14. package/app/settings/settings.vue +10 -4
  15. package/app/settings/theme.vue +8 -1
  16. package/app/shims.d.ts +3 -0
  17. package/app/status/index.ts +4 -0
  18. package/app/status/loading.vue +8 -1
  19. package/app/status/status.vue +4 -0
  20. package/app/styles/element.scss +4 -0
  21. package/app/styles/hc.scss +4 -0
  22. package/app/styles/index.scss +4 -0
  23. package/app/styles/index.ts +4 -0
  24. package/app/styles/layout.scss +4 -0
  25. package/app/theme/activity/button.vue +8 -1
  26. package/app/theme/activity/index.vue +16 -4
  27. package/app/theme/activity/item.vue +22 -4
  28. package/app/theme/activity/separator.vue +48 -12
  29. package/app/theme/activity/utils.ts +4 -0
  30. package/app/theme/blank.vue +4 -0
  31. package/app/theme/index.ts +9 -1
  32. package/app/theme/index.vue +7 -1
  33. package/app/theme/menu/index.vue +4 -0
  34. package/app/theme/menu/menu-item.vue +14 -4
  35. package/app/theme/menu/menu.vue +13 -4
  36. package/app/theme/status.vue +4 -0
  37. package/client/components/chat/image.vue +4 -0
  38. package/client/components/chat/overlay.vue +34 -8
  39. package/client/components/chat/utils.ts +4 -0
  40. package/client/components/common/index.ts +4 -0
  41. package/client/components/common/k-button.vue +4 -0
  42. package/client/components/common/k-hint.vue +7 -1
  43. package/client/components/common/k-tab.vue +4 -0
  44. package/client/components/dynamic.vue +6 -1
  45. package/client/components/icons/activity/default.vue +4 -0
  46. package/client/components/icons/activity/ellipsis.vue +4 -0
  47. package/client/components/icons/activity/home.vue +4 -0
  48. package/client/components/icons/activity/moon.vue +4 -0
  49. package/client/components/icons/activity/settings.vue +4 -0
  50. package/client/components/icons/activity/sun.vue +4 -0
  51. package/client/components/icons/index.ts +11 -2
  52. package/client/components/icons/style.scss +4 -0
  53. package/client/components/icons/svg/arrow-left.vue +4 -0
  54. package/client/components/icons/svg/arrow-right.vue +4 -0
  55. package/client/components/icons/svg/box-open.vue +4 -0
  56. package/client/components/icons/svg/check-full.vue +4 -0
  57. package/client/components/icons/svg/chevron-down.vue +4 -0
  58. package/client/components/icons/svg/chevron-left.vue +4 -0
  59. package/client/components/icons/svg/chevron-right.vue +4 -0
  60. package/client/components/icons/svg/chevron-up.vue +4 -0
  61. package/client/components/icons/svg/clipboard-list.vue +4 -0
  62. package/client/components/icons/svg/edit.vue +4 -0
  63. package/client/components/icons/svg/exclamation-full.vue +4 -0
  64. package/client/components/icons/svg/expand.vue +4 -0
  65. package/client/components/icons/svg/file-archive.vue +4 -0
  66. package/client/components/icons/svg/filter.vue +4 -0
  67. package/client/components/icons/svg/github.vue +4 -0
  68. package/client/components/icons/svg/gitlab.vue +4 -0
  69. package/client/components/icons/svg/info-full.vue +4 -0
  70. package/client/components/icons/svg/koishi.vue +4 -0
  71. package/client/components/icons/svg/link.vue +4 -0
  72. package/client/components/icons/svg/paper-plane.vue +4 -0
  73. package/client/components/icons/svg/question-empty.vue +4 -0
  74. package/client/components/icons/svg/redo.vue +4 -0
  75. package/client/components/icons/svg/search-minus.vue +4 -0
  76. package/client/components/icons/svg/search-plus.vue +4 -0
  77. package/client/components/icons/svg/search.vue +4 -0
  78. package/client/components/icons/svg/star-empty.vue +4 -0
  79. package/client/components/icons/svg/star-full.vue +4 -0
  80. package/client/components/icons/svg/start.vue +4 -0
  81. package/client/components/icons/svg/tag.vue +4 -0
  82. package/client/components/icons/svg/times-full.vue +4 -0
  83. package/client/components/icons/svg/tools.vue +4 -0
  84. package/client/components/icons/svg/undo.vue +4 -0
  85. package/client/components/icons/svg/user.vue +4 -0
  86. package/client/components/index.ts +12 -2
  87. package/client/components/layout/card.vue +4 -0
  88. package/client/components/layout/content.vue +4 -0
  89. package/client/components/layout/empty.vue +4 -0
  90. package/client/components/layout/index.ts +4 -0
  91. package/client/components/layout/tab-group.vue +4 -0
  92. package/client/components/layout/tab-item.vue +4 -0
  93. package/client/components/link.ts +9 -2
  94. package/client/components/perms.vue +26 -5
  95. package/client/components/slot.ts +23 -4
  96. package/client/context.ts +16 -4
  97. package/client/data.ts +61 -26
  98. package/client/index.ts +6 -1
  99. package/client/plugins/action.ts +50 -13
  100. package/client/plugins/i18n.ts +4 -0
  101. package/client/plugins/loader.ts +71 -45
  102. package/client/plugins/router.ts +38 -11
  103. package/client/plugins/setting.ts +49 -12
  104. package/client/plugins/theme.ts +25 -6
  105. package/client/shims.d.ts +3 -0
  106. package/client/utils.ts +11 -2
  107. package/global.d.ts +4 -0
  108. package/lib/bin.mjs +7 -3
  109. package/lib/{client-CaTn_gVG.mjs → client-C97iYPCr.mjs} +1 -1
  110. package/lib/index.mjs +1 -1
  111. package/lib/{src-lTfcJ1xA.mjs → src-BOakssV3.mjs} +8 -2
  112. package/package.json +6 -2
  113. package/src/bin.ts +13 -3
  114. package/src/index.ts +72 -14
  115. 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
  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 { default?: unknown } | null | undefined;
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<(ctx: Context, url: string) => Promise<void>> = {
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(unwrapExports(exports) as Plugin.Function, ctx.extension?.data);
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({} as Dict<LoadResult>);
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 }>("entry-data", ({ id, data }) => {
84
- const entry = store.entry?.[id];
85
- if (!entry) return;
86
- entry.data = data;
87
- const extension = this.extensions[id];
88
- if (extension) extension.data.value = data;
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 || {}) as Dict<EntryData> & {
118
+ const { _id, ...rest } = (newValue ||
119
+ {}) as Dict<EntryData> & {
102
120
  _id?: string | undefined;
103
121
  };
104
- if (this.backendId && _id && this.backendId !== _id) {
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(([key, { files, paths, data }]) => {
119
- if (this.extensions[key]) return;
120
- // 每个扩展在隔离的 "extension" 作用域中运行,
121
- // 并通过 ctx.extension 拿到自己的加载结果
122
- const scope = this.ctx.isolate("extension").plugin(() => {});
123
- scope.ctx.extension = this.extensions[key] = {
124
- done: ref(false),
125
- scope,
126
- paths,
127
- data: ref(data),
128
- };
129
- // 依次加载入口声明的全部文件,按后缀分发给对应 loader;
130
- // task 返回给外层 Promise.all,使 initTask 真正等到全部
131
- // 入口文件 settle 后才放行(否则 router install 早于扩展
132
- // 注册路由,初始导航会对当前路径报 no match)。
133
- // 扩展加载失败不阻塞界面可用性
134
- const task = Promise.all(
135
- files.map((url) => {
136
- for (const ext in loaders) {
137
- if (!url.endsWith(ext)) continue;
138
- return loaders[ext]?.(scope.ctx, url);
139
- }
140
- // 无匹配后缀的入口文件:显式跳过(noImplicitReturns)
141
- return undefined;
142
- }),
143
- );
144
- void task.finally(() => {
145
- const extension = this.extensions[key];
146
- if (extension) extension.done.value = true;
147
- });
148
- return task.catch(() => {});
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
@@ -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 { createRouter, createWebHistory, START_LOCATION } from "vue-router";
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(this, omit(options, ["icon", "name", "desc", "disabled"]));
106
- const { path, id = getActivityId(path), component } = options;
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(redirectTo.value);
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])) return true;
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 = this.ctx.$router.router.currentRoute.value;
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(this.ctx.$router.cache["home"] || "/");
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 } = this.router.currentRoute.value;
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) document.title += ` | ${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(options.component);
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(options.component);
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 { type RemovableRef, useLocalStorage } from "@vueuse/core";
12
+ import {
13
+ type RemovableRef,
14
+ useLocalStorage,
15
+ } from "@vueuse/core";
9
16
  import { type Dict, remove } from "cosmokit";
10
- import { type Component, computed, markRaw, reactive, ref, watch } from "vue";
11
- import { Schema, SchemaBase } from "../../../components/client/index.ts";
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(`koishi.console.${key}`, initial);
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 = { ...initial, ...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(["zh-CN", "en-US"]).description("语言设置。"),
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(ctx.internal.settings)) {
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(() => watch(original, update, { deep: true }));
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(extension.component);
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(options.component);
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[options.id] ||= []);
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) delete this.ctx.internal.settings[options.id];
249
+ if (!list.length)
250
+ delete this.ctx.internal.settings[options.id];
214
251
  };
215
252
  });
216
253
  }
@@ -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 { type Component, computed, markRaw, reactive, watchEffect } from "vue";
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 = window.document.querySelector("html");
109
+ const root =
110
+ window.document.querySelector("html");
100
111
  if (!root) return;
101
112
  // 把当前主题名写到 <html theme="...">,并同步 dark 类;
102
113
  // 主题样式与深色变量均由 CSS 依据这两个标记选择
103
- root.setAttribute("theme", config.value.theme[colorMode.value]);
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(options.components || {})) {
136
+ for (const [type, component] of Object.entries(
137
+ options.components || {},
138
+ )) {
123
139
  this.ctx.slot({
124
140
  type,
125
- disabled: () => config.value.theme[colorMode.value] !== options.id,
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 () => delete this.ctx.internal.themes[options.id];
149
+ return () =>
150
+ delete this.ctx.internal.themes[options.id];
132
151
  });
133
152
  }
134
153
  }
package/client/shims.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026-present Koishi-CE contributors.
3
+
1
4
  /**
2
5
  * 浏览器端类型垫片(packages/web/client 自身使用;app 项目通过
3
6
  * app/shims.d.ts 引用本文件):
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>(list: T[], item: T) {
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 && item.order !== undefined && a.order < item.order,
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-lTfcJ1xA.mjs";
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.2",
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-CaTn_gVG.mjs")).default();
107
+ await (await import("./client-C97iYPCr.mjs")).default();
104
108
  return;
105
109
  }
106
110
  console.error(`Unknown command ${JSON.stringify(command)}.`);
@@ -70,7 +70,7 @@ async function build(root, config = {}, isClient = false) {
70
70
  }
71
71
  },
72
72
  plugins: [
73
- vue(),
73
+ vue({ template: { compilerOptions: { comments: false } } }),
74
74
  yaml(),
75
75
  ...config.plugins || []
76
76
  ],
package/lib/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as createServer, t as build } from "./src-lTfcJ1xA.mjs";
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.2",
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.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 (command === undefined || command === "-h" || command === "--help") {
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 = root ?? (existsSync("client") ? "." : undefined);
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(`Unknown command ${JSON.stringify(command)}.`);
64
+ console.error(
65
+ `Unknown command ${JSON.stringify(command)}.`,
66
+ );
57
67
  console.log(help);
58
68
  process.exitCode = 1;
59
69
  }