dsh-messager 0.3.1 → 0.3.3
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.en.md +7 -4
- package/README.md +55 -29
- package/lib/client.js +311 -106
- package/lib/client.js.map +1 -1
- package/lib/config-route.d.ts +14 -4
- package/lib/config-route.d.ts.map +1 -1
- package/lib/config-route.js +22 -11
- package/lib/config-route.js.map +1 -1
- package/lib/config.d.ts +251 -8
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +78 -47
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts +19 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +66 -17
- package/lib/index.js.map +1 -1
- package/lib/notify.d.ts +14 -9
- package/lib/notify.d.ts.map +1 -1
- package/lib/notify.js +33 -32
- package/lib/notify.js.map +1 -1
- package/lib/settings.d.ts +29 -16
- package/lib/settings.d.ts.map +1 -1
- package/lib/settings.js +55 -15
- package/lib/settings.js.map +1 -1
- package/lib/types/client/card-controller.d.ts +2 -0
- package/lib/types/client/card-controller.d.ts.map +1 -1
- package/lib/types/client/config.d.ts +11 -2
- package/lib/types/client/config.d.ts.map +1 -1
- package/lib/types/client/diff.d.ts +16 -15
- package/lib/types/client/diff.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/config.d.ts +251 -8
- package/lib/types/config.d.ts.map +1 -1
- package/package.json +71 -55
package/lib/client.js
CHANGED
|
@@ -5,7 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
-
//#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.
|
|
8
|
+
//#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.4/node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
9
9
|
/** Return true when a value is `null` or `undefined`. */
|
|
10
10
|
function isNullable(value) {
|
|
11
11
|
return value === null || value === void 0;
|
|
@@ -29,6 +29,43 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
|
|
30
30
|
return result;
|
|
31
31
|
}
|
|
32
|
+
/** Shared config references used by schema validators and plugin runtimes. */
|
|
33
|
+
const write = Symbol.for("cosmokit.volatile.write");
|
|
34
|
+
function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
35
|
+
if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
|
|
36
|
+
if (value === null || typeof value !== "object") return value;
|
|
37
|
+
if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
|
|
38
|
+
ancestors.add(value);
|
|
39
|
+
try {
|
|
40
|
+
if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
|
|
41
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
|
|
42
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
|
|
43
|
+
} finally {
|
|
44
|
+
ancestors.delete(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a detached reference containing an immutable copy of the supplied data.
|
|
49
|
+
* @param value - validated config data; class instances and functions are unsupported.
|
|
50
|
+
* @returns a reference whose value is updated only by its owning runtime.
|
|
51
|
+
*/
|
|
52
|
+
function createVolatile(value) {
|
|
53
|
+
let current = snapshot(value);
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
get: () => current,
|
|
56
|
+
[write]: (value) => {
|
|
57
|
+
current = value;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Identify references across ESM/CJS copies of the shared library.
|
|
63
|
+
* @param value - a parsed config value.
|
|
64
|
+
* @returns whether the value implements the shared reference protocol.
|
|
65
|
+
*/
|
|
66
|
+
function isVolatile(value) {
|
|
67
|
+
return typeof value === "object" && value !== null && write in value;
|
|
68
|
+
}
|
|
32
69
|
/** Test values using `instanceof` with a `toStringTag` fallback. */
|
|
33
70
|
function is(type, value) {
|
|
34
71
|
if (arguments.length === 1) return (value) => is(type, value);
|
|
@@ -109,26 +146,47 @@ window.__ModuleLoader__.load({
|
|
|
109
146
|
}
|
|
110
147
|
return result;
|
|
111
148
|
}
|
|
112
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Compare values recursively, treating two volatile references as equal regardless of value.
|
|
151
|
+
* Strict comparison distinguishes null/undefined, treats opaque objects by identity,
|
|
152
|
+
* compares URLs by normalized href, treats array holes as undefined, and considers distinct cyclic structures unequal.
|
|
153
|
+
* @param a - first value.
|
|
154
|
+
* @param b - second value.
|
|
155
|
+
* @param strict - whether to require strict data equality outside volatile references.
|
|
156
|
+
* @returns whether the values compare equal.
|
|
157
|
+
*/
|
|
113
158
|
function deepEqual(a, b, strict) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
159
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
160
|
+
function compare(a, b) {
|
|
161
|
+
if (a === b) return true;
|
|
162
|
+
if (isVolatile(a) || isVolatile(b)) return isVolatile(a) && isVolatile(b);
|
|
163
|
+
if (!strict && isNullable(a) && isNullable(b)) return true;
|
|
164
|
+
if (typeof a !== typeof b || typeof a !== "object" || !a || !b) return false;
|
|
165
|
+
if (ancestors.has(a)) return false;
|
|
166
|
+
function check(test, then) {
|
|
167
|
+
return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
|
|
168
|
+
}
|
|
169
|
+
ancestors.add(a);
|
|
170
|
+
try {
|
|
171
|
+
return check(Array.isArray, (a, b) => {
|
|
172
|
+
if (a.length !== b.length) return false;
|
|
173
|
+
for (let index = 0; index < a.length; index++) if (!compare(a[index], b[index])) return false;
|
|
174
|
+
return true;
|
|
175
|
+
}) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("URL"), (a, b) => a.href === b.href) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
|
|
176
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
177
|
+
const viewA = new Uint8Array(a);
|
|
178
|
+
const viewB = new Uint8Array(b);
|
|
179
|
+
for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
|
|
180
|
+
return true;
|
|
181
|
+
}) ?? ((!strict || [a, b].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
|
|
182
|
+
...a,
|
|
183
|
+
...b
|
|
184
|
+
}).every((key) => compare(a[key], b[key])));
|
|
185
|
+
} finally {
|
|
186
|
+
ancestors.delete(a);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return compare(a, b);
|
|
132
190
|
}
|
|
133
191
|
/** Time constants plus parsing and formatting helpers. */
|
|
134
192
|
var Time;
|
|
@@ -201,7 +259,7 @@ window.__ModuleLoader__.load({
|
|
|
201
259
|
Time.template = template;
|
|
202
260
|
})(Time || (Time = {}));
|
|
203
261
|
//#endregion
|
|
204
|
-
//#region node_modules/.pnpm/@deepseek-ai+schemastery@3.18.
|
|
262
|
+
//#region node_modules/.pnpm/@deepseek-ai+schemastery@3.18.3/node_modules/@deepseek-ai/schemastery/lib/index.mjs
|
|
205
263
|
const kSchema = Symbol.for("schemastery");
|
|
206
264
|
const kValidationError = Symbol.for("ValidationError");
|
|
207
265
|
globalThis.__schemastery_index__ ??= 0;
|
|
@@ -377,6 +435,7 @@ window.__ModuleLoader__.load({
|
|
|
377
435
|
return schema;
|
|
378
436
|
};
|
|
379
437
|
Schema.prototype.simplify = function simplify(value) {
|
|
438
|
+
if (isVolatile(value)) value = value.get();
|
|
380
439
|
if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
|
|
381
440
|
if (isNullable(value)) return value;
|
|
382
441
|
if (this.type === "object" || this.type === "dict") {
|
|
@@ -433,12 +492,49 @@ window.__ModuleLoader__.load({
|
|
|
433
492
|
};
|
|
434
493
|
return schema;
|
|
435
494
|
} });
|
|
495
|
+
Schema.prototype.volatile = function volatile() {
|
|
496
|
+
if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
|
|
497
|
+
return this.extra("volatile", true);
|
|
498
|
+
};
|
|
436
499
|
const resolvers = {};
|
|
500
|
+
const checkedVolatile = Symbol("checked-volatile-schema");
|
|
501
|
+
function validateVolatileSchema(schema, path = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
|
|
502
|
+
const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
|
|
503
|
+
if (states.has(blocked)) return;
|
|
504
|
+
states.add(blocked);
|
|
505
|
+
seen.set(schema, states);
|
|
506
|
+
if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path });
|
|
507
|
+
const nested = blocked || !!schema.meta?.volatile;
|
|
508
|
+
if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen);
|
|
509
|
+
if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, "<key>"], true, seen);
|
|
510
|
+
if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path, "*"], true, seen);
|
|
511
|
+
if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path, String(index)], true, seen);
|
|
512
|
+
}
|
|
437
513
|
Schema.extend = function extend(type, resolve) {
|
|
438
514
|
resolvers[type] = resolve;
|
|
439
515
|
};
|
|
440
516
|
Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
|
|
441
517
|
if (!schema) return [data];
|
|
518
|
+
if (!options[checkedVolatile]) {
|
|
519
|
+
validateVolatileSchema(schema, options.path);
|
|
520
|
+
options = {
|
|
521
|
+
...options,
|
|
522
|
+
[checkedVolatile]: true
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (schema.meta?.volatile) {
|
|
526
|
+
const inner = Schema(schema);
|
|
527
|
+
inner.meta = {
|
|
528
|
+
...schema.meta,
|
|
529
|
+
volatile: false
|
|
530
|
+
};
|
|
531
|
+
const [value, adapted] = Schema.resolve(data, inner, options, strict);
|
|
532
|
+
try {
|
|
533
|
+
return [createVolatile(value), adapted];
|
|
534
|
+
} catch (error) {
|
|
535
|
+
throw new ValidationError(error instanceof Error ? error.message : String(error), options);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
442
538
|
if (options.ignore?.(data, schema)) return [data];
|
|
443
539
|
if (isNullable(data) && schema.type !== "lazy") {
|
|
444
540
|
if (schema.meta.required) throw new ValidationError(`missing required value`, options);
|
|
@@ -541,6 +637,7 @@ window.__ModuleLoader__.load({
|
|
|
541
637
|
...schema.meta,
|
|
542
638
|
...schema.inner.meta
|
|
543
639
|
};
|
|
640
|
+
validateVolatileSchema(schema.inner, options.path, true);
|
|
544
641
|
}
|
|
545
642
|
return Schema.resolve(data, schema.inner, options, strict);
|
|
546
643
|
});
|
|
@@ -640,7 +737,7 @@ window.__ModuleLoader__.load({
|
|
|
640
737
|
} catch (e) {
|
|
641
738
|
if (!options?.autofix) throw e;
|
|
642
739
|
delete data[key];
|
|
643
|
-
return schema.meta.default;
|
|
740
|
+
return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
|
|
644
741
|
}
|
|
645
742
|
}
|
|
646
743
|
Schema.extend("array", (data, { inner, meta }, options) => {
|
|
@@ -799,12 +896,18 @@ window.__ModuleLoader__.load({
|
|
|
799
896
|
/**
|
|
800
897
|
* dsh-messager 配置模型。
|
|
801
898
|
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
899
|
+
* DSH 0.1.7 起,本 schema 由 **Loader 自动接管**:插件模块导出的 `Config` 即
|
|
900
|
+
* settings 表单的 schema,命名空间 = profile 中该插件条目的 id(本仓库的
|
|
901
|
+
* `cordis.patch.yml` / `cordis.yml` 都声明为 `messager`)。
|
|
902
|
+
* 因此**不再有** `ctx.settings.register(...)` 这一步。
|
|
806
903
|
*
|
|
807
|
-
*
|
|
904
|
+
* ⚠️ 所有可编辑字段必须标记 `.volatile()`:DSH 用 `isVolatilePath` 门控写入,
|
|
905
|
+
* 非 volatile 路径一律被 `settings.mutate` 拒绝("Config field ... is not volatile"),
|
|
906
|
+
* 且没有任何 volatile 字段时整个命名空间会被 `volatileForm` 判为不可配置
|
|
907
|
+
* ("Plugin entry ... has no volatile fields"),设置页直接报废。
|
|
908
|
+
* 这是 0.1.7 的硬契约,不是可选项。
|
|
909
|
+
*
|
|
910
|
+
* 有效值优先级:schema 默认值 → base(Loader config)→ 用户层(设置页)。
|
|
808
911
|
*/
|
|
809
912
|
const verbosity = Schema.union([
|
|
810
913
|
"minimal",
|
|
@@ -813,72 +916,87 @@ window.__ModuleLoader__.load({
|
|
|
813
916
|
]);
|
|
814
917
|
const Config = Schema.object({
|
|
815
918
|
triggers: Schema.object({
|
|
816
|
-
interaction: Schema.boolean().default(true),
|
|
817
|
-
completed: Schema.boolean().default(true),
|
|
818
|
-
error: Schema.boolean().default(true)
|
|
919
|
+
interaction: Schema.boolean().default(true).volatile(),
|
|
920
|
+
completed: Schema.boolean().default(true).volatile(),
|
|
921
|
+
error: Schema.boolean().default(true).volatile()
|
|
819
922
|
}),
|
|
820
923
|
system: Schema.object({
|
|
821
|
-
enabled: Schema.boolean().default(true),
|
|
822
|
-
icon: Schema.string(),
|
|
823
|
-
verbosity: verbosity.default("normal")
|
|
924
|
+
enabled: Schema.boolean().default(true).volatile(),
|
|
925
|
+
icon: Schema.string().volatile(),
|
|
926
|
+
verbosity: verbosity.default("normal").volatile()
|
|
824
927
|
}),
|
|
825
928
|
browser: Schema.object({
|
|
826
|
-
enabled: Schema.boolean().default(true),
|
|
827
|
-
icon: Schema.string(),
|
|
828
|
-
onlyWhenHidden: Schema.boolean().default(true),
|
|
829
|
-
verbosity: verbosity.default("normal")
|
|
929
|
+
enabled: Schema.boolean().default(true).volatile(),
|
|
930
|
+
icon: Schema.string().volatile(),
|
|
931
|
+
onlyWhenHidden: Schema.boolean().default(true).volatile(),
|
|
932
|
+
verbosity: verbosity.default("normal").volatile()
|
|
830
933
|
}),
|
|
831
934
|
feishu: Schema.object({
|
|
832
|
-
enabled: Schema.boolean().default(false),
|
|
833
|
-
webhookUrl: Schema.string(),
|
|
834
|
-
secret: Schema.string().role("secret"),
|
|
835
|
-
timeoutMs: Schema.number().default(5e3),
|
|
836
|
-
verbosity: verbosity.default("normal")
|
|
935
|
+
enabled: Schema.boolean().default(false).volatile(),
|
|
936
|
+
webhookUrl: Schema.string().volatile(),
|
|
937
|
+
secret: Schema.string().role("secret").volatile(),
|
|
938
|
+
timeoutMs: Schema.number().default(5e3).volatile(),
|
|
939
|
+
verbosity: verbosity.default("normal").volatile()
|
|
837
940
|
}),
|
|
838
941
|
wecom: Schema.object({
|
|
839
|
-
enabled: Schema.boolean().default(false),
|
|
840
|
-
webhookUrl: Schema.string(),
|
|
841
|
-
secret: Schema.string().role("secret"),
|
|
842
|
-
timeoutMs: Schema.number().default(5e3),
|
|
843
|
-
verbosity: verbosity.default("normal")
|
|
942
|
+
enabled: Schema.boolean().default(false).volatile(),
|
|
943
|
+
webhookUrl: Schema.string().volatile(),
|
|
944
|
+
secret: Schema.string().role("secret").volatile(),
|
|
945
|
+
timeoutMs: Schema.number().default(5e3).volatile(),
|
|
946
|
+
verbosity: verbosity.default("normal").volatile()
|
|
844
947
|
}),
|
|
845
948
|
discord: Schema.object({
|
|
846
|
-
enabled: Schema.boolean().default(false),
|
|
847
|
-
webhookUrl: Schema.string(),
|
|
848
|
-
timeoutMs: Schema.number().default(5e3),
|
|
849
|
-
verbosity: verbosity.default("normal")
|
|
949
|
+
enabled: Schema.boolean().default(false).volatile(),
|
|
950
|
+
webhookUrl: Schema.string().volatile(),
|
|
951
|
+
timeoutMs: Schema.number().default(5e3).volatile(),
|
|
952
|
+
verbosity: verbosity.default("normal").volatile()
|
|
850
953
|
}),
|
|
851
954
|
dingtalk: Schema.object({
|
|
852
|
-
enabled: Schema.boolean().default(false),
|
|
853
|
-
webhookUrl: Schema.string(),
|
|
854
|
-
secret: Schema.string().role("secret"),
|
|
855
|
-
timeoutMs: Schema.number().default(5e3),
|
|
856
|
-
verbosity: verbosity.default("normal")
|
|
955
|
+
enabled: Schema.boolean().default(false).volatile(),
|
|
956
|
+
webhookUrl: Schema.string().volatile(),
|
|
957
|
+
secret: Schema.string().role("secret").volatile(),
|
|
958
|
+
timeoutMs: Schema.number().default(5e3).volatile(),
|
|
959
|
+
verbosity: verbosity.default("normal").volatile()
|
|
857
960
|
}),
|
|
858
961
|
telegram: Schema.object({
|
|
859
|
-
enabled: Schema.boolean().default(false),
|
|
860
|
-
botToken: Schema.string().role("secret"),
|
|
861
|
-
chatId: Schema.string(),
|
|
862
|
-
timeoutMs: Schema.number().default(5e3),
|
|
863
|
-
verbosity: verbosity.default("normal")
|
|
962
|
+
enabled: Schema.boolean().default(false).volatile(),
|
|
963
|
+
botToken: Schema.string().role("secret").volatile(),
|
|
964
|
+
chatId: Schema.string().volatile(),
|
|
965
|
+
timeoutMs: Schema.number().default(5e3).volatile(),
|
|
966
|
+
verbosity: verbosity.default("normal").volatile()
|
|
864
967
|
}),
|
|
865
968
|
dedup: Schema.object({
|
|
866
|
-
interactionCooldownMs: Schema.number().default(1e4),
|
|
867
|
-
completedDebounceMs: Schema.number().default(1e3),
|
|
868
|
-
perChannelPerMinute: Schema.number().default(20)
|
|
969
|
+
interactionCooldownMs: Schema.number().default(1e4).volatile(),
|
|
970
|
+
completedDebounceMs: Schema.number().default(1e3).volatile(),
|
|
971
|
+
perChannelPerMinute: Schema.number().default(20).volatile()
|
|
869
972
|
}),
|
|
870
973
|
message: Schema.object({
|
|
871
|
-
titlePrefix: Schema.string(),
|
|
872
|
-
includeSessionTitle: Schema.boolean().default(true),
|
|
873
|
-
guiUrl: Schema.string().default("http://127.0.0.1:3080")
|
|
974
|
+
titlePrefix: Schema.string().volatile(),
|
|
975
|
+
includeSessionTitle: Schema.boolean().default(true).volatile(),
|
|
976
|
+
guiUrl: Schema.string().default("http://127.0.0.1:3080").volatile()
|
|
874
977
|
})
|
|
875
978
|
});
|
|
876
979
|
/**
|
|
980
|
+
* 递归解开 volatile 引用,得到纯值。
|
|
981
|
+
*
|
|
982
|
+
* volatile 引用在配置热更新时**原地变更**(这正是无需重挂载的原因),
|
|
983
|
+
* 所以引用本身要保留;本插件的内部逻辑(通道构建/调度/模板)只需要某一时刻的
|
|
984
|
+
* 纯值,因此在边界处统一取值。
|
|
985
|
+
*/
|
|
986
|
+
function unwrapVolatile(value) {
|
|
987
|
+
if (isVolatile(value)) return unwrapVolatile(value.get());
|
|
988
|
+
if (Array.isArray(value)) return value.map(unwrapVolatile);
|
|
989
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, unwrapVolatile(child)]));
|
|
990
|
+
return value;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
877
993
|
* 用 schema 默认值解析一份配置(输入可省略任意字段)。
|
|
878
|
-
*
|
|
994
|
+
*
|
|
995
|
+
* 输入既可以是纯值(Loader config / settings 视图),也可以是 Loader 传入的
|
|
996
|
+
* volatile 形态 —— 两者都先解包再校验,保证「默认值 → base → 用户层」解析一致。
|
|
879
997
|
*/
|
|
880
998
|
function resolveConfig(input = {}) {
|
|
881
|
-
return Config(input);
|
|
999
|
+
return unwrapVolatile(Config(unwrapVolatile(input)));
|
|
882
1000
|
}
|
|
883
1001
|
//#endregion
|
|
884
1002
|
//#region src/config-shared.ts
|
|
@@ -893,10 +1011,19 @@ window.__ModuleLoader__.load({
|
|
|
893
1011
|
/**
|
|
894
1012
|
* 客户端配置读取:经 host 配置路由(GET /dsh-messager/config)拉取 messager
|
|
895
1013
|
* 命名空间有效值,并通过 `settings/document-updated` 失效后重拉。
|
|
896
|
-
* 路由不受 Web
|
|
1014
|
+
* 路由不受 Web 设置白名单门控,发行版同样可用。
|
|
1015
|
+
*
|
|
1016
|
+
* ⚠️ 拉取失败时的兜底值必须**关闭**通知(见 FAIL_SAFE),不能用 schema 默认值:
|
|
1017
|
+
* `browser.enabled` 的 schema 默认值是 `true`,用它兜底意味着任何一次拉取失败
|
|
1018
|
+
* 都会在用户已关闭通知的情况下静默开启推送 —— 表现为「我明明关了还弹」。
|
|
897
1019
|
*/
|
|
1020
|
+
/**
|
|
1021
|
+
* 拉取成功前的失效安全值:**显式关闭浏览器通知**。
|
|
1022
|
+
* 通知类插件的正确取向是「宁可漏报,不可误报」:未确认用户意愿前一律不推。
|
|
1023
|
+
*/
|
|
1024
|
+
const FAIL_SAFE = resolveConfig({ browser: { enabled: false } });
|
|
898
1025
|
var ClientConfig = class {
|
|
899
|
-
current =
|
|
1026
|
+
current = FAIL_SAFE;
|
|
900
1027
|
listeners = /* @__PURE__ */ new Set();
|
|
901
1028
|
get() {
|
|
902
1029
|
return this.current;
|
|
@@ -907,7 +1034,12 @@ window.__ModuleLoader__.load({
|
|
|
907
1034
|
this.listeners.delete(listener);
|
|
908
1035
|
};
|
|
909
1036
|
}
|
|
910
|
-
/**
|
|
1037
|
+
/**
|
|
1038
|
+
* 拉取 host 配置视图。
|
|
1039
|
+
*
|
|
1040
|
+
* 失败(路由未挂载 / 网络问题 / 视图非 ready)时**保持上一次的已知值**;
|
|
1041
|
+
* 若从未成功过,则保持 FAIL_SAFE(通知关闭)。
|
|
1042
|
+
*/
|
|
911
1043
|
async refresh() {
|
|
912
1044
|
try {
|
|
913
1045
|
const response = await fetch(CONFIG_PATH, { headers: { accept: "application/json" } });
|
|
@@ -922,20 +1054,16 @@ window.__ModuleLoader__.load({
|
|
|
922
1054
|
//#endregion
|
|
923
1055
|
//#region src/client/diff.ts
|
|
924
1056
|
/**
|
|
925
|
-
*
|
|
926
|
-
* 首次出现的会话只建立基线、不通知;被移除的会话不通知。
|
|
927
|
-
* @param previous - 上一次快照的 byId。
|
|
928
|
-
* @param next - 本次快照的 byId。
|
|
929
|
-
* @param current - 本次快照的当前选中会话(选中会话的完成不打扰)。
|
|
1057
|
+
* 对比两次列表快照。首次出现的会话只建立基线、不通知;被移除的会话不通知。
|
|
930
1058
|
*/
|
|
931
|
-
function diffSessionSummaries(previous, next
|
|
1059
|
+
function diffSessionSummaries(previous, next) {
|
|
932
1060
|
const notices = [];
|
|
933
1061
|
for (const id of Object.keys(next)) {
|
|
934
1062
|
const summary = next[id];
|
|
935
1063
|
if (summary === void 0) continue;
|
|
936
1064
|
const before = previous[id];
|
|
937
1065
|
if (before === void 0) continue;
|
|
938
|
-
if (before.running && !summary.running &&
|
|
1066
|
+
if (before.running && !summary.running && (summary.retainedBy.mainView ?? 0) === 0) notices.push({
|
|
939
1067
|
kind: "completed",
|
|
940
1068
|
sessionId: id,
|
|
941
1069
|
title: summary.displayTitle
|
|
@@ -953,13 +1081,20 @@ window.__ModuleLoader__.load({
|
|
|
953
1081
|
}
|
|
954
1082
|
}
|
|
955
1083
|
/**
|
|
956
|
-
*
|
|
957
|
-
*
|
|
1084
|
+
* 触发开关门控:与 host 端 `triggers` 语义对齐。
|
|
1085
|
+
*
|
|
1086
|
+
* 此前浏览器通道完全没有这层判断,导致「关闭某类触发」对浏览器通知无效 ——
|
|
1087
|
+
* 用户关掉后仍会收到(Windows 把浏览器通知渲染成系统 toast,难以分辨来源)。
|
|
958
1088
|
*/
|
|
1089
|
+
function triggerAllows(notice, triggers) {
|
|
1090
|
+
return notice.kind === "interaction" ? triggers.interaction : triggers.completed;
|
|
1091
|
+
}
|
|
1092
|
+
/** 对比两次 uiSession 统一状态快照。 */
|
|
959
1093
|
function diffPendingInteractions(previous, next, summaries) {
|
|
960
1094
|
const notices = [];
|
|
961
|
-
for (const [sessionId,
|
|
962
|
-
|
|
1095
|
+
for (const [sessionId, status] of next) {
|
|
1096
|
+
const pending = status.pendingInteraction;
|
|
1097
|
+
if (previous.get(sessionId)?.pendingInteraction !== void 0 || pending === void 0) continue;
|
|
963
1098
|
const interaction = clientInteractionKindOf(pending.kind);
|
|
964
1099
|
if (interaction === void 0) continue;
|
|
965
1100
|
const title = summaries[sessionId]?.displayTitle;
|
|
@@ -1092,8 +1227,9 @@ window.__ModuleLoader__.load({
|
|
|
1092
1227
|
status: snapshot.status,
|
|
1093
1228
|
mode: snapshot.mode ?? "",
|
|
1094
1229
|
writable: snapshot.writable,
|
|
1095
|
-
dirty: plan.length > 0,
|
|
1096
|
-
|
|
1230
|
+
dirty: plan.ops.length > 0 || plan.invalid,
|
|
1231
|
+
dirtyCount: plan.ops.length,
|
|
1232
|
+
invalid: plan.invalid,
|
|
1097
1233
|
saving: this.saving,
|
|
1098
1234
|
failed: this.failed,
|
|
1099
1235
|
fields
|
|
@@ -1154,7 +1290,11 @@ window.__ModuleLoader__.load({
|
|
|
1154
1290
|
continue;
|
|
1155
1291
|
}
|
|
1156
1292
|
const desired = parseDraft(spec, staged.text);
|
|
1157
|
-
if (desired === void 0) return
|
|
1293
|
+
if (desired === void 0) return {
|
|
1294
|
+
ops: [],
|
|
1295
|
+
run: void 0,
|
|
1296
|
+
invalid: true
|
|
1297
|
+
};
|
|
1158
1298
|
if (spec.secret !== true && deepEqualJson(desired, this.effectiveValue(spec))) continue;
|
|
1159
1299
|
ops.push({
|
|
1160
1300
|
op: "set",
|
|
@@ -1162,11 +1302,19 @@ window.__ModuleLoader__.load({
|
|
|
1162
1302
|
value: desired
|
|
1163
1303
|
});
|
|
1164
1304
|
}
|
|
1165
|
-
if (ops.length === 0) return
|
|
1305
|
+
if (ops.length === 0) return {
|
|
1306
|
+
ops: [],
|
|
1307
|
+
run: void 0,
|
|
1308
|
+
invalid: false
|
|
1309
|
+
};
|
|
1166
1310
|
const scope = this.scope;
|
|
1167
|
-
return
|
|
1168
|
-
|
|
1169
|
-
|
|
1311
|
+
return {
|
|
1312
|
+
ops,
|
|
1313
|
+
invalid: false,
|
|
1314
|
+
run: async () => {
|
|
1315
|
+
return await scope.writeOps(ops);
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1170
1318
|
}
|
|
1171
1319
|
/**
|
|
1172
1320
|
* 门控判断:hiddenUnless 指定的开关字段当前(含草稿覆盖)不为 true 时,
|
|
@@ -1185,15 +1333,11 @@ window.__ModuleLoader__.load({
|
|
|
1185
1333
|
}
|
|
1186
1334
|
async save() {
|
|
1187
1335
|
const plan = this.plan();
|
|
1188
|
-
if (plan.length === 0 || this.saving || plan.
|
|
1336
|
+
if (plan.ops.length === 0 || this.saving || plan.run === void 0) return;
|
|
1189
1337
|
this.saving = true;
|
|
1190
1338
|
this.failed = false;
|
|
1191
1339
|
this.invalidate();
|
|
1192
|
-
|
|
1193
|
-
for (const item of plan) {
|
|
1194
|
-
if (item.run === void 0) continue;
|
|
1195
|
-
landed = await item.run() && landed;
|
|
1196
|
-
}
|
|
1340
|
+
const landed = await plan.run();
|
|
1197
1341
|
if (landed) this.staged.clear();
|
|
1198
1342
|
this.saving = false;
|
|
1199
1343
|
this.failed = !landed;
|
|
@@ -1752,9 +1896,32 @@ window.__ModuleLoader__.load({
|
|
|
1752
1896
|
alignItems: "center",
|
|
1753
1897
|
justifyContent: "flex-end",
|
|
1754
1898
|
gap: 8,
|
|
1755
|
-
padding: "12px 0
|
|
1899
|
+
padding: "12px 0",
|
|
1756
1900
|
borderTop: "1px solid var(--dsw-alias-border-l2)",
|
|
1757
|
-
marginTop: 12
|
|
1901
|
+
marginTop: 12,
|
|
1902
|
+
position: "sticky",
|
|
1903
|
+
bottom: 0,
|
|
1904
|
+
zIndex: 1,
|
|
1905
|
+
background: "var(--dsw-alias-bg-layer-2)"
|
|
1906
|
+
};
|
|
1907
|
+
/** 顶部「有未保存的修改」提示条(滚到顶部时可见,与底部粘性操作栏互补)。 */
|
|
1908
|
+
const UNSAVED_BAR_STYLE = {
|
|
1909
|
+
display: "flex",
|
|
1910
|
+
alignItems: "center",
|
|
1911
|
+
justifyContent: "space-between",
|
|
1912
|
+
gap: 12,
|
|
1913
|
+
margin: "12px 0 0",
|
|
1914
|
+
padding: "8px 12px",
|
|
1915
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1916
|
+
borderRadius: 8,
|
|
1917
|
+
background: "var(--dsw-alias-bg-module-platform)"
|
|
1918
|
+
};
|
|
1919
|
+
const UNSAVED_TEXT_STYLE = {
|
|
1920
|
+
margin: 0,
|
|
1921
|
+
fontSize: 13,
|
|
1922
|
+
lineHeight: 1.5,
|
|
1923
|
+
fontWeight: 500,
|
|
1924
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1758
1925
|
};
|
|
1759
1926
|
const FAILED_STYLE = {
|
|
1760
1927
|
flex: 1,
|
|
@@ -1952,6 +2119,26 @@ window.__ModuleLoader__.load({
|
|
|
1952
2119
|
role: "status",
|
|
1953
2120
|
children: t("status.readOnly")
|
|
1954
2121
|
}),
|
|
2122
|
+
state.dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2123
|
+
style: UNSAVED_BAR_STYLE,
|
|
2124
|
+
role: "status",
|
|
2125
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2126
|
+
style: UNSAVED_TEXT_STYLE,
|
|
2127
|
+
children: [state.invalid ? t("status.invalidInput") : t("status.unsaved"), !state.invalid && state.dirtyCount > 0 ? ` (${state.dirtyCount})` : ""]
|
|
2128
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2129
|
+
type: "button",
|
|
2130
|
+
style: {
|
|
2131
|
+
...SAVE_STYLE,
|
|
2132
|
+
...state.invalid || state.saving ? {
|
|
2133
|
+
opacity: .4,
|
|
2134
|
+
cursor: "default"
|
|
2135
|
+
} : {}
|
|
2136
|
+
},
|
|
2137
|
+
disabled: state.invalid || state.saving,
|
|
2138
|
+
onClick: actions.save,
|
|
2139
|
+
children: state.saving ? t("action.saving") : t("action.save")
|
|
2140
|
+
})]
|
|
2141
|
+
}),
|
|
1955
2142
|
groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1956
2143
|
style: GROUP_HEADING_STYLE,
|
|
1957
2144
|
children: t(GROUP_TITLE_KEYS[group] ?? group)
|
|
@@ -1981,6 +2168,14 @@ window.__ModuleLoader__.load({
|
|
|
1981
2168
|
role: "status",
|
|
1982
2169
|
children: t("status.invalidInput")
|
|
1983
2170
|
}),
|
|
2171
|
+
state.dirty && !state.failed && !state.invalid && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2172
|
+
style: {
|
|
2173
|
+
...UNSAVED_TEXT_STYLE,
|
|
2174
|
+
flex: 1
|
|
2175
|
+
},
|
|
2176
|
+
role: "status",
|
|
2177
|
+
children: [t("status.unsaved"), state.dirtyCount > 0 ? ` (${state.dirtyCount})` : ""]
|
|
2178
|
+
}),
|
|
1984
2179
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1985
2180
|
type: "button",
|
|
1986
2181
|
style: {
|
|
@@ -2128,6 +2323,7 @@ window.__ModuleLoader__.load({
|
|
|
2128
2323
|
"action.discard": "放弃修改",
|
|
2129
2324
|
"action.reset": "重置",
|
|
2130
2325
|
"badge.overridden": "已覆盖",
|
|
2326
|
+
"status.unsaved": "有未保存的修改",
|
|
2131
2327
|
"status.readOnly": "设置文档当前为只读。",
|
|
2132
2328
|
"status.saveFailed": "保存失败,请重试。",
|
|
2133
2329
|
"status.invalidInput": "存在无效输入,请修正后再保存。",
|
|
@@ -2191,6 +2387,7 @@ window.__ModuleLoader__.load({
|
|
|
2191
2387
|
"action.discard": "Discard",
|
|
2192
2388
|
"action.reset": "Reset",
|
|
2193
2389
|
"badge.overridden": "Overridden",
|
|
2390
|
+
"status.unsaved": "Unsaved changes",
|
|
2194
2391
|
"status.readOnly": "The settings document is read-only.",
|
|
2195
2392
|
"status.saveFailed": "Save failed, please retry.",
|
|
2196
2393
|
"status.invalidInput": "There are invalid inputs, fix them before saving.",
|
|
@@ -2225,9 +2422,9 @@ window.__ModuleLoader__.load({
|
|
|
2225
2422
|
}
|
|
2226
2423
|
/** 列表快照变化 → 通知。 */
|
|
2227
2424
|
onListChange(previous, next) {
|
|
2228
|
-
this.onNotices(diffSessionSummaries(previous.byId, next.byId
|
|
2425
|
+
this.onNotices(diffSessionSummaries(previous.byId, next.byId));
|
|
2229
2426
|
}
|
|
2230
|
-
/**
|
|
2427
|
+
/** 待交互状态变化 → 通知。 */
|
|
2231
2428
|
onPendingChange(previous, next, sessions) {
|
|
2232
2429
|
this.onNotices(diffPendingInteractions(previous, next, sessions.byId));
|
|
2233
2430
|
}
|
|
@@ -2238,6 +2435,7 @@ window.__ModuleLoader__.load({
|
|
|
2238
2435
|
if (Notification.permission !== "granted") return;
|
|
2239
2436
|
if (config.browser.onlyWhenHidden && document.visibilityState !== "hidden") return;
|
|
2240
2437
|
for (const notice of notices) {
|
|
2438
|
+
if (!triggerAllows(notice, config.triggers)) continue;
|
|
2241
2439
|
if (!this.allow(notice, config)) continue;
|
|
2242
2440
|
this.show(notice, config);
|
|
2243
2441
|
}
|
|
@@ -2320,6 +2518,13 @@ window.__ModuleLoader__.load({
|
|
|
2320
2518
|
fetchScope.refresh();
|
|
2321
2519
|
});
|
|
2322
2520
|
ctx.effect(() => () => offRemote?.(), "dsh-messager: settings invalidation");
|
|
2521
|
+
const onVisibilityChange = () => {
|
|
2522
|
+
if (document.visibilityState !== "visible") return;
|
|
2523
|
+
config.refresh();
|
|
2524
|
+
fetchScope.refresh();
|
|
2525
|
+
};
|
|
2526
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
2527
|
+
ctx.effect(() => () => document.removeEventListener("visibilitychange", onVisibilityChange), "dsh-messager: visibility refresh");
|
|
2323
2528
|
const notifier = new BrowserNotifier(config);
|
|
2324
2529
|
ctx.effect(() => () => notifier.dispose(), "dsh-messager: browser notifier");
|
|
2325
2530
|
let previous = ctx.sessions.list.getSnapshot();
|
|
@@ -2329,13 +2534,13 @@ window.__ModuleLoader__.load({
|
|
|
2329
2534
|
previous = next;
|
|
2330
2535
|
});
|
|
2331
2536
|
ctx.effect(() => () => offList(), "dsh-messager: sessions subscription");
|
|
2332
|
-
let
|
|
2333
|
-
const
|
|
2334
|
-
const next = ctx.uiSession.
|
|
2335
|
-
notifier.onPendingChange(
|
|
2336
|
-
|
|
2537
|
+
let previousStatus = ctx.uiSession.sessionStatus.getSnapshot();
|
|
2538
|
+
const offStatus = ctx.uiSession.sessionStatus.subscribe(() => {
|
|
2539
|
+
const next = ctx.uiSession.sessionStatus.getSnapshot();
|
|
2540
|
+
notifier.onPendingChange(previousStatus, next, ctx.sessions.list.getSnapshot());
|
|
2541
|
+
previousStatus = next;
|
|
2337
2542
|
});
|
|
2338
|
-
ctx.effect(() => () =>
|
|
2543
|
+
ctx.effect(() => () => offStatus(), "dsh-messager: session status subscription");
|
|
2339
2544
|
const controller = new MessagerCardController(fetchScope.scope, CARD_FIELDS);
|
|
2340
2545
|
ctx.effect(() => ctx.locale.register(LOCALE_NS, {
|
|
2341
2546
|
zh,
|