@iasiv5/dsh-skins 1.5.12 → 1.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/client.js +48 -49
  2. package/package.json +4 -2
package/lib/client.js CHANGED
@@ -1264,19 +1264,14 @@ ${darkSelector}{${dark.join(";")}}`;
1264
1264
  setDeletingId(null);
1265
1265
  }
1266
1266
  };
1267
- const previewRefsFor = (assetId) => (configClient.dirtyFields?.() ?? []).filter((entry) => entry.value === assetId).map((entry) => `${labelFor(entry.skinId)} · ${entry.key} · ${tr("personalization.library.previewRef")}`);
1267
+ const referenceLabelsFor = (assetId) => configClient.referencesFor(assetId).map((entry) => entry.layer === "preview" ? `${labelFor(entry.skinId)} · ${entry.key} · ${tr("personalization.library.previewRef")}` : `${labelFor(entry.skinId)} · ${entry.key}`);
1268
1268
  const removeAsset = (asset) => {
1269
1269
  if (deletingId !== null || clearing) return;
1270
- const references = state.references[asset.id] ?? [];
1271
- const previewRefs = previewRefsFor(asset.id);
1272
- if (references.length === 0 && previewRefs.length === 0) return deleteAsset(asset);
1273
- const names = [
1274
- ...references.map((entry) => `${labelFor(entry.skinId)} · ${entry.key}`),
1275
- ...previewRefs
1276
- ].join("\n");
1270
+ const references = referenceLabelsFor(asset.id);
1271
+ if (references.length === 0) return deleteAsset(asset);
1277
1272
  requestConfirm(
1278
1273
  `${tr("personalization.library.deleteConfirm")}
1279
- ${names}`,
1274
+ ${references.join("\n")}`,
1280
1275
  () => deleteAsset(asset)
1281
1276
  );
1282
1277
  };
@@ -1284,10 +1279,7 @@ ${names}`,
1284
1279
  if (clearing || deletingId !== null || library.length === 0) return;
1285
1280
  const names = /* @__PURE__ */ new Map();
1286
1281
  for (const asset of library) {
1287
- for (const entry of state.references[asset.id] ?? []) {
1288
- names.set(`${labelFor(entry.skinId)} · ${entry.key}`, true);
1289
- }
1290
- for (const label of previewRefsFor(asset.id)) {
1282
+ for (const label of referenceLabelsFor(asset.id)) {
1291
1283
  names.set(label, true);
1292
1284
  }
1293
1285
  }
@@ -1483,8 +1475,7 @@ ${names}`,
1483
1475
  if (confirmationRef.current !== confirmation) return;
1484
1476
  confirmationRef.current = null;
1485
1477
  setConfirmation(null);
1486
- const current = configClient.getState();
1487
- if (current.status === "synced" && current.mode !== "recovery") {
1478
+ if (configClient.canWrite()) {
1488
1479
  void confirmation.accept();
1489
1480
  }
1490
1481
  };
@@ -1496,7 +1487,7 @@ ${names}`,
1496
1487
  headerRef.current?.focus?.(wideLayout ? { preventScroll: true } : void 0);
1497
1488
  }, []);
1498
1489
  if (schema === null) return null;
1499
- const writesBlocked = state.status !== "synced" || state.mode === "recovery";
1490
+ const writesBlocked = !configClient.canWrite();
1500
1491
  const overrides = configClient.effectiveOverrides(skinId);
1501
1492
  const { values } = mergeValues(skinId, overrides, void 0, profile);
1502
1493
  const hasAnyOverride = Object.keys(overrides).length > 0;
@@ -1547,7 +1538,7 @@ ${names}`,
1547
1538
  jsx("button", {
1548
1539
  type: "button",
1549
1540
  className: "dsh-skins-pz-btn",
1550
- onClick: () => configClient.retry(),
1541
+ onClick: () => configClient.refetch(),
1551
1542
  children: tr("personalization.status.retry")
1552
1543
  })
