dsh-plugin-shop 0.4.0 → 0.4.2
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 +3 -3
- package/lib/client.js +348 -60
- package/lib/index.js +144 -36
- package/lib/typert.host.js +76 -28
- package/lib/typert.remote-client.d.ts +5 -3
- package/lib/typert.remote-client.js +57 -20
- package/lib/types/client/ShopTab.d.ts +5 -2
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/client/present.d.ts +22 -0
- package/lib/types/client/useInstall.d.ts +9 -0
- package/lib/types/client/useUninstall.d.ts +21 -0
- package/lib/types/host/catalog.d.ts +6 -1
- package/lib/types/host/executor.d.ts +18 -6
- package/lib/types/host/index.d.ts +38 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -4306,6 +4306,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4306
4306
|
})),
|
|
4307
4307
|
"stars": record(string(), number())
|
|
4308
4308
|
});
|
|
4309
|
+
const dsh_plugin_shop_shop_installed_result$schema = array(object({
|
|
4310
|
+
"name": string(),
|
|
4311
|
+
"installed": string(),
|
|
4312
|
+
"latest": string(),
|
|
4313
|
+
"outdated": boolean()
|
|
4314
|
+
}));
|
|
4309
4315
|
const dsh_plugin_shop_shop_installStart_parameter_0$schema = object({
|
|
4310
4316
|
"name": string(),
|
|
4311
4317
|
"version": string(),
|
|
@@ -4344,11 +4350,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4344
4350
|
]).optional(),
|
|
4345
4351
|
"detail": union([_undefined(), string()]).optional()
|
|
4346
4352
|
});
|
|
4347
|
-
const dsh_plugin_shop_shop_outdated_result$schema = array(object({
|
|
4348
|
-
"name": string(),
|
|
4349
|
-
"installed": string(),
|
|
4350
|
-
"latest": string()
|
|
4351
|
-
}));
|
|
4352
4353
|
const dsh_plugin_shop_shop_setEnabled_parameter_0$schema = object({
|
|
4353
4354
|
"name": string(),
|
|
4354
4355
|
"enabled": boolean()
|
|
@@ -4357,6 +4358,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4357
4358
|
"ok": boolean(),
|
|
4358
4359
|
"detail": union([_undefined(), string()]).optional()
|
|
4359
4360
|
});
|
|
4361
|
+
const dsh_plugin_shop_shop_uninstallStart_parameter_0$schema = object({ "name": string() });
|
|
4362
|
+
const dsh_plugin_shop_shop_uninstallStart_result$schema = union([object({
|
|
4363
|
+
"ok": literal(true),
|
|
4364
|
+
"installId": string()
|
|
4365
|
+
}), object({
|
|
4366
|
+
"ok": literal(false),
|
|
4367
|
+
"detail": string()
|
|
4368
|
+
})]);
|
|
4360
4369
|
const TYPERT_REMOTE = {
|
|
4361
4370
|
package: "dsh-plugin-shop",
|
|
4362
4371
|
descriptors: [
|
|
@@ -4384,7 +4393,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4384
4393
|
},
|
|
4385
4394
|
sourceLocation: {
|
|
4386
4395
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4387
|
-
"line":
|
|
4396
|
+
"line": 190,
|
|
4397
|
+
"column": 9
|
|
4398
|
+
}
|
|
4399
|
+
},
|
|
4400
|
+
{
|
|
4401
|
+
id: "dsh-plugin-shop#shop/installed",
|
|
4402
|
+
service: "shop",
|
|
4403
|
+
namespace: "shop",
|
|
4404
|
+
method: "installed",
|
|
4405
|
+
invocation: { kind: "direct" },
|
|
4406
|
+
parameters: [],
|
|
4407
|
+
result: {
|
|
4408
|
+
mode: "strict",
|
|
4409
|
+
typeSymbol: "dsh-plugin-shop#shop/installed:result",
|
|
4410
|
+
schema: dsh_plugin_shop_shop_installed_result$schema
|
|
4411
|
+
},
|
|
4412
|
+
sourceLocation: {
|
|
4413
|
+
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4414
|
+
"line": 265,
|
|
4388
4415
|
"column": 9
|
|
4389
4416
|
}
|
|
4390
4417
|
},
|
|
@@ -4412,7 +4439,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4412
4439
|
},
|
|
4413
4440
|
sourceLocation: {
|
|
4414
4441
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4415
|
-
"line":
|
|
4442
|
+
"line": 216,
|
|
4416
4443
|
"column": 9
|
|
4417
4444
|
}
|
|
4418
4445
|
},
|
|
@@ -4439,33 +4466,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4439
4466
|
},
|
|
4440
4467
|
sourceLocation: {
|
|
4441
4468
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4442
|
-
"line":
|
|
4469
|
+
"line": 254,
|
|
4443
4470
|
"column": 3
|
|
4444
4471
|
}
|
|
4445
4472
|
},
|
|
4446
4473
|
{
|
|
4447
|
-
id: "dsh-plugin-shop#shop/
|
|
4474
|
+
id: "dsh-plugin-shop#shop/setEnabled",
|
|
4448
4475
|
service: "shop",
|
|
4449
4476
|
namespace: "shop",
|
|
4450
|
-
method: "
|
|
4477
|
+
method: "setEnabled",
|
|
4451
4478
|
invocation: { kind: "direct" },
|
|
4452
|
-
parameters: [
|
|
4479
|
+
parameters: [{
|
|
4480
|
+
name: "args",
|
|
4481
|
+
wire: "args",
|
|
4482
|
+
source: "json",
|
|
4483
|
+
codec: {
|
|
4484
|
+
mode: "strict",
|
|
4485
|
+
typeSymbol: "dsh-plugin-shop#shop/setEnabled:args",
|
|
4486
|
+
schema: dsh_plugin_shop_shop_setEnabled_parameter_0$schema
|
|
4487
|
+
}
|
|
4488
|
+
}],
|
|
4453
4489
|
result: {
|
|
4454
4490
|
mode: "strict",
|
|
4455
|
-
typeSymbol: "dsh-plugin-shop#
|
|
4456
|
-
schema:
|
|
4491
|
+
typeSymbol: "dsh-plugin-shop/types#ShopSetEnabledResult",
|
|
4492
|
+
schema: dsh_plugin_shop_shop_setEnabled_result$schema
|
|
4457
4493
|
},
|
|
4458
4494
|
sourceLocation: {
|
|
4459
4495
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4460
|
-
"line":
|
|
4461
|
-
"column":
|
|
4496
|
+
"line": 159,
|
|
4497
|
+
"column": 3
|
|
4462
4498
|
}
|
|
4463
4499
|
},
|
|
4464
4500
|
{
|
|
4465
|
-
id: "dsh-plugin-shop#shop/
|
|
4501
|
+
id: "dsh-plugin-shop#shop/uninstallStart",
|
|
4466
4502
|
service: "shop",
|
|
4467
4503
|
namespace: "shop",
|
|
4468
|
-
method: "
|
|
4504
|
+
method: "uninstallStart",
|
|
4505
|
+
implementation: "uninstall",
|
|
4469
4506
|
invocation: { kind: "direct" },
|
|
4470
4507
|
parameters: [{
|
|
4471
4508
|
name: "args",
|
|
@@ -4473,19 +4510,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4473
4510
|
source: "json",
|
|
4474
4511
|
codec: {
|
|
4475
4512
|
mode: "strict",
|
|
4476
|
-
typeSymbol: "dsh-plugin-shop#shop/
|
|
4477
|
-
schema:
|
|
4513
|
+
typeSymbol: "dsh-plugin-shop#shop/uninstallStart:args",
|
|
4514
|
+
schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema
|
|
4478
4515
|
}
|
|
4479
4516
|
}],
|
|
4480
4517
|
result: {
|
|
4481
4518
|
mode: "strict",
|
|
4482
|
-
typeSymbol: "dsh-plugin-shop/types#
|
|
4483
|
-
schema:
|
|
4519
|
+
typeSymbol: "dsh-plugin-shop/types#ShopUninstallResult",
|
|
4520
|
+
schema: dsh_plugin_shop_shop_uninstallStart_result$schema
|
|
4484
4521
|
},
|
|
4485
4522
|
sourceLocation: {
|
|
4486
4523
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4487
|
-
"line":
|
|
4488
|
-
"column":
|
|
4524
|
+
"line": 305,
|
|
4525
|
+
"column": 9
|
|
4489
4526
|
}
|
|
4490
4527
|
}
|
|
4491
4528
|
]
|
|
@@ -4590,6 +4627,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4590
4627
|
const segments = name.toLowerCase().split(/[-_.]+/);
|
|
4591
4628
|
const hasPlugin = segments.includes("plugin");
|
|
4592
4629
|
if (segments.some((segment) => /plugin(store|market|mall|shop|marketplace)/.test(segment) || /(store|market|mall|shop|marketplace)plugin/.test(segment))) return true;
|
|
4630
|
+
if (segments.some((segment) => segment.startsWith("dsh") && SHOP_KEYWORDS.some((keyword) => segment === `dsh${keyword}`))) return true;
|
|
4631
|
+
if (segments.length >= 2 && (segments[0] === "dsh" || segments[0]?.endsWith("/dsh") === true) && SHOP_KEYWORDS.includes(segments[1]) && (segments.length === 2 || segments[2] !== void 0 && [
|
|
4632
|
+
"plus",
|
|
4633
|
+
"pro",
|
|
4634
|
+
"hub",
|
|
4635
|
+
"center",
|
|
4636
|
+
"centre",
|
|
4637
|
+
"max",
|
|
4638
|
+
"free",
|
|
4639
|
+
"lite"
|
|
4640
|
+
].includes(segments[2]))) return true;
|
|
4593
4641
|
if (segments.filter((segment) => SHOP_KEYWORDS.includes(segment)).length === 0) return false;
|
|
4594
4642
|
return hasPlugin || SHOP_KEYWORDS.includes(segments.at(-1));
|
|
4595
4643
|
}
|
|
@@ -4606,6 +4654,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4606
4654
|
function categoryKey(entry) {
|
|
4607
4655
|
return CATEGORY_KEYS[entry.catalog?.category ?? "other"];
|
|
4608
4656
|
}
|
|
4657
|
+
/** The six categories in display order (map insertion order). */
|
|
4658
|
+
const CATEGORY_ORDER = Object.keys(CATEGORY_KEYS);
|
|
4659
|
+
/** The locale key for one bare category value (the filter buttons). */
|
|
4660
|
+
function categoryLocaleKey(category) {
|
|
4661
|
+
return CATEGORY_KEYS[category];
|
|
4662
|
+
}
|
|
4609
4663
|
/** Sort the shelf: stars descending, un-starred entries last, name ascending
|
|
4610
4664
|
* (case-insensitive) on ties (spec 2026-08-26-github-stars-design.md D1).
|
|
4611
4665
|
* Display-time only — the catalog's own name sort is untouched. */
|
|
@@ -4632,6 +4686,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4632
4686
|
error: "暂时无法读取目录。",
|
|
4633
4687
|
retry: "重试",
|
|
4634
4688
|
search: "搜索插件",
|
|
4689
|
+
all: "全部",
|
|
4635
4690
|
catalog: "插件目录",
|
|
4636
4691
|
catalogStats: "{count} 个插件 · 构建于 {date}",
|
|
4637
4692
|
stars: "{count} 星",
|
|
@@ -4673,6 +4728,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4673
4728
|
installedVersion: "已安装 v{version}",
|
|
4674
4729
|
latestVersion: "最新 v{version}",
|
|
4675
4730
|
update: "更新",
|
|
4731
|
+
installed: "已安装",
|
|
4732
|
+
uninstall: "卸载",
|
|
4733
|
+
uninstalling: "正在卸载…",
|
|
4734
|
+
uninstallFailed: "卸载失败",
|
|
4735
|
+
uninstallTransportFailed: "卸载请求未能送达。请稍后重试。",
|
|
4736
|
+
uninstalledRestartNotice: "已卸载;重启 dsh 后生效",
|
|
4676
4737
|
enabledSwitch: "启用",
|
|
4677
4738
|
toggleFailed: "设置失败,请重试。",
|
|
4678
4739
|
hotApplyNote: "无需重启,立即生效"
|
|
@@ -4684,6 +4745,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4684
4745
|
error: "The catalog is temporarily unavailable.",
|
|
4685
4746
|
retry: "Retry",
|
|
4686
4747
|
search: "Search plugins",
|
|
4748
|
+
all: "All",
|
|
4687
4749
|
catalog: "Plugin catalog",
|
|
4688
4750
|
catalogStats: "{count} packages · built {date}",
|
|
4689
4751
|
stars: "{count} stars",
|
|
@@ -4725,6 +4787,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4725
4787
|
installedVersion: "installed v{version}",
|
|
4726
4788
|
latestVersion: "latest v{version}",
|
|
4727
4789
|
update: "Update",
|
|
4790
|
+
installed: "Installed",
|
|
4791
|
+
uninstall: "Uninstall",
|
|
4792
|
+
uninstalling: "Uninstalling…",
|
|
4793
|
+
uninstallFailed: "Uninstall failed",
|
|
4794
|
+
uninstallTransportFailed: "The uninstall request could not be delivered. Please retry.",
|
|
4795
|
+
uninstalledRestartNotice: "uninstalled; restart dsh to activate",
|
|
4728
4796
|
enabledSwitch: "Enable",
|
|
4729
4797
|
toggleFailed: "Failed to update. Please retry.",
|
|
4730
4798
|
hotApplyNote: "takes effect without a restart"
|
|
@@ -4732,6 +4800,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4732
4800
|
//#endregion
|
|
4733
4801
|
//#region src/client/useInstall.ts
|
|
4734
4802
|
/** Install driving hook for one entry: start, poll to terminal, reset. */
|
|
4803
|
+
/** The poll loop shared by the install and uninstall drivers: while the view
|
|
4804
|
+
* is `running`, poll once per second and fold each status through the
|
|
4805
|
+
* reducer. A poll failure is transient — the host retains the record, so the
|
|
4806
|
+
* next tick finds it. The rejection handler must be present: an unhandled
|
|
4807
|
+
* rejection here would escape the poll loop. */
|
|
4808
|
+
function usePollStatus(view, setView, installStatus) {
|
|
4809
|
+
(0, react.useEffect)(() => {
|
|
4810
|
+
if (view.kind !== "running") return;
|
|
4811
|
+
const timer = setInterval(() => {
|
|
4812
|
+
installStatus({ installId: view.installId }).then((status) => {
|
|
4813
|
+
setView((current) => reduceInstall(current, {
|
|
4814
|
+
type: "status",
|
|
4815
|
+
status
|
|
4816
|
+
}));
|
|
4817
|
+
}, () => {});
|
|
4818
|
+
}, INSTALL_POLL_MS);
|
|
4819
|
+
return () => clearInterval(timer);
|
|
4820
|
+
}, [
|
|
4821
|
+
view,
|
|
4822
|
+
setView,
|
|
4823
|
+
installStatus
|
|
4824
|
+
]);
|
|
4825
|
+
}
|
|
4735
4826
|
/** Drive one install: rejections, the polling loop at INSTALL_POLL_MS, and
|
|
4736
4827
|
* terminal states. The interval is cleared on unmount and whenever the view
|
|
4737
4828
|
* leaves `running`, so a done/failed/rejected install never polls again. */
|
|
@@ -4762,18 +4853,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4762
4853
|
});
|
|
4763
4854
|
}
|
|
4764
4855
|
}, [install]);
|
|
4765
|
-
(
|
|
4766
|
-
if (view.kind !== "running") return;
|
|
4767
|
-
const timer = setInterval(() => {
|
|
4768
|
-
installStatus({ installId: view.installId }).then((status) => {
|
|
4769
|
-
setView((current) => reduceInstall(current, {
|
|
4770
|
-
type: "status",
|
|
4771
|
-
status
|
|
4772
|
-
}));
|
|
4773
|
-
}, () => {});
|
|
4774
|
-
}, INSTALL_POLL_MS);
|
|
4775
|
-
return () => clearInterval(timer);
|
|
4776
|
-
}, [view, installStatus]);
|
|
4856
|
+
usePollStatus(view, setView, installStatus);
|
|
4777
4857
|
return {
|
|
4778
4858
|
view,
|
|
4779
4859
|
start,
|
|
@@ -4781,8 +4861,51 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4781
4861
|
};
|
|
4782
4862
|
}
|
|
4783
4863
|
//#endregion
|
|
4864
|
+
//#region src/client/useUninstall.ts
|
|
4865
|
+
/** Uninstall driving hook for one entry: start, poll to terminal. Shares the
|
|
4866
|
+
* install view machine and poll loop; the only difference is the start
|
|
4867
|
+
* mapping — an uninstall business failure (not in the catalog / not
|
|
4868
|
+
* installed) lands in the `failed` view with the host's published detail,
|
|
4869
|
+
* never in the install `rejected` codes, which belong to the install gate
|
|
4870
|
+
* (§7.2). */
|
|
4871
|
+
/** Drive one uninstall: the host's business union, the shared polling loop,
|
|
4872
|
+
* and terminal states. The `rejected` install state never occurs here. */
|
|
4873
|
+
function useUninstall(uninstall, installStatus) {
|
|
4874
|
+
const [view, setView] = (0, react.useState)({ kind: "idle" });
|
|
4875
|
+
const start = (0, react.useCallback)(async (args) => {
|
|
4876
|
+
setView({ kind: "idle" });
|
|
4877
|
+
try {
|
|
4878
|
+
const result = await uninstall(args);
|
|
4879
|
+
if (!result.ok) {
|
|
4880
|
+
setView({
|
|
4881
|
+
kind: "failed",
|
|
4882
|
+
detail: result.detail,
|
|
4883
|
+
log: []
|
|
4884
|
+
});
|
|
4885
|
+
return;
|
|
4886
|
+
}
|
|
4887
|
+
setView({
|
|
4888
|
+
kind: "running",
|
|
4889
|
+
installId: result.installId,
|
|
4890
|
+
log: []
|
|
4891
|
+
});
|
|
4892
|
+
} catch {
|
|
4893
|
+
setView({
|
|
4894
|
+
kind: "failed",
|
|
4895
|
+
detail: "",
|
|
4896
|
+
log: []
|
|
4897
|
+
});
|
|
4898
|
+
}
|
|
4899
|
+
}, [uninstall]);
|
|
4900
|
+
usePollStatus(view, setView, installStatus);
|
|
4901
|
+
return {
|
|
4902
|
+
view,
|
|
4903
|
+
start
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
//#endregion
|
|
4784
4907
|
//#region \0dsh-plugin-shop-css:e3675a89
|
|
4785
|
-
const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (six fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so six brand opacities read as six shades\n * of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s30e662_skeletonCover {\n display: block;\n height: 26px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent);\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 12px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 14px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.s668774_catalogHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sccc4d1_catalogStats {\n margin: -8px 0 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 240px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n.s9e2bec_cardCover {\n display: block;\n padding: 5px 12px 5px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--spine-hue, #8B8E96) 12%, transparent);\n}\n\n.s1a7c5b_coverLabel {\n display: block;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 10px 12px 6px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name owns the full header width; badges sit on their own line below. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.s23e3e1_unclaimedBadge {\n padding: 1px 7px;\n border-radius: 999px;\n border: 1px dashed var(--dsw-alias-border-l2);\n font-size: 11px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n";
|
|
4908
|
+
const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (six fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so six brand opacities read as six shades\n * of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s30e662_skeletonCover {\n display: block;\n height: 26px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent);\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 12px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 14px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s26d58e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sccc4d1_catalogStats {\n margin: 6px 0 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 240px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n.s9e2bec_cardCover {\n display: block;\n padding: 5px 12px 5px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--spine-hue, #8B8E96) 12%, transparent);\n}\n\n.s1a7c5b_coverLabel {\n display: block;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 10px 12px 6px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name owns the full header width; badges sit on their own line below. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.s23e3e1_unclaimedBadge {\n padding: 1px 7px;\n border-radius: 999px;\n border: 1px dashed var(--dsw-alias-border-l2);\n font-size: 11px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n";
|
|
4786
4909
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
4787
4910
|
const tag = document.createElement("style");
|
|
4788
4911
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -4808,6 +4931,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4808
4931
|
"searchInput": "s232ec4_searchInput",
|
|
4809
4932
|
"staleBlock": "s1555aa_staleBlock",
|
|
4810
4933
|
"staleBadge": "s3946f1_staleBadge",
|
|
4934
|
+
"categoryBar": "sb2c859_categoryBar",
|
|
4935
|
+
"categoryButton": "s26d58e_categoryButton",
|
|
4936
|
+
"categoryButtonOn": "s5f68c4_categoryButtonOn",
|
|
4811
4937
|
"catalogHeading": "s668774_catalogHeading",
|
|
4812
4938
|
"catalogStats": "sccc4d1_catalogStats",
|
|
4813
4939
|
"emptyLine": "s6de1ab_emptyLine",
|
|
@@ -4829,6 +4955,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4829
4955
|
"body": "s820ebf_body",
|
|
4830
4956
|
"summary": "sb8fbc8_summary",
|
|
4831
4957
|
"summaryZh": "se507db_summaryZh",
|
|
4958
|
+
"summaryExpanded": "s10baf0_summaryExpanded",
|
|
4959
|
+
"summaryZhExpanded": "sb9dba9_summaryZhExpanded",
|
|
4832
4960
|
"capabilitiesBlock": "s9d2029_capabilitiesBlock",
|
|
4833
4961
|
"capabilitiesNote": "s3bdbd5_capabilitiesNote",
|
|
4834
4962
|
"capabilities": "s441702_capabilities",
|
|
@@ -4840,6 +4968,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4840
4968
|
"installButton": "se7aaed_installButton",
|
|
4841
4969
|
"confirmButton": "sb43a49_confirmButton",
|
|
4842
4970
|
"cancelButton": "s6e45d1_cancelButton",
|
|
4971
|
+
"installedLabel": "sb6fc74_installedLabel",
|
|
4972
|
+
"installedActions": "sf53ca0_installedActions",
|
|
4973
|
+
"uninstallButton": "s96967f_uninstallButton",
|
|
4843
4974
|
"gate": "sc4f2ac_gate",
|
|
4844
4975
|
"gateTitle": "s559c38_gateTitle",
|
|
4845
4976
|
"gateBody": "s3c0ace_gateBody",
|
|
@@ -4885,8 +5016,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4885
5016
|
/** One entry card: the category spine and cover band, the expandable header
|
|
4886
5017
|
* (name, tier, category, unclaimed marker), the plain-text summary in both
|
|
4887
5018
|
* languages, the self-declared capabilities, the detail section, and the
|
|
4888
|
-
* install controls.
|
|
4889
|
-
|
|
5019
|
+
* install controls. An installed plugin's card carries its installed row:
|
|
5020
|
+
* current → the non-interactive installed label, behind → the update button;
|
|
5021
|
+
* uninstalled → the install button. */
|
|
5022
|
+
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, t, install, installStatus, uninstall }) {
|
|
4890
5023
|
const [open, setOpen] = (0, react.useState)(false);
|
|
4891
5024
|
const detailId = (0, react.useId)();
|
|
4892
5025
|
const summary = entry.catalog?.summary;
|
|
@@ -4943,11 +5076,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4943
5076
|
className: _dsh_plugin_shop_css_e3675a89_default.body,
|
|
4944
5077
|
children: [
|
|
4945
5078
|
summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4946
|
-
className: _dsh_plugin_shop_css_e3675a89_default.summary,
|
|
5079
|
+
className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summary} ${_dsh_plugin_shop_css_e3675a89_default.summaryExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summary,
|
|
4947
5080
|
children: summary.en
|
|
4948
5081
|
}),
|
|
4949
5082
|
summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4950
|
-
className: _dsh_plugin_shop_css_e3675a89_default.summaryZh,
|
|
5083
|
+
className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summaryZh,
|
|
4951
5084
|
children: summary.zh
|
|
4952
5085
|
}),
|
|
4953
5086
|
entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4992,13 +5125,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4992
5125
|
})
|
|
4993
5126
|
})]
|
|
4994
5127
|
}),
|
|
4995
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5128
|
+
installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
4996
5129
|
name: entry.name,
|
|
4997
5130
|
version: entry.version,
|
|
4998
5131
|
tier: entry.tier,
|
|
4999
5132
|
t,
|
|
5000
5133
|
install,
|
|
5001
5134
|
installStatus
|
|
5135
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5136
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
|
|
5137
|
+
children: [installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5138
|
+
name: entry.name,
|
|
5139
|
+
version: entry.version,
|
|
5140
|
+
tier: entry.tier,
|
|
5141
|
+
variant: "update",
|
|
5142
|
+
t,
|
|
5143
|
+
install,
|
|
5144
|
+
installStatus
|
|
5145
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5146
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
|
|
5147
|
+
"data-shop-installed": true,
|
|
5148
|
+
children: t("installed")
|
|
5149
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
5150
|
+
name: entry.name,
|
|
5151
|
+
t,
|
|
5152
|
+
uninstall,
|
|
5153
|
+
installStatus
|
|
5154
|
+
})]
|
|
5002
5155
|
})
|
|
5003
5156
|
]
|
|
5004
5157
|
})
|
|
@@ -5112,6 +5265,61 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5112
5265
|
children: t(update ? "update" : "install")
|
|
5113
5266
|
});
|
|
5114
5267
|
}
|
|
5268
|
+
/** One installed entry's uninstall flow: remove from the profile through the
|
|
5269
|
+
* same executor records and poll loop as installs. Uninstalling revokes
|
|
5270
|
+
* privilege rather than granting it, so there is no acknowledgement gate —
|
|
5271
|
+
* §9.3 is about granting. A business failure (not in the catalog / not
|
|
5272
|
+
* installed) lands in the failed view with the host's published detail; a
|
|
5273
|
+
* transport failure carries the empty detail and the localized fallback. */
|
|
5274
|
+
function UninstallPanel({ name, t, uninstall, installStatus }) {
|
|
5275
|
+
const { view, start } = useUninstall(uninstall, installStatus);
|
|
5276
|
+
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5277
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
|
|
5278
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5279
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installing,
|
|
5280
|
+
children: t("uninstalling")
|
|
5281
|
+
}), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5282
|
+
className: _dsh_plugin_shop_css_e3675a89_default.log,
|
|
5283
|
+
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5284
|
+
className: _dsh_plugin_shop_css_e3675a89_default.logLine,
|
|
5285
|
+
children: line
|
|
5286
|
+
}, index))
|
|
5287
|
+
})]
|
|
5288
|
+
});
|
|
5289
|
+
if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5290
|
+
className: _dsh_plugin_shop_css_e3675a89_default.notice,
|
|
5291
|
+
"data-shop-uninstall-done": true,
|
|
5292
|
+
children: t("uninstalledRestartNotice")
|
|
5293
|
+
});
|
|
5294
|
+
if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5295
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
|
|
5296
|
+
children: [
|
|
5297
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5298
|
+
className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
|
|
5299
|
+
children: t("uninstallFailed")
|
|
5300
|
+
}),
|
|
5301
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5302
|
+
className: _dsh_plugin_shop_css_e3675a89_default.log,
|
|
5303
|
+
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5304
|
+
className: _dsh_plugin_shop_css_e3675a89_default.logLine,
|
|
5305
|
+
children: line
|
|
5306
|
+
}, index))
|
|
5307
|
+
}),
|
|
5308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5309
|
+
className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
|
|
5310
|
+
children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
|
|
5311
|
+
})
|
|
5312
|
+
]
|
|
5313
|
+
});
|
|
5314
|
+
if (view.kind !== "idle") return null;
|
|
5315
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5316
|
+
type: "button",
|
|
5317
|
+
className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
|
|
5318
|
+
"data-shop-uninstall": true,
|
|
5319
|
+
onClick: () => void start({ name }),
|
|
5320
|
+
children: t("uninstall")
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
5115
5323
|
/** One outdated install row (§7.3): the name, the installed and latest
|
|
5116
5324
|
* versions, an optimistic enable/disable switch, and the update button (the
|
|
5117
5325
|
* install flow for `name@latest`, reusing `InstallPanel`). The switch has no
|
|
@@ -5199,10 +5407,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5199
5407
|
]
|
|
5200
5408
|
});
|
|
5201
5409
|
}
|
|
5202
|
-
/** The §7.3
|
|
5203
|
-
* both versions, a switch, and an update button. The
|
|
5204
|
-
*
|
|
5205
|
-
*
|
|
5410
|
+
/** The §7.3 installed list, rendered as the "installed" section: each row
|
|
5411
|
+
* shows both versions, a switch, and an update button. The rows are the
|
|
5412
|
+
* installed entries filtered to `outdated` — a current install is already
|
|
5413
|
+
* spoken for by its shelf card's installed label, and has no row here. The
|
|
5414
|
+
* tier for the update gate is looked up from the catalog by name (community →
|
|
5415
|
+
* acknowledgement); an entry absent from the catalog defaults to the
|
|
5416
|
+
* community gate (the safer read). */
|
|
5206
5417
|
function OutdatedSection({ state, tiers, t, setEnabled, install, installStatus }) {
|
|
5207
5418
|
if (state.kind === "loading") return null;
|
|
5208
5419
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -5210,7 +5421,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5210
5421
|
"data-shop-outdated-error": true,
|
|
5211
5422
|
children: t("error")
|
|
5212
5423
|
});
|
|
5213
|
-
|
|
5424
|
+
const outdated = state.entries.filter((entry) => entry.outdated);
|
|
5425
|
+
if (outdated.length === 0) return null;
|
|
5214
5426
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5215
5427
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedSection,
|
|
5216
5428
|
"data-shop-outdated": true,
|
|
@@ -5219,7 +5431,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5219
5431
|
children: t("installedSection")
|
|
5220
5432
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
5221
5433
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
|
|
5222
|
-
children:
|
|
5434
|
+
children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
|
|
5223
5435
|
row,
|
|
5224
5436
|
tier: tiers.get(row.name) ?? "community",
|
|
5225
5437
|
t,
|
|
@@ -5233,11 +5445,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5233
5445
|
/** The shop tab root: browse, search, refresh, and render one card per
|
|
5234
5446
|
* entry. Data attributes on the e2e-relevant nodes follow the Task 3 list. */
|
|
5235
5447
|
function ShopTab(props) {
|
|
5236
|
-
const { t, catalog, install, installStatus, setEnabled,
|
|
5448
|
+
const { t, catalog, install, installStatus, setEnabled, installed, uninstall } = props;
|
|
5237
5449
|
const [catalogState, setCatalogState] = (0, react.useState)({ kind: "loading" });
|
|
5238
|
-
const [
|
|
5450
|
+
const [installedState, setInstalledState] = (0, react.useState)({ kind: "loading" });
|
|
5239
5451
|
const [request, setRequest] = (0, react.useState)({ kind: "initial" });
|
|
5240
5452
|
const [query, setQuery] = (0, react.useState)("");
|
|
5453
|
+
const [category, setCategory] = (0, react.useState)(null);
|
|
5241
5454
|
const incremental = typeof IntersectionObserver !== "undefined";
|
|
5242
5455
|
const [visibleCount, setVisibleCount] = (0, react.useState)(48);
|
|
5243
5456
|
const sentinelRef = (0, react.useRef)(null);
|
|
@@ -5265,31 +5478,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5265
5478
|
let cancelled = false;
|
|
5266
5479
|
const load = async () => {
|
|
5267
5480
|
try {
|
|
5268
|
-
const entries = await
|
|
5269
|
-
if (!cancelled)
|
|
5481
|
+
const entries = await installed();
|
|
5482
|
+
if (!cancelled) setInstalledState({
|
|
5270
5483
|
kind: "ready",
|
|
5271
5484
|
entries
|
|
5272
5485
|
});
|
|
5273
5486
|
} catch {
|
|
5274
|
-
if (!cancelled)
|
|
5487
|
+
if (!cancelled) setInstalledState({ kind: "error" });
|
|
5275
5488
|
}
|
|
5276
5489
|
};
|
|
5277
5490
|
load();
|
|
5278
5491
|
return () => {
|
|
5279
5492
|
cancelled = true;
|
|
5280
5493
|
};
|
|
5281
|
-
}, [
|
|
5494
|
+
}, [installed, request]);
|
|
5495
|
+
const installedByName = (0, react.useMemo)(() => {
|
|
5496
|
+
const map = /* @__PURE__ */ new Map();
|
|
5497
|
+
if (installedState.kind === "ready") for (const entry of installedState.entries) map.set(entry.name, entry);
|
|
5498
|
+
return map;
|
|
5499
|
+
}, [installedState]);
|
|
5282
5500
|
const filtered = (0, react.useMemo)(() => {
|
|
5283
5501
|
if (catalogState.kind !== "ready") return [];
|
|
5284
5502
|
const q = query.trim().toLowerCase();
|
|
5285
5503
|
return catalogState.result.plugins.filter((entry) => {
|
|
5286
5504
|
if (isShopLike(entry.name)) return false;
|
|
5505
|
+
if (category === "installed") {
|
|
5506
|
+
if (!installedByName.has(entry.name)) return false;
|
|
5507
|
+
} else if (category !== null && categoryKey(entry) !== categoryLocaleKey(category)) return false;
|
|
5287
5508
|
if (q === "") return true;
|
|
5288
5509
|
const summaryEn = entry.catalog?.summary.en ?? "";
|
|
5289
5510
|
const summaryZh = entry.catalog?.summary.zh ?? "";
|
|
5290
5511
|
return entry.name.toLowerCase().includes(q) || summaryEn.toLowerCase().includes(q) || summaryZh.toLowerCase().includes(q);
|
|
5291
5512
|
});
|
|
5292
|
-
}, [
|
|
5513
|
+
}, [
|
|
5514
|
+
catalogState,
|
|
5515
|
+
query,
|
|
5516
|
+
category,
|
|
5517
|
+
installedByName
|
|
5518
|
+
]);
|
|
5293
5519
|
filteredLenRef.current = filtered.length;
|
|
5294
5520
|
const stars = catalogState.kind === "ready" ? catalogState.result.stars : {};
|
|
5295
5521
|
const sorted = (0, react.useMemo)(() => sortByStars(filtered, stars), [filtered, stars]);
|
|
@@ -5308,6 +5534,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5308
5534
|
filtered.length
|
|
5309
5535
|
]);
|
|
5310
5536
|
const visible = incremental ? sorted.slice(0, visibleCount) : sorted;
|
|
5537
|
+
const categoryCounts = (0, react.useMemo)(() => {
|
|
5538
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5539
|
+
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) {
|
|
5540
|
+
if (isShopLike(entry.name)) continue;
|
|
5541
|
+
const key = categoryKey(entry);
|
|
5542
|
+
const bare = CATEGORY_ORDER.find((c) => categoryLocaleKey(c) === key);
|
|
5543
|
+
if (bare !== void 0) counts.set(bare, (counts.get(bare) ?? 0) + 1);
|
|
5544
|
+
}
|
|
5545
|
+
return counts;
|
|
5546
|
+
}, [catalogState]);
|
|
5547
|
+
const installedCount = (0, react.useMemo)(() => {
|
|
5548
|
+
if (installedState.kind !== "ready") return 0;
|
|
5549
|
+
return installedState.entries.filter((entry) => !isShopLike(entry.name)).length;
|
|
5550
|
+
}, [installedState]);
|
|
5311
5551
|
const tiers = (0, react.useMemo)(() => {
|
|
5312
5552
|
const map = /* @__PURE__ */ new Map();
|
|
5313
5553
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.tier);
|
|
@@ -5378,9 +5618,54 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5378
5618
|
})]
|
|
5379
5619
|
})]
|
|
5380
5620
|
}),
|
|
5381
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5382
|
-
className: _dsh_plugin_shop_css_e3675a89_default.
|
|
5383
|
-
|
|
5621
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5622
|
+
className: _dsh_plugin_shop_css_e3675a89_default.categoryBar,
|
|
5623
|
+
role: "group",
|
|
5624
|
+
"aria-label": t("catalog"),
|
|
5625
|
+
children: [
|
|
5626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5627
|
+
type: "button",
|
|
5628
|
+
className: category === null ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
|
|
5629
|
+
"aria-pressed": category === null,
|
|
5630
|
+
onClick: () => {
|
|
5631
|
+
setCategory(null);
|
|
5632
|
+
setVisibleCount(48);
|
|
5633
|
+
},
|
|
5634
|
+
children: [
|
|
5635
|
+
t("all"),
|
|
5636
|
+
" ",
|
|
5637
|
+
[...categoryCounts.values()].reduce((a, b) => a + b, 0)
|
|
5638
|
+
]
|
|
5639
|
+
}),
|
|
5640
|
+
CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5641
|
+
type: "button",
|
|
5642
|
+
className: category === key ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
|
|
5643
|
+
"aria-pressed": category === key,
|
|
5644
|
+
onClick: () => {
|
|
5645
|
+
setCategory(key);
|
|
5646
|
+
setVisibleCount(48);
|
|
5647
|
+
},
|
|
5648
|
+
children: [
|
|
5649
|
+
t(categoryLocaleKey(key)),
|
|
5650
|
+
" ",
|
|
5651
|
+
categoryCounts.get(key) ?? 0
|
|
5652
|
+
]
|
|
5653
|
+
}, key)),
|
|
5654
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5655
|
+
type: "button",
|
|
5656
|
+
className: category === "installed" ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
|
|
5657
|
+
"aria-pressed": category === "installed",
|
|
5658
|
+
onClick: () => {
|
|
5659
|
+
setCategory("installed");
|
|
5660
|
+
setVisibleCount(48);
|
|
5661
|
+
},
|
|
5662
|
+
children: [
|
|
5663
|
+
t("installed"),
|
|
5664
|
+
" ",
|
|
5665
|
+
installedCount
|
|
5666
|
+
]
|
|
5667
|
+
})
|
|
5668
|
+
]
|
|
5384
5669
|
}),
|
|
5385
5670
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5386
5671
|
className: _dsh_plugin_shop_css_e3675a89_default.catalogStats,
|
|
@@ -5400,9 +5685,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5400
5685
|
children: [visible.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
|
|
5401
5686
|
entry,
|
|
5402
5687
|
stars: stars[entry.name],
|
|
5688
|
+
installed: installedByName.get(entry.name),
|
|
5403
5689
|
t,
|
|
5404
5690
|
install,
|
|
5405
|
-
installStatus
|
|
5691
|
+
installStatus,
|
|
5692
|
+
uninstall
|
|
5406
5693
|
}) }, entry.name)), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
5407
5694
|
ref: sentinelRef,
|
|
5408
5695
|
className: _dsh_plugin_shop_css_e3675a89_default.cardsSentry,
|
|
@@ -5418,7 +5705,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5418
5705
|
})
|
|
5419
5706
|
})] }),
|
|
5420
5707
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
|
|
5421
|
-
state:
|
|
5708
|
+
state: installedState,
|
|
5422
5709
|
tiers,
|
|
5423
5710
|
t,
|
|
5424
5711
|
setEnabled,
|
|
@@ -5466,7 +5753,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5466
5753
|
install: async (args) => unwrap(await ns.installStart(args)),
|
|
5467
5754
|
installStatus: async (args) => unwrap(await ns.installStatus(args)),
|
|
5468
5755
|
setEnabled: async (args) => unwrap(await ns.setEnabled(args)),
|
|
5469
|
-
|
|
5756
|
+
installed: async () => unwrap(await ns.installed()),
|
|
5757
|
+
uninstall: async (args) => unwrap(await ns.uninstallStart(args))
|
|
5470
5758
|
});
|
|
5471
5759
|
ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
5472
5760
|
name: "settings.plugins.tab",
|