dsh-life-pack 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # 爱生活(dsh-life-pack)
2
+
3
+ DSH 设置弹窗里的「爱生活」卡:把六个技能(备忘录、卡路里、私家大厨、居家管家、作息管家、饼干记账)的**设置页**收进一排页签,另带**配置体检**与**更新区**。
4
+
5
+ One settings card for the iLife home-life suite: memo, calorie, chef, home, schedule and bill in a tab bar, with a read-only config health check and an update panel.
6
+
7
+ ## 装
8
+
9
+ ```sh
10
+ dsh plugin add dsh-life-pack # 只要这张卡
11
+ dsh plugin add dsh-life-pack dsh-calorie # 卡 + 某一个技能(一条命令两个包)
12
+ ```
13
+
14
+ 某个技能没装时,那一个页签的位置会变成**补装提示**,带上该条可直接复制的命令——不需要先装齐六个。
15
+
16
+ ## 卡里有什么
17
+
18
+ | 区域 | 做什么 |
19
+ | --- | --- |
20
+ | 页签条 | 六个技能的设置页各自注册进「爱生活页签槽」;点谁切谁 |
21
+ | 配置体检 | 点一次,按家取数:顶部一行总账,每家一张表(每条:灯/标题/档/一句话/去哪修)。**只读**——不建目录、不改配置、不自动重置 |
22
+ | 更新区 | 七个包(它自己 + 六家)检查更新、逐个安装;装完按事实给下一步(重启 DSH 生效/重试/重新检查) |
23
+ | 面板入口 | 右上角:检查更新、去仓库点 Star、反馈问题;底部一句「作者其他插件」 |
24
+
25
+ ## 它不做什么
26
+
27
+ 不 import 任何一个单品包,也不依赖它们——依赖方向是反的(单品硬依赖 `dsh-life-pack`)。总管只做导航与聚合:六家的页面各自注册进槽位,缺席就渲染补装提示,不轮询、不代管。
28
+
29
+ ## 配置与数据
30
+
31
+ - 配置:`~/.ilife/<技能>.yaml`(六个技能各一份,在各自的设置页里改)
32
+ - 数据目录:默认 `~/.ilife/data`(可在各技能设置页里改;留空=用默认)
33
+
34
+ ## 链接
35
+
36
+ - 仓库与整套插件(六个单品同仓):<https://github.com/FeatherHunter/ilife>
37
+ - 问题与建议:<https://github.com/FeatherHunter/ilife/issues>
38
+ - 许可:MIT
package/dist/client.js CHANGED
@@ -1747,10 +1747,15 @@ window.__ModuleLoader__.load({
1747
1747
  if (typeof raw !== "object" || raw === null) return false;
1748
1748
  return typeof raw.pick === "function";
1749
1749
  }
1750
- /** 三态判定:先 native(有 pick),再 browse(有两格原语),都没有=`none`。 */
1750
+ /** 三态判定:**先看两格浏览原语**(都在=应用内浏览器这条路在),再看单独的 `pick`,都没有=`none`。
1751
+ *
1752
+ * 为什么浏览优先:客户端命名空间上三条动词一定都在(由 `TYPERT_REMOTE.descriptors` 生成,
1753
+ * 与组合里服务哪种能力无关),所以两格原语在=这台宿主**可能**给浏览;真调一次才知道给不给,
1754
+ * 不给({@link CAPABILITY_REFUSED})再由调用方换系统对话框。反过来先认 `pick` 就会在
1755
+ * Desktop 这种「只服务 browse」的宿主上每次都去唤一次系统对话框、被拒一次(#744 实测)。 */
1751
1756
  function pickerModeOf(raw) {
1752
- if (hasPickFn(raw)) return "native";
1753
- return isBrowseFace(raw) ? "browse" : "none";
1757
+ if (isBrowseFace(raw)) return "browse";
1758
+ return hasPickFn(raw) ? "native" : "none";
1754
1759
  }
1755
1760
  /** 平台回执 → 三态(**永不抛**):成功回的是信封里的 `value` 不是路径;`ok:false` 是「供不了」不是「取消」。
1756
1761
  *
@@ -1771,8 +1776,10 @@ window.__ModuleLoader__.load({
1771
1776
  } : { kind: "cancelled" };
1772
1777
  }
1773
1778
  const detail = answer.error?.message?.trim() ?? "";
1779
+ const code = answer.error?.code;
1774
1780
  return {
1775
1781
  kind: "unavailable",
1782
+ ...typeof code === "string" && code !== "" ? { code } : {},
1776
1783
  message: "打不开系统文件夹对话框" + (detail === "" ? "" : "(" + detail + ")") + ":请直接在框里填绝对路径。"
1777
1784
  };
1778
1785
  }
@@ -1944,7 +1951,15 @@ window.__ModuleLoader__.load({
1944
1951
  listeners.add(listener);
1945
1952
  return () => listeners.delete(listener);
1946
1953
  },
1947
- open: () => load(null),
1954
+ async open() {
1955
+ const wanted = deps.initialPath.trim();
1956
+ if (wanted !== "") {
1957
+ await load(wanted);
1958
+ if (state.phase === "ready") return state;
1959
+ }
1960
+ await load(null);
1961
+ return state;
1962
+ },
1948
1963
  enter: (path) => load(path),
1949
1964
  async up() {
1950
1965
  const listing = state.listing;
@@ -2053,6 +2068,7 @@ window.__ModuleLoader__.load({
2053
2068
  });
2054
2069
  return {
2055
2070
  open: () => controller.open(),
2071
+ subscribe: (listener) => controller.subscribe(listener),
2056
2072
  state: () => controller.getState(),
2057
2073
  summary: () => describe(controller.getState()),
2058
2074
  actions: {
@@ -2225,7 +2241,7 @@ window.__ModuleLoader__.load({
2225
2241
  },
2226
2242
  error: { color: "var(--dsw-alias-label-error, #e66)" }
2227
2243
  };
2228
- function crumbRow(state, labels, onEnter) {
2244
+ function crumbRow(state, onEnter) {
2229
2245
  const listing = state.listing;
2230
2246
  if (listing === null || listing.crumbs.length === 0) return null;
2231
2247
  const parts = [];
@@ -2244,7 +2260,7 @@ window.__ModuleLoader__.load({
2244
2260
  },
2245
2261
  title: crumb.path,
2246
2262
  onClick: () => onEnter(crumb.path)
2247
- }, index === 0 ? labels.up : crumb.name));
2263
+ }, crumb.name));
2248
2264
  });
2249
2265
  return react.createElement("div", { style: S$1.crumbs }, parts);
2250
2266
  }
@@ -2310,7 +2326,7 @@ window.__ModuleLoader__.load({
2310
2326
  const listing = state.listing ?? EMPTY_LISTING;
2311
2327
  const hidden = hiddenCount(listing);
2312
2328
  const body = [];
2313
- body.push(crumbRow(state, labels, props.onEnter));
2329
+ body.push(crumbRow(state, props.onEnter));
2314
2330
  body.push(react.createElement("div", { style: S$1.pathRow }, react.createElement("input", {
2315
2331
  style: S$1.input,
2316
2332
  value: state.draft,
@@ -2391,11 +2407,15 @@ window.__ModuleLoader__.load({
2391
2407
  /** 目录行接线的 React 半边:把操作半那组动作直接喂给组件。
2392
2408
  *
2393
2409
  * 操作半(`directory-browser-state.ts` 的 `createDirectoryRowBrowser`)不碰 React,
2394
- * 这里只把它的 `actions` 摊进 props —— 于是「进哪一层」那套在 Node 里可测,这里只做接线。 */
2410
+ * 这里只把它的 `actions` 摊进 props —— 于是「进哪一层」那套在 Node 里可测,这里只做接线。
2411
+ *
2412
+ * `useSyncExternalStore` 是**必需**的:列举是异步落定的,不订阅的话这一帧画完就再没有重画,
2413
+ * 图上会永远停在「正在读取…」(#744 实测)。 */
2395
2414
  function DirectoryBrowserFromRow(props) {
2415
+ const state = react.useSyncExternalStore(props.row.subscribe, props.row.state);
2396
2416
  return DirectoryBrowser({
2397
2417
  open: props.open,
2398
- state: props.row.state(),
2418
+ state,
2399
2419
  labels: props.labels,
2400
2420
  ...props.row.actions
2401
2421
  });