1553
1544
  ] }));
@@ -1557,22 +1548,26 @@ ${names}`,
1557
1548
  jsx("span", { children: tr("personalization.status.unsupported") })
1558
1549
  ] }));
1559
1550
  }
1560
- if (state.lastFlushCode === "REVISION_CONFLICT") {
1551
+ const notices = state.notices;
1552
+ const conflictNotice = notices.find((notice) => notice.kind === "conflict");
1553
+ const errorNotice = notices.find((notice) => notice.kind === "error");
1554
+ if (conflictNotice !== void 0) {
1561
1555
  statusCluster.push(jsx("div", { key: "save-conflict", className: "dsh-skins-pz-status dsh-skins-pz-warn", children: [
1562
1556
  jsx("span", { children: tr("personalization.saveConflict") })
1563
1557
  ] }));
1564
- } else if (state.lastFlushError) {
1558
+ } else if (errorNotice !== void 0) {
1565
1559
  statusCluster.push(jsx("div", { key: "save-error", className: "dsh-skins-pz-status dsh-skins-pz-warn", children: [
1566
1560
  jsx("span", { children: tr("personalization.saveFailed") }),
1567
- jsx("div", { className: "dsh-skins-pz-muted", children: state.lastFlushError })
1561
+ jsx("div", { className: "dsh-skins-pz-muted", children: errorNotice.message })
1568
1562
  ] }));
1569
1563
  }
1570
- if (Array.isArray(state.revertedFields) && state.revertedFields.length > 0) {
1564
+ const revertedNotice = notices.find((notice) => notice.kind === "reverted");
1565
+ if (revertedNotice !== void 0 && revertedNotice.fields.length > 0) {
1571
1566
  statusCluster.push(jsx("div", { key: "reverted", className: "dsh-skins-pz-status", children: [
1572
1567
  jsx("div", { children: tr("personalization.fieldReverted") }),
1573
1568
  jsx("div", {
1574
1569
  className: "dsh-skins-pz-muted",
1575
- children: state.revertedFields.map((entry) => `${labelFor(entry.skinId)} · ${entry.key}`).join("、")
1570
+ children: revertedNotice.fields.map((entry) => `${labelFor(entry.skinId)} · ${entry.key}`).join("、")
1576
1571
  })
1577
1572
  ] }));
1578
1573
  }
@@ -2213,13 +2208,20 @@ ${names}`,
2213
2208
  let fetchGeneration = 0;
2214
2209
  let disposed = false;
2215
2210
  let writeChain = Promise.resolve();
2216
- let lastFlushCode = null;
2217
- let lastFlushError = null;
2218
- let revertedFields = [];
2211
+ let notices = [];
2219
2212
  let channel = null;
2220
2213
  function setFlushFailure(code, message) {
2221
- lastFlushCode = code;
2222
- lastFlushError = message;
2214
+ notices = notices.filter((notice) => notice.kind === "reverted");
2215
+ if (code === "REVISION_CONFLICT") notices.push({ kind: "conflict" });
2216
+ else if (message) notices.push({ kind: "error", message });
2217
+ }
2218
+ function noteReverted(entry) {
2219
+ const current = notices.find((notice) => notice.kind === "reverted");
2220
+ if (current !== void 0 && current.fields.some((field) => field.skinId === entry.skinId && field.key === entry.key)) return;
2221
+ notices = [
2222
+ ...notices.filter((notice) => notice.kind !== "reverted"),
2223
+ { kind: "reverted", fields: [...current?.fields ?? [], entry] }
2224
+ ];
2223
2225
  }
