ygc-dsh-sync 1.2.0 → 1.2.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.
Files changed (6) hide show
  1. package/client.js +69 -17
  2. package/index.js +43 -16
  3. package/lib/api.js +1818 -1781
  4. package/lib/core.js +484 -484
  5. package/lib/features.js +169 -154
  6. package/package.json +62 -62
package/client.js CHANGED
@@ -295,28 +295,34 @@ function makePanel(React) {
295
295
  };
296
296
 
297
297
  /**
298
- * 删本机特征文件夹:本机不再参与这个工作区的同步。
298
+ * 删工作区(= 删本机特征文件夹,1.2.1 起两者是同一件事)。
299
299
  *
300
- * removeWorkspace=true 时连工作区注册一起去掉(侧边栏不再显示,
301
- * 与「在侧边栏删除工作区」等价)。别人的格子与文件夹本身不动。
300
+ * 插件里不再提供「只删格子、留着工作区」的入口:那条路会让工作区变成
301
+ * 「已注册但没格子」,下一轮巡检必然把它列回人工队列,用户看到的就是
302
+ * 「删了又自己冒出来」。所以这里只保留一个动作,且必须删注册。
302
303
  */
303
- const doDrop = async (paths, removeWorkspace) => {
304
+ const doDrop = async (paths) => {
304
305
  const list = (paths || []).filter(Boolean);
305
306
  if (!list.length) return;
306
- if (removeWorkspace && typeof window !== 'undefined' && typeof window.confirm === 'function') {
307
- if (!window.confirm('删除后会从侧边栏移除这个工作区,并清掉本机特征文件夹。目录内容与会话文件不受影响。继续?')) {
307
+ if (typeof window !== 'undefined' && typeof window.confirm === 'function') {
308
+ if (
309
+ !window.confirm(
310
+ '会从侧边栏删除这个工作区,并清掉本机特征文件夹(本机不再同步它)。\n目录内容、会话文件、其他机器的备份都不受影响。继续?',
311
+ )
312
+ ) {
308
313
  return;
309
314
  }
310
315
  }
311
316
  setBusy('drop');
312
317
  setNote(null);
313
318
  try {
314
- const r = await call('/features/drop', { paths: list, removeWorkspace: !!removeWorkspace });
319
+ const r = await call('/features/drop', { paths: list });
320
+ const errs = (r.errors || []).length;
315
321
  setNote({
316
- tone: r.errors && r.errors.length ? 'warn' : 'ok',
317
- text: `已删除本机特征文件夹 ${r.dropped.length} 个${
318
- r.errors && r.errors.length ? `,${r.errors.length} 个失败` : ''
319
- }(本机不再同步该工作区)`,
322
+ tone: errs ? 'warn' : 'ok',
323
+ text: `已删除 ${r.dropped.length} 个工作区,并清掉对应本机特征文件夹${
324
+ errs ? `;${errs} 个失败:${r.errors[0].message}` : ''
325
+ }`,
320
326
  });
321
327
  await load();
322
328
  } catch (e) {
@@ -755,9 +761,11 @@ function makePanel(React) {
755
761
  ? (() => {
756
762
  const pendingList = (scan && scan.pending) || (state.features && state.features.pending) || [];
757
763
  const whyText = {
758
- unregistered: '检测到匹配目录,本机还没建特征文件夹',
759
- existing: '存量工作区,本机还没建特征文件夹',
760
- awaiting: '等你决定',
764
+ // 矩阵里唯一要人决定的两档
765
+ othersOnly: '别的机器有特征文件夹,本机没有',
766
+ structureOnly: '目录里有同步结构,本机没建特征文件夹',
767
+ unregistered: '检测到匹配目录,本机没建特征文件夹',
768
+ existing: '已注册成工作区,本机没建特征文件夹',
761
769
  declined: '你之前选了不建(可改主意)',
762
770
  };
763
771
  return h(
@@ -795,6 +803,9 @@ function makePanel(React) {
795
803
  h('span', { className: 'dsb-tag dsb-tag-warn' }, whyText[d.why] || d.why),
796
804
  ),
797
805
  h('div', { className: 'dsb-row-d' }, d.path),
806
+ d.others && d.others.length
807
+ ? h('div', { className: 'dsb-row-d' }, `别的机器:${d.others.join(' · ')}`)
808
+ : null,
798
809
  ),
799
810
  ),
800
811
  ),
@@ -1007,10 +1018,10 @@ function makePanel(React) {
1007
1018
  {
1008
1019
  className: 'dsb-btn dsb-btn-sm dsb-btn-danger',
1009
1020
  disabled: !!busy,
1010
- title: '删掉本机特征文件夹:本机不再参与这个工作区的同步;别人的格子不动',
1011
- onClick: () => doDrop([w.path], false),
1021
+ title: '删除工作区,连带清掉本机特征文件夹;目录内容与别人的备份不动',
1022
+ onClick: () => doDrop([w.path]),
1012
1023
  },
1013
- '删本机特征文件夹',
1024
+ '删除工作区',
1014
1025
  ),
1015
1026
  ),
1016
1027
  ),
@@ -1051,6 +1062,47 @@ function makePanel(React) {
1051
1062
  ),
1052
1063
  )
1053
1064
  : null,
1065
+
1066
+ /**
1067
+ * 你点过「跳过」的目录。
1068
+ *
1069
+ * 跳过的语义就是「从待决定清单里消失」,所以它必须在这里有个落点,
1070
+ * 否则改不了主意只能翻文件。点「改为建特征文件夹」走 /features/adopt,
1071
+ * 会顺手把跳过记录清掉。
1072
+ */
1073
+ (state.features && (state.features.declined || []).length)
1074
+ ? h(
1075
+ 'div',
1076
+ { style: { marginTop: 14 } },
1077
+ h(
1078
+ 'div',
1079
+ { className: 'dsb-row-t', style: { marginBottom: 6 } },
1080
+ `已跳过:${state.features.declined.length} 个(不再询问,可改主意)`,
1081
+ ),
1082
+ (state.features.declined || []).map((w) =>
1083
+ h(
1084
+ 'div',
1085
+ { className: 'dsb-row', key: w.path },
1086
+ h(
1087
+ 'div',
1088
+ { className: 'dsb-row-main' },
1089
+ h('div', { className: 'dsb-row-t' }, w.title),
1090
+ h('div', { className: 'dsb-row-d' }, '你选了不在这个目录建本机特征文件夹'),
1091
+ h('div', { className: 'dsb-row-d' }, w.path),
1092
+ ),
1093
+ h(
1094
+ 'button',
1095
+ {
1096
+ className: 'dsb-btn dsb-btn-sm',
1097
+ disabled: !!busy,
1098
+ onClick: () => doAdopt([w.path]),
1099
+ },
1100
+ '改为建特征文件夹',
1101
+ ),
1102
+ ),
1103
+ ),
1104
+ )
1105
+ : null,
1054
1106
  ),
1055
1107
 
1056
1108
  // 插件清单同步(1.0.0 从自动更新器迁入)
package/index.js CHANGED
@@ -295,13 +295,13 @@ export function apply(ctx, config = {}) {
295
295
  });
296
296
 
297
297
  // ---- 定时巡检 ----
298
- // 行为(1.2.0 起):
299
- // 1. 扫描特定文件夹下的直接子目录,列出「符合插件结构、本机还没建格子」的
300
- // → 只记录,等人到面板上点,绝不自己建、也不自己注册;
301
- // 2. 本机新建的工作区默认建格子(保持 1.1.0 原有设置);
302
- // 3. 曾经建过、现在磁盘上没有的(被外部删了)→ 自动补建;
303
- // 4. 本机已删除的工作区 → 跟着删格子;
304
- // 5. 补登记只对「有格子」的工作区做(没格子的本机根本没在同步它)。
298
+ // 判据(用户 2026-11-14 定的矩阵,只认磁盘上有没有那三个东西):
299
+ // 别人的格子 有 / 我的格子 没有 / 工作区 没有 → 问人要不要在本机建
300
+ // 我的格子 有 / 工作区 有 → 正常,参与备份与拉取
301
+ // 我的格子 有 / 工作区 没有 → 补工作区(注册回侧边栏)
302
+ // 我的格子 没有 / 工作区 有 → 补文件夹
303
+ // 工作区被删(基线里有、清单里没有) → 删我的格子(含格里对话)
304
+ // 补工作区这一步要调宿主接口(async),所以放在这里做,不在纯函数里。
305
305
  const runPatrol = async (reason) => {
306
306
  try {
307
307
  const state = loadState();
@@ -313,7 +313,6 @@ export function apply(ctx, config = {}) {
313
313
  machineId: mid,
314
314
  });
315
315
 
316
- // 特征文件夹计划:adopt / restore / drop 自动做,pending 留给人
317
316
  const plan = planFeatures({
318
317
  folders: state.folders,
319
318
  workspaces: all,
@@ -322,9 +321,31 @@ export function apply(ctx, config = {}) {
322
321
  machineId: mid,
323
322
  });
324
323
  const next = loadState();
324
+ // 补文件夹 + 删格子(这两件是同步的、不涉及注册)
325
325
  const applied = applyFeaturePlan({ plan, state: next, machineId: mid, log });
326
326
  saveState(next);
327
327
 
328
+ // 补工作区:有我的格子、但侧边栏里没有它
329
+ const restoredWs = [];
330
+ const restoreErrors = [];
331
+ if (typeof registerWorkspace === 'function') {
332
+ for (const item of plan.restoreWorkspace || []) {
333
+ try {
334
+ const out = await registerWorkspace(item.path, item.title);
335
+ restoredWs.push({ path: item.path, id: out?.id });
336
+ } catch (err) {
337
+ restoreErrors.push({ path: item.path, message: String((err && err.message) || err) });
338
+ }
339
+ }
340
+ if (restoredWs.length) {
341
+ const s2 = loadState();
342
+ for (const r of restoredWs) {
343
+ if (r.id !== undefined && r.id !== null) s2.features.known[String(r.id)] = String(r.path).replace(/[\\/]+/g, '\\');
344
+ }
345
+ saveState(s2);
346
+ }
347
+ }
348
+
328
349
  const attachment = await repairAttachments();
329
350
 
330
351
  const after = loadState();
@@ -334,9 +355,9 @@ export function apply(ctx, config = {}) {
334
355
  lastResult: {
335
356
  discover: scan.discover.length,
336
357
  repair: scan.repair.length,
337
- pending: plan.pending.length,
338
- adopted: applied.adopted.length,
339
- restored: applied.restored.length,
358
+ ask: plan.ask.length,
359
+ createdFolder: applied.created.length,
360
+ restoredWorkspace: restoredWs.length,
340
361
  dropped: applied.dropped.length,
341
362
  attached: attachment.attached,
342
363
  attachFailed: attachment.failed,
@@ -346,16 +367,22 @@ export function apply(ctx, config = {}) {
346
367
  pushHistory(
347
368
  after,
348
369
  'patrol',
349
- `巡检(${reason}):待人工确认 ${plan.pending.length} 个、新建特征文件夹 ${applied.adopted.length} 个、` +
350
- `补建 ${applied.restored.length} 个、随工作区删除 ${applied.dropped.length} 个` +
370
+ `巡检(${reason}):待人工确认 ${plan.ask.length} 个、补文件夹 ${applied.created.length} 个、` +
371
+ `补工作区 ${restoredWs.length} 个、删格子 ${applied.dropped.length} 个` +
351
372
  `${attachment.attached ? `、补登记 ${attachment.attached} 个会话归属` : ''}`,
352
373
  );
353
374
  saveState(after);
354
375
 
355
- if (plan.pending.length || applied.adopted.length || applied.restored.length || applied.dropped.length) {
376
+ if (
377
+ plan.ask.length ||
378
+ applied.created.length ||
379
+ restoredWs.length ||
380
+ applied.dropped.length ||
381
+ attachment.attached
382
+ ) {
356
383
  log(
357
- `巡检:待人工确认 ${plan.pending.length},新建 ${applied.adopted.length},补建 ${applied.restored.length},` +
358
- `删除 ${applied.dropped.length},补登记 ${attachment.attached}`,
384
+ `巡检:待人工确认 ${plan.ask.length},补文件夹 ${applied.created.length},补工作区 ${restoredWs.length},` +
385
+ `删格子 ${applied.dropped.length},补登记 ${attachment.attached}`,
359
386
  );
360
387
  }
361
388
  return after.patrol;