2224
2226
  function emit() {
2225
2227
  for (const listener of [...listeners]) {
@@ -2239,9 +2241,7 @@ ${names}`,
2239
2241
  references: snapshot.references ?? {},
2240
2242
  recovery: snapshot.recovery,
2241
2243
  quota: snapshot.quota,
2242
- lastFlushCode,
2243
- lastFlushError,
2244
- revertedFields: revertedFields.map((entry) => ({ ...entry })),
2244
+ notices: structuredClone(notices),
2245
2245
  dirtyCount: previews.size
2246
2246
  };
2247
2247
  }
@@ -2305,8 +2305,10 @@ ${names}`,
2305
2305
  }
2306
2306
  const FLUSH_DEBOUNCE_MS = 400;
2307
2307
  let flushTimer = null;
2308
- function gateWrites() {
2309
- if (status === "synced") return null;
2308
+ function writeGate() {
2309
+ if (status === "synced") {
2310
+ return snapshot.mode === "recovery" ? "recovery" : null;
2311
+ }
2310
2312
  if (status === "loading") return "loading";
2311
2313
  if (status === "unsupported-readonly") return "unsupported";
2312
2314
  return "offline";
@@ -2333,7 +2335,7 @@ ${names}`,
2333
2335
  writeChain = writeChain.then(async () => {
2334
2336
  try {
2335
2337
  if (disposed) return { flushed: 0, blocked: "disposed" };
2336
- const blocked = gateWrites();
2338
+ const blocked = writeGate();
2337
2339
  if (blocked !== null) return { flushed: 0, blocked };
2338
2340
  const attemptedRevision = snapshot.revision;
2339
2341
  for (let attempt = 0; attempt < 2; attempt += 1) {
@@ -2410,10 +2412,7 @@ ${names}`,
2410
2412
  if (previews.has(composite)) {
2411
2413
  previews.delete(composite);
2412
2414
  excised = true;
2413
- const alreadyNoted = revertedFields.some(
2414
- (entry) => entry.skinId === issue.skinId && entry.key === issue.key
2415
- );
2416
- if (!alreadyNoted) revertedFields.push({ skinId: issue.skinId, key: issue.key });
2415
+ noteReverted({ skinId: issue.skinId, key: issue.key });
2417
2416
  }
2418
2417
  }
2419
2418
  }
@@ -2430,7 +2429,7 @@ ${names}`,
2430
2429
  if (previews.get(composite) === value) previews.delete(composite);
2431
2430
  }
2432
2431
  setFlushFailure(null, null);
2433
- revertedFields = [];
2432
+ notices = [];
2434
2433
  await refetch();
2435
2434
  announce();
2436
2435
  return { flushed: count };
@@ -2488,10 +2487,6 @@ ${names}`,
2488
2487
  }
2489
2488
  return merged;
2490
2489
  }
2491
- function retry() {
2492
- if (status !== "offline-failed" && status !== "synced") return refetch();
2493
- return refetch();
2494
- }
2495
2490
  async function assetFingerprint(bytes) {
2496
2491
  if (typeof crypto?.subtle?.digest !== "function") return null;
2497
2492
  const digest = await crypto.subtle.digest("SHA-256", bytes);
@@ -2525,7 +2520,7 @@ ${names}`,
2525
2520
  return { error: `HTTP ${response.status}` };
2526
2521
  }
2527
2522
  async function uploadImage(file, displayName) {
2528
- const blocked = gateWrites();
2523
+ const blocked = writeGate();
2529
2524
  if (blocked !== null) return { error: blocked };
2530
2525
  const bytes = file instanceof Uint8Array ? file : new Uint8Array(await file.arrayBuffer());
2531
2526
  let networkError;
@@ -2574,7 +2569,7 @@ ${names}`,
2574
2569
  function runGalleryOp(operations, goalAchieved) {
2575
2570
  writeChain = writeChain.then(async () => {
2576
2571
  if (disposed) return { error: "disposed" };
2577
- const blocked = gateWrites();
2572
+ const blocked = writeGate();
2578
2573
  if (blocked !== null) return { error: blocked };
2579
2574
  for (let attempt = 0; attempt < 2; attempt += 1) {
2580
2575
  let response;
@@ -2623,7 +2618,7 @@ ${names}`,
2623
2618
  return writeChain;
2624
2619
  }
2625
2620
  async function deleteImage(id) {
2626
- const blocked = gateWrites();
2621
+ const blocked = writeGate();
2627
2622
  if (blocked !== null) return { error: blocked };
2628
2623
  return runGalleryOp(
2629
2624
  [{ op: "deleteAsset", assetId: id }],
@@ -2633,7 +2628,7 @@ ${names}`,
2633
2628
  function clearLibrary() {
2634
2629
  return runGalleryOp([{ op: "clearLibrary" }], () => snapshot.library.length === 0);
2635
2630
  }
2636
- function dirtyFields() {
2631
+ function valuedPreviews() {
2637
2632
  const fields = [];
2638
2633
  for (const [composite, value] of previews) {
2639
2634
  if (value === null) continue;
@@ -2642,6 +2637,11 @@ ${names}`,
2642
2637
  }
2643
2638
  return fields;
2644
2639
  }
2640
+ function referencesFor(assetId) {
2641
+ const persisted = (snapshot.references?.[assetId] ?? []).map((entry) => ({ skinId: entry.skinId, key: entry.key, layer: "persisted" }));
2642
+ const preview2 = valuedPreviews().filter((entry) => entry.value === assetId).map((entry) => ({ skinId: entry.skinId, key: entry.key, layer: "preview" }));
2643
+ return [...persisted, ...preview2];
2644
+ }
2645
2645
  async function confirmRecovery() {
2646
2646
  const response = await request("/recovery", { method: "POST" });
2647
2647
  if (!response.ok) {
@@ -2658,7 +2658,6 @@ ${names}`,
2658
2658
  const api = {
2659
2659
  boot: refetch,
2660
2660
  refetch,
2661
- retry,
2662
2661
  flushNow,
2663
2662
  preview,
2664
2663
  previewReset,
@@ -2666,11 +2665,11 @@ ${names}`,
2666
2665
  uploadImage,
2667
2666
  deleteImage,
2668
2667
  clearLibrary,
2669
- dirtyFields,
2668
+ referencesFor,
2670
2669
  confirmRecovery,
2671
2670
  assetUrl,
2672
2671
  getState: publicState,
2673
- writeBlocked: gateWrites,
2672
+ canWrite: () => writeGate() === null,
2674
2673
  onStateChange(listener) {
2675
2674
  listeners.add(listener);
2676
2675
  return () => listeners.delete(listener);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iasiv5/dsh-skins",
3
3
  "description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换。",
4
- "version": "1.5.12",
4
+ "version": "1.5.13",
5
5
  "homepage": "https://github.com/iasiv5/dsh-skins#readme",
6
6
  "repository": {
7
7
  "type": "git",
@@ -66,6 +66,7 @@
66
66
  "devDependencies": {
67
67
  "@deepseek-ai/cordis": "^4.0.4",
68
68
  "esbuild": "^0.28.2",
69
+ "eslint": "^10.13.0",
69
70
  "playwright-core": "1.62.1",
70
71
  "semver": "^7.8.5"
71
72
  },
@@ -77,6 +78,7 @@
77
78
  "verify:readme": "node scripts/verify-readme-pairing.mjs",
78
79
  "verify:bundle": "node scripts/verify-bundle-guard.mjs",
79
80
  "verify:hooks": "node scripts/verify-upstream-hooks.mjs",
80
- "check": "pnpm run check:lockfile && pnpm run build && node --check lib/client.js && node --check lib/index.js && pnpm run test && pnpm run verify:readme && pnpm run verify:bundle && pnpm run verify:hooks"
81
+ "check": "pnpm run check:lockfile && pnpm run build && node --check lib/client.js && node --check lib/index.js && pnpm run lint:tests && pnpm run test && pnpm run verify:readme && pnpm run verify:bundle && pnpm run verify:hooks && git diff --exit-code -- lib/index.js lib/client.js",
82
+ "lint:tests": "eslint tests/"
81
83
  }
82
84
  }