nx-sk 0.20.0 → 0.23.0
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/CHANGELOG.md +165 -0
- package/README.md +16 -0
- package/assets/nx-sk/SKILL.md +3 -1
- package/assets/nx-sk/references/10-sections-entries.md +110 -9
- package/assets/nx-sk/references/15-special-fields.md +21 -2
- package/assets/nx-sk/references/60-transfer.md +10 -4
- package/package.json +15 -4
- package/src/core/attachments.js +34 -1
- package/src/core/paths.js +11 -0
- package/src/core/render.js +13 -0
- package/src/core/source.js +47 -7
- package/src/core/store.js +51 -3
- package/src/core/vault.js +18 -2
- package/src/modules/entries/aiPending.js +303 -0
- package/src/modules/entries/blockPanel.jsx +239 -0
- package/src/modules/entries/boardView.jsx +136 -0
- package/src/modules/entries/entriesTable.jsx +119 -0
- package/src/modules/entries/index.js +147 -0
- package/src/modules/entries/service.js +535 -6
- package/src/modules/entries/view.jsx +136 -6
- package/src/modules/sections/index.js +72 -1
- package/src/modules/sections/service.js +482 -7
- package/src/modules/sections/view.jsx +337 -31
- package/src/modules/settings/service.js +90 -7
- package/src/modules/system/service.js +19 -3
- package/src/modules/transfer/service.js +160 -12
- package/src/shared/blocks.js +400 -0
- package/src/shared/navTree.js +252 -0
- package/src/shared/views.js +221 -0
- package/src/web/frontend/App.jsx +200 -234
- package/src/web/frontend/activeView.js +56 -0
- package/src/web/frontend/blocks/registry.jsx +304 -0
- package/src/web/frontend/components/aiBar.jsx +192 -0
- package/src/web/frontend/components/blockDnd.jsx +69 -0
- package/src/web/frontend/components/fieldEditor.jsx +16 -3
- package/src/web/frontend/components/listDrag.jsx +82 -0
- package/src/web/frontend/components/sidebar.jsx +354 -0
- package/src/web/frontend/components/sourcePanel.jsx +179 -22
- package/src/web/frontend/components/tableField.jsx +33 -7
- package/src/web/frontend/components/viewChips.jsx +43 -0
- package/src/web/frontend/listSort.js +18 -0
- package/src/web/frontend/main.jsx +4 -0
- package/src/web/frontend/panels.jsx +75 -0
- package/src/web/frontend/store.jsx +4 -1
- package/src/web/frontend/styles.css +402 -0
- package/src/web/frontend/workspace.js +253 -0
- package/src/web/public/assets/{files-vbBBJ5q_.js → files-BoJqTGyZ.js} +1 -1
- package/src/web/public/assets/index-DDcA8xZc.js +64 -0
- package/src/web/public/assets/index-FDvbnX_f.css +1 -0
- package/src/web/public/assets/{view-DzlhjD91.js → view-B04bzsph.js} +1 -1
- package/src/web/public/assets/view-CvJr_elT.js +42 -0
- package/src/web/public/assets/{view-C2PCebaC.js → view-DNT9of0L.js} +1 -1
- package/src/web/public/assets/view-tNIpJ3PQ.js +1 -0
- package/src/web/public/assets/view-xwEy1P7C.js +3 -0
- package/src/web/public/index.html +2 -2
- package/src/web/public/assets/index-CYX-3oPr.css +0 -1
- package/src/web/public/assets/index-Czcw5Khj.js +0 -41
- package/src/web/public/assets/view-B3IpBaPq.js +0 -1
- package/src/web/public/assets/view-Dlnt7qN2.js +0 -40
- package/src/web/public/assets/view-PNU16Ybm.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,171 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式参考 [Keep a Changelog](https://keepachangelog.com/),中文正文。
|
|
4
4
|
|
|
5
|
+
## 0.23.0 (2026-10-31)
|
|
6
|
+
|
|
7
|
+
**应用壳换成可停靠的工作区(Notion 式目录树 + FlexLayout);看板视图落地;条目有了块级 AI 的「提议 → 审 → 应用/拒绝」流;栏目结构在面板里可自己搭。**
|
|
8
|
+
|
|
9
|
+
壳是「面板的布局由你自己摆」:栏目只从目录树进,布局跨会话恢复。
|
|
10
|
+
AI **不自带大模型**——提议由调用方(agent 或外部模型)交进来,人只做评审。
|
|
11
|
+
**栏目结构**(分组 + 字段)过去只有命令行能改:面板连建一个分组都要回 CLI 或换模板——
|
|
12
|
+
于是「这个栏目长什么样」这件事面板说了不算。这次把四个分组操作与两个字段操作做成
|
|
13
|
+
action(面板与 CLI 同一份),面板「编辑栏目」里直接可改。
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Notion 式工作区壳**:
|
|
18
|
+
- 顶栏只留全局动作(品牌 / 存储 / 打码 / +面板 / 撤销重做 / 设置 / 刷新);
|
|
19
|
+
**栏目 tab 从顶栏移除**——栏目只从目录树进,两处入口必然分叉。
|
|
20
|
+
- 目录树:栏目 + 分组 + 条目子节点;**拖拽重排与嵌套**(第一次拖动把「从旧导航配置
|
|
21
|
+
推断出的树」固化成显式 `settings.navTree`,之前始终只读推断、不写回);
|
|
22
|
+
隐藏的栏目收在树底(一键恢复,数据不动)。
|
|
23
|
+
- 布局 = FlexLayout 模型:面板可拖到任意一边拆分、弹出成浮窗;布局存
|
|
24
|
+
localStorage(`nx-sk-ui.workspace`),跨会话恢复;撤销/重做按「整段拖拽 = 一步」。
|
|
25
|
+
- 深链升级为两段:`#/栏目` 开该栏目的数据库面板,`#/栏目/条目` 再定位到条目;
|
|
26
|
+
栏目 / 条目缺失时明确提示是哪一级没了。
|
|
27
|
+
- 正文块支持拖拽排序(块头把手);数据库面板即工作区里的一块面板,可与其他面板并列。
|
|
28
|
+
- **看板视图**:列 = select 字段的候选值;**拖卡 = 写操作**,等于 `entry update --set 字段=值`;
|
|
29
|
+
空值与候选外的值落「未分组」列,不静默丢,「未分组」不是落点。
|
|
30
|
+
- **块级 AI(提议 → 审 → 应用/拒绝)**:
|
|
31
|
+
- CLI:`entry ai list | suggest | apply | reject`(HTTP 同名路由)。
|
|
32
|
+
`suggest` 收 `--from @proposal.json`(AI 给出的**最终正文**)与 `--prompt`(指令留痕),
|
|
33
|
+
只落一条 pending;`apply` 才写 store;`reject` 只删文件。
|
|
34
|
+
- **pending 落 `~/nx-sk/pending/`(运行态)**:不进 `store.json`、不进导出包、不进迁移副本
|
|
35
|
+
——里面对 secret 块是明文,落进数据面就等于写进备份与导出面。删目录即清空。
|
|
36
|
+
- diff 是**字符级**的:`same / del / ins` 三段,面板上红删绿增;容器块的改动由子块表达
|
|
37
|
+
(不把父块一起报成改动);secret 块只报「变了」,明文不经面板接口。
|
|
38
|
+
- **冲突检测**:pending 记每块应用前的 `baseHash`(服务端算),块被改过 → `CONFLICT`,
|
|
39
|
+
一个字都不写;`--force` 才覆盖。没有差异的提议返回 `noop`,不留空的待审。
|
|
40
|
+
- secret 块的差异按**明文**比对(store 里是密文、提议里是明文),否则一个没动的密钥
|
|
41
|
+
会被报成「密文块已变更」。
|
|
42
|
+
- **表格 / 清单:行与列都能拖**(Notion 那种手感):拖**表头**搬列、拖行尾的 ⠿ 搬行,
|
|
43
|
+
松手前一条**插入线**告诉你落在哪(只高亮整行说不清「会插到哪」)。排序算法单独一个
|
|
44
|
+
`reorderAt`(插入位语义,有单测)——差一的表现是「拖到第二行结果跑到第三行」。
|
|
45
|
+
整行不做 `draggable`:那会跟单元格里选文本打架,把手是唯一的拖拽入口。
|
|
46
|
+
↑↓ / ←→ 保留但**悬停才浮出**,行内操作键统一收成 16px 小图标键(密集表里的按钮
|
|
47
|
+
不该抢内容的注意力);entrylist 那颗「正常大小的删除按钮」也换成了小图标键。
|
|
48
|
+
- **降级运行的主动提示**:store 由更新版本写入时,`bootstrap` 与 `health`(CLI 输出与 JSON)
|
|
49
|
+
会明确说明「磁盘版本 > 当前版本,写入会被拒」——不等用户改一次被 BLOCKED 才知道。
|
|
50
|
+
- **栏目结构在面板里可自己搭**:分组能增删改序,字段能改名 / 换组 / 换类型 / 换位置。
|
|
51
|
+
- **分组骨架**:`section update --add-group / --rename-group / --move-group / --remove-group`。
|
|
52
|
+
改名可以**连 id 一起换**(组里字段的 `group` 引用跟着改,不会整组从表单里消失)。
|
|
53
|
+
三条规矩全是**拦下来**而不是悄悄做:id 重复报 `CONFLICT`(不覆盖同名分组)、
|
|
54
|
+
**非空分组不给删**(报 `BLOCKED` 并列出是哪几个字段)、内置「原始资料」分组不给删。
|
|
55
|
+
- **字段级改动**:`section update --set-field`(标签 / 类型 / 分组 / 提示 / 必填策略)
|
|
56
|
+
与 `--move-field`(在自己分组内上下挪)。**key 不可改**——值按 key 存在条目里,
|
|
57
|
+
改 key 等于让已有值读不出来;要换就新增字段再搬值。
|
|
58
|
+
- **面板**:「编辑栏目」弹窗里两块——「分组」(id / 名字就地改、↑↓ 换序、空组才能删)
|
|
59
|
+
与「字段字典」(每行可改标签 / 类型 / 分组 / 提示 / 必填策略,组内 ↑↓,删除)。
|
|
60
|
+
一次改动一次落盘(每次改动都要过后端校验,出错时知道是哪一步),改完立刻回读。
|
|
61
|
+
清单类字段(`entrylist` / `table`)的**类型置灰**:列是字段自己的结构,
|
|
62
|
+
就地换类型等于换一个字段——要建就在「新增字段」里连列一起建。
|
|
63
|
+
- 每次改动都把**新旧值**打进输出(`字段 phone:分组 contact → reach`),
|
|
64
|
+
不再只说「已更新栏目 job:fields」——那种输出改错了没法回溯。
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **导入会把打码的密钥块加密存盘**(与 0.19 字段路径同类缺陷):导入包里的 `******`
|
|
69
|
+
占位会被当成真密钥加密落盘。现在值清空待重填,并在预览里计数与警告;
|
|
70
|
+
导出预览的掩码统计同步扩到块。
|
|
71
|
+
- **表格/看板视图把详情列挤成一条竖缝**(S2 起的老缺陷,列多的栏目才暴露):
|
|
72
|
+
表格列的 flex 基准用的是内容宽度(75 列 ≈ 7300px),行溢出时按基准比例收缩,
|
|
73
|
+
详情列被抽走全部溢出量。现在表格列 basis 为 0(「拿剩余空间」),
|
|
74
|
+
横向滚动交给表格与看板自己的 overflow。
|
|
75
|
+
- **布局里的空面板组自愈**:拖动/关闭面板可能留下空 tabset,FlexLayout 会把它原样
|
|
76
|
+
存盘、恢复时渲染成一块白板。现在存盘与恢复两端都剪掉空面板组——
|
|
77
|
+
已经存坏的布局**刷新即自愈**,不用点「重置布局」。
|
|
78
|
+
|
|
79
|
+
## 0.22.0 (2026-10-31)
|
|
80
|
+
|
|
81
|
+
**原始资料的 text 底层改为 markdown:图片内嵌在正文里,图文不再分家。**
|
|
82
|
+
|
|
83
|
+
聊天记录是图文混排的——把文字存成一条、图存成另一个附件条目,等于把一段话拆得没头没尾。
|
|
84
|
+
所以 `text` 直接以 markdown 为底层:`` 内嵌附件库的图、`` 直接外链,
|
|
85
|
+
正文照旧原样存(面板上的修改是人显式点了「编辑」)。
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- **抽屉收集即排版**:粘贴框变成 markdown 收集器——图片直接 **Ctrl+V** 或**拖进编辑器**(自动上传
|
|
90
|
+
附件库并在光标处插入引用)、「插入图片」按钮选文件;图片内嵌显示,其它文件以可点链接内嵌。
|
|
91
|
+
拖拽两条路语义不同:拖**编辑器** = 插进正文,拖**左栏清单** = 入袋成附件条目。
|
|
92
|
+
- **text 条目可显式编辑**:正文面板加「编辑 / 保存修改」(就地更新——是人点的保存,
|
|
93
|
+
「原文不改写」的纪律不破);正文渲染走与 md 字段同一台机器(图片解析 / 外链 / 点击放大 / 任务勾选)。
|
|
94
|
+
- 导出/导入:资料袋正文里内嵌的图片引用计入**引用扫描**(随包带走)并在导入时**重映射**到新库 id
|
|
95
|
+
(`sourceImageIds` 一处实现,打包与完整性检查共用)。
|
|
96
|
+
- CLI:`source get` 的 text 用 `rewriteMdImages` 还原成 `[图片: 说明] 本地路径`(外链标注「外链」);
|
|
97
|
+
标题推断与摘要压掉图片语法(`` 不再当标题、摘要里是 `[图: 说明]`)。
|
|
98
|
+
|
|
99
|
+
### Compatibility
|
|
100
|
+
|
|
101
|
+
- **老数据不用迁移**:普通文字本来就是合法 markdown(段落 + 换行),渲染层无缝接管;
|
|
102
|
+
`--set source='随手记'`、`source add --text` 的用法全部不变。
|
|
103
|
+
|
|
104
|
+
## 0.21.0 (2026-10-30)
|
|
105
|
+
|
|
106
|
+
**条目有了块正文(`entry.body`);栏目有了数据库视图(`section.views`)。**
|
|
107
|
+
|
|
108
|
+
块是「递归的内容单元」,视图是「同一批数据的多种投影」。两者都**只加可选键**,
|
|
109
|
+
老 store 一个字节不用改;反方向的降级被显式拦住(见 Changed)。
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
|
|
113
|
+
- **块正文**:`entry.body: Block[]`,七种叶块(段落 / 标题 / 图文 / 表格 / 清单 / 附件 / 密钥)
|
|
114
|
+
与两种容器块(内嵌栏目 `section` / 分栏 `column`,容器块的编辑随下一版)。
|
|
115
|
+
- CLI:`entry block add | update | move | remove | remove-many`、`entry body <ref> [--mask]`;
|
|
116
|
+
HTTP 同名路由,**一条 action 双端**(面板与 CLI 走同一份 service)。
|
|
117
|
+
- 详细页下半部分是块面板:双击编辑文本块、表格块复用表格字段的同一台机器(公式/统计照旧)、
|
|
118
|
+
密钥块与 secret 字段同一条 AES-256-GCM 路径,打码态**只读**(把星号写回去等于把星号存成密钥)。
|
|
119
|
+
- 块随导出包往返;file 块里的附件 id 计入引用扫描(`file cleanup` 不会误删);
|
|
120
|
+
md 块里的贴图随包重映射。
|
|
121
|
+
- **数据库视图**:`section.views` / `section.defaultView`。
|
|
122
|
+
- CLI:`section view list | add | update | remove | set-default`(HTTP 同名路由)。
|
|
123
|
+
- **缺省视图是推断的、不落盘**:非 KV 栏目给「表格 / 精简 / 看板」,KV 栏目给「KV」;
|
|
124
|
+
第一次改视图才固化(并把当时生效的那套一起写进去,chips 不会凭空消失)。
|
|
125
|
+
- 投影只存 `key/width/hidden`,标签与类型回查字段字典;引用的字段被删 → 列还在,
|
|
126
|
+
表头写「已删字段」、单元格「—」(不炸、也不静默消失)。
|
|
127
|
+
- 面板:栏目页顶部视图 chips + 表格视图(横向投影,密文列在表格里一律打码)。
|
|
128
|
+
看板**渲染**随下一版(配置与 action 已可用)。
|
|
129
|
+
- **Notion 式工作区壳**(S3):
|
|
130
|
+
- 顶栏只留全局动作(品牌 / 存储 / 打码 / +面板 / 撤销重做 / 设置 / 刷新);
|
|
131
|
+
**栏目 tab 从顶栏移除**——栏目只从目录树进,两处入口必然分叉。
|
|
132
|
+
- 目录树:栏目 + 分组 + 条目子节点;**拖拽重排与嵌套**(第一次拖动把「从旧导航配置
|
|
133
|
+
推断出的树」固化成显式 `settings.navTree`,之前始终只读推断、不写回);
|
|
134
|
+
隐藏的栏目收在树底(一键恢复,数据不动)。
|
|
135
|
+
- 布局 = FlexLayout 模型:面板可拖到任意一边拆分、弹出成浮窗;布局存
|
|
136
|
+
localStorage(`nx-sk-ui.workspace`),跨会话恢复;撤销/重做按「整段拖拽 = 一步」。
|
|
137
|
+
- 深链升级为两段:`#/栏目` 开该栏目的数据库面板,`#/栏目/条目` 再定位到条目;
|
|
138
|
+
栏目 / 条目缺失时明确提示是哪一级没了。
|
|
139
|
+
- 正文块支持拖拽排序(块头把手);数据库面板即工作区里的一块面板,可与其他面板并列。
|
|
140
|
+
- **块级 AI(提议 → 审 → 应用/拒绝)**(S4):
|
|
141
|
+
- CLI:`entry ai list | suggest | apply | reject`(HTTP 同名路由)。
|
|
142
|
+
`suggest` 收 `--from @proposal.json`(AI 给出的**最终正文**)与 `--prompt`(指令留痕),
|
|
143
|
+
只落一条 pending;`apply` 才写 store;`reject` 只删文件。
|
|
144
|
+
- **nx-sk 不自带大模型**:提议由调用方(agent 或外部模型)产出后交进来。这样
|
|
145
|
+
「agent 走 CLI」与「人在面板上审同一条 pending」是同一套语义,不需要第二个 AI 入口。
|
|
146
|
+
- **pending 落 `~/nx-sk/pending/`(运行态)**:不进 `store.json`、不进导出包、不进迁移副本
|
|
147
|
+
——里面对 secret 块是明文,落进数据面就等于写进备份与导出面。删目录即清空。
|
|
148
|
+
- diff 是**字符级**的:`same / del / ins` 三段,面板上红删绿增;容器块的改动由子块表达
|
|
149
|
+
(不把父块一起报成改动);secret 块只报「变了」,明文不经面板接口。
|
|
150
|
+
- **冲突检测**:pending 记每块应用前的 `baseHash`(服务端算),块被改过 → `CONFLICT`,
|
|
151
|
+
一个字都不写;`--force` 才覆盖。没有差异的提议返回 `noop`,不留空的待审。
|
|
152
|
+
- secret 块的差异按**明文**比对(store 里是密文、提议里是明文),否则一个没动的密钥
|
|
153
|
+
会被报成「密文块已变更」。
|
|
154
|
+
|
|
155
|
+
### Changed
|
|
156
|
+
|
|
157
|
+
- **`STORE_VERSION` 3 → 4**。只加可选键、无迁移函数;但**老版本读 v4 store 后写入会被拒**
|
|
158
|
+
(`BLOCKED`:「store 由更新版本的 nx-sk 写入」)。不拦的话,老版本的白名单归一会把
|
|
159
|
+
`body` / `views` 静默抹掉,且版本号相同、无从警告。
|
|
160
|
+
- **导出包 `formatVersion` 1 → 2**(包里多了 `entries[].body` 与 `section.views`)。
|
|
161
|
+
老版本读到新包会明确报「版本过新,请先升级」,而不是把两个键静默丢掉。
|
|
162
|
+
导入时视图只在「新建栏目」或「目标没配过视图」时应用——目标已有视图说明用户自己配过,不覆盖。
|
|
163
|
+
|
|
164
|
+
### Fixed
|
|
165
|
+
|
|
166
|
+
- **表格字段的敏感列导出为明文**(0.19 起的老缺陷):`plainValues` 没有 table 分支,
|
|
167
|
+
导出包里是密文 blob,跨机器导入永远解不开。现在与其它敏感字段同一口径(包里明文,保护靠口令)。
|
|
168
|
+
- 块面板在 `bootstrap` 后渲染崩溃(注释与函数声明粘连导致 TDZ)。
|
|
169
|
+
|
|
5
170
|
## 0.20.0 (2026-10-30)
|
|
6
171
|
|
|
7
172
|
**栏目管理重构成树;初始化不再播种一堆场景栏目,改播一个「字段样例」说明书。**
|
package/README.md
CHANGED
|
@@ -168,8 +168,24 @@ CLI 与 API 同源,skill 驱动 agent**。落到具体功能上:
|
|
|
168
168
|
| `nx-sk entry remove-many <ids>` | 批量删条目(ids 逗号分隔);找不到的跳过并报告,一个都找不到才报错 |
|
|
169
169
|
| `nx-sk section templates` | 内置模板(job / secret / projects / internships / campus / deploy / platform / thesis) |
|
|
170
170
|
| `nx-sk section sync <id> [--dry-run]` | 按模板**补齐**本栏目缺的分组与字段(只增不改不删)——模板演进后让老栏目追上 |
|
|
171
|
+
| `nx-sk section update <id> --add-group / --rename-group / --move-group / --remove-group` | 改**分组骨架**:新增 / 改名(可换 id,字段引用跟着改)/ 上下挪 / 删(空的才让删) |
|
|
172
|
+
| `nx-sk section update <id> --set-field / --move-field` | 改**字段**:改标签 / 类型 / 分组 / 提示 / 必填策略,组内上下挪(key 不可改)。面板「编辑栏目」同源 |
|
|
171
173
|
| `nx-sk entry list \| get \| add \| update \| remove` | 条目 CRUD(PATCH 语义) |
|
|
172
174
|
| `nx-sk entry fields [--section job]` | 字段字典 —— 填之前先看这个,别猜字段名 |
|
|
175
|
+
| `nx-sk entry block add <ref> --type <段落\\|标题\\|图文\\|表格\\|清单\\|附件\\|密钥> [--data @x.json] [--after <块id>]` | 追加**块正文**(`entry.body`,可选键);详细页下半部分同源 |
|
|
176
|
+
| `nx-sk entry block update <ref> <块id> [--data @x.json]` | 改块(PATCH 语义);密钥块在打码态下写回会被拒(星号不能存成密钥) |
|
|
177
|
+
| `nx-sk entry block move <ref> <块id> --to <index>` | 移动块(拖拽落点 = 一次 move) |
|
|
178
|
+
| `nx-sk entry block remove \\| remove-many <ref> <块id…>` | 删块;批量时找不到的跳过并报告 |
|
|
179
|
+
| `nx-sk entry body <ref> [--mask]` | 读块正文(人读渲染;`--mask` 只作用于密钥块) |
|
|
180
|
+
| `nx-sk entry ai suggest <ref> --prompt "改短一点" --from @proposal.json [--block <块id>]` | 生成一条**AI 提议**:只落 pending,不写 store。`--from` 是 AI 给出的最终正文(nx-sk 不自带大模型) |
|
|
181
|
+
| `nx-sk entry ai list <ref>` | 看待审提议(新的在前)。pending 是运行态,不进 store、不进导出包 |
|
|
182
|
+
| `nx-sk entry ai apply <pendingId> [--block <块id>] [--force]` | 应用提议(**唯一会写 store 的一步**);块被改过 → `CONFLICT`,`--force` 才覆盖 |
|
|
183
|
+
| `nx-sk entry ai reject <pendingId>` | 拒绝提议:删掉 pending 文件,数据一个字不动 |
|
|
184
|
+
| `nx-sk section view list <id>` | 列视图配置;**没配过时给缺省推断**(表格/精简/看板,标 provisional,不落盘) |
|
|
185
|
+
| `nx-sk section view add <id> --type table\\|board\\|kv [--title 名] [--columns 姓名,状态] [--groupby 字段] [--sort 字段:desc]` | 新增视图(第一次 add 把当前生效的推断视图一起固化,chips 不会凭空消失) |
|
|
186
|
+
| `nx-sk section view update <id> <viewId> [--title] [--columns] [--sort] [--set col.字段.width=120]` | 改视图;列宽拖拽结束 = 一次 update |
|
|
187
|
+
| `nx-sk section view remove <id> <viewId>` | 删视图;删光回到缺省推断态(`defaultView` 一并清掉) |
|
|
188
|
+
| `nx-sk section view set-default <id> <viewId>` | 设缺省视图(写 `section.defaultView` —— 是数据,不是面板设置) |
|
|
173
189
|
| `nx-sk file list \| get \| add \| update \| remove` | 附件 CRUD(原件在 `~/nx-sk/files/`,同内容去重) |
|
|
174
190
|
| `nx-sk file add --path <文件> [--copy] [--category C]` | 导入附件:**默认移动原件**,`--copy` 仅复制 |
|
|
175
191
|
| `nx-sk file list [--section <栏目\|none>] [--q 关键字]` | 列附件:按「哪些栏目在引用」筛选(`none` = 未引用);返回栏目汇总与存储目录 |
|
package/assets/nx-sk/SKILL.md
CHANGED
|
@@ -263,9 +263,11 @@ nx-sk entry table 周报 --field 任务表
|
|
|
263
263
|
|
|
264
264
|
原始资料袋(`type: 'source'`)——**每个栏目都自带一个**的「乱入区」。值 = 一组资料条目
|
|
265
265
|
(`{id, kind, name, text|fileId|url, addedAt, note, mapped}`;kind = text / file / url)。
|
|
266
|
-
|
|
266
|
+
杂乱资料(邮件、聊天记录、截图、PDF 摘录)先**原样**丢进袋里,之后由 agent 或人
|
|
267
267
|
按栏目字段字典整理进上面的字段——「先乱入、后整理」因此成立:没有这个袋子,
|
|
268
268
|
用户只有两个坏选择:把乱数据硬塞进结构化字段(把结构搞脏),或干脆不存(信息丢了)。
|
|
269
|
+
**text 的底层是 markdown**:聊天记录图文混排,图片直接写进正文
|
|
270
|
+
(`` / 外链 ``),图和文不分家——形状见 [[15-special-fields]]。
|
|
269
271
|
|
|
270
272
|
```bash
|
|
271
273
|
nx-sk source list # 各栏目的袋概况
|
|
@@ -105,7 +105,64 @@ nx-sk entry get 张三 --json
|
|
|
105
105
|
- 身份证号、手机号、紧急联系人电话这类字段,写之前跟用户确认一次来源。
|
|
106
106
|
- 每次批量写之前先 `--dry-run`。
|
|
107
107
|
|
|
108
|
-
##
|
|
108
|
+
## 五、块正文与 AI 提议(entry.body / entry ai)
|
|
109
|
+
|
|
110
|
+
条目除了结构化字段还有**块正文**(`entry.body`,可选键):段落 / 标题 / 图文 / 表格 / 清单 /
|
|
111
|
+
附件 / 密钥七种叶块,加内嵌栏目(`section`)与分栏(`column`)两种容器块。
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# 建块 / 看块
|
|
115
|
+
nx-sk entry block add <ref> --type paragraph --data '{"text":"第一段"}'
|
|
116
|
+
nx-sk entry block list <ref> # secret 块默认原文,--mask 打码
|
|
117
|
+
nx-sk entry block update <ref> <块id> --data @块.json
|
|
118
|
+
nx-sk entry block move <ref> <块id> --to 0
|
|
119
|
+
|
|
120
|
+
# AI 提议:**AI 永不直接改数据**
|
|
121
|
+
# ① 你(agent / 外部模型)产出提议正文 = 该条目的最终块数组
|
|
122
|
+
# ② suggest 只落一条 pending(~/nx-sk/pending/,运行态,不进 store / 导出包)
|
|
123
|
+
# ③ 人或你在面板/CLI 上 apply(才写 store)或 reject(只删文件)
|
|
124
|
+
nx-sk entry ai suggest <ref> --prompt "把自我介绍改短" --from @proposal.json
|
|
125
|
+
nx-sk entry ai list <ref>
|
|
126
|
+
nx-sk entry ai apply <pendingId> # 块被改过 → CONFLICT,--force 才覆盖
|
|
127
|
+
nx-sk entry ai reject <pendingId>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**纪律**:
|
|
131
|
+
|
|
132
|
+
- proposal.json 是 `{"blocks":[…]}` 或直接块数组;给**最终正文**,不是补丁。
|
|
133
|
+
- 建议先 `nx-sk entry block list <ref> --json` 拿现正文再改,块 id 保持不变
|
|
134
|
+
(同 id 不同内容 = 一次「修改」,出现新 id = 「新增」,缺了的 id = 「删除」)。
|
|
135
|
+
- 密钥块在提议里是明文——pending 落在本机运行态目录(0600),这是有意设计;
|
|
136
|
+
但**不要**把 pending 文件内容贴进对话或日志。
|
|
137
|
+
- 应用前想留底就先 `transfer export`(apply 写 store,与手改同一条边界)。
|
|
138
|
+
|
|
139
|
+
## 六、数据库视图(section.views / section view)
|
|
140
|
+
|
|
141
|
+
视图是**同一批条目的多种投影**:表格(列 = 字段投影)、精简(两列)、看板(按 select 字段分列)、
|
|
142
|
+
KV 栏目的键值面。配置存在 `section.views` / `section.defaultView`(可选键)。
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
nx-sk section view list <id> # 没配过时给缺省推断(provisional: true,不落盘)
|
|
146
|
+
nx-sk section view add <id> --type board --title 按状态 --groupby status
|
|
147
|
+
nx-sk section view update <id> <viewId> --set col.状态.width=180 --sort 状态:desc
|
|
148
|
+
nx-sk section view set-default <id> <viewId>
|
|
149
|
+
nx-sk section view remove <id> <viewId>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**语义**:
|
|
153
|
+
|
|
154
|
+
- **缺省视图是推断的、不落盘**:非 KV 栏目给「表格 / 精简 / 看板」,KV 栏目给「KV」。
|
|
155
|
+
第一次改视图才固化(把当时生效的那套推断视图一起写进去——只写用户新建的那条,
|
|
156
|
+
其余 chips 会凭空消失)。`views: []` 与「没配过」行为一致,都走推断。
|
|
157
|
+
- 投影只存 `key / width / hidden`;标签与类型回查字段字典。引用的字段被删 → 列还在,
|
|
158
|
+
表头写「已删字段」、单元格「—」(不炸、也不静默消失)。
|
|
159
|
+
- **看板的拖卡 = 写操作**:等于 `entry update --set <groupby字段>=<列值>`;
|
|
160
|
+
空值与候选外的值落「未分组」列——展示可以,**不是拖拽落点**(拖进去等于写一个不存在的值)。
|
|
161
|
+
- **没有筛选 DSL**(`--filter` 明确不做):存一个不生效的配置比没有更糟。
|
|
162
|
+
- 视图随导出包往返;导入只在「新建栏目」或「目标没配过视图」时应用——
|
|
163
|
+
目标已有视图说明用户自己配过,不静默覆盖(预览里给警告)。
|
|
164
|
+
|
|
165
|
+
## 七、扩字段字典(不写代码)
|
|
109
166
|
|
|
110
167
|
**最省事的做法:直接写。** 字典里没有的键会自动登记(键名就是它本身),类型按值推断
|
|
111
168
|
(`是/否` → bool,数组 → tags,其余 → text)。
|
|
@@ -151,7 +208,8 @@ nx-sk section update job --fields @fields.json
|
|
|
151
208
|
> `type: 'entrylist'` 是**结构清单字段**:值 = 行对象的**数组**,行数不限(变长);列(子字段)
|
|
152
209
|
> 在字段定义的 `fields` 里声明。适合「一个条目挂多组 链接/账号/密码」这类密集结构——
|
|
153
210
|
> tags 存不了结构,拆成多个字段又不知道会有几组。声明 `sensitive: true` 的列与顶层敏感字段
|
|
154
|
-
> 同一条边界:密文落盘、默认原文、`--mask`
|
|
211
|
+
> 同一条边界:密文落盘、默认原文、`--mask` 打码。面板渲染成密集迷你表,直接增删行,
|
|
212
|
+
> **拖行尾的 ⠿ 调行序**(行内操作键是小图标键,不抢内容的注意力)。
|
|
155
213
|
>
|
|
156
214
|
> ```bash
|
|
157
215
|
> # 建字段(面板「新增字段」选「结构清单」会预填 名称/链接/账号/密码(密文) 四列)
|
|
@@ -170,6 +228,7 @@ nx-sk section update job --fields @fields.json
|
|
|
170
228
|
> 还能有**计算列**(JS 表达式)与**表尾统计**。最根本的差别是**结构归数据**:列定义、行、公式、
|
|
171
229
|
> 统计配置全部存在**条目值里**(`{v, columns, rows, totals?, summary?}`),字段定义里的 `columns`
|
|
172
230
|
> 只是「新建时的初始列」。所以「这一条想多一列」不必改 schema,也不影响同栏目其它条目。
|
|
231
|
+
> 面板上**行与列都能拖**:拖表头搬列、拖行尾的 ⠿ 搬行(松手前有插入线)。
|
|
173
232
|
>
|
|
174
233
|
> ```bash
|
|
175
234
|
> nx-sk section update mysec --add-field '{"key":"tasks","label":"任务表","type":"table"}'
|
|
@@ -198,6 +257,37 @@ nx-sk section update job --fields @fields.json
|
|
|
198
257
|
|
|
199
258
|
> `select` 的 `options` 是**候选项不是白名单**:真实值(如某个少数民族)不在候选里也允许写入。
|
|
200
259
|
|
|
260
|
+
### 栏目结构:分组也是数据,面板里就能改
|
|
261
|
+
|
|
262
|
+
栏目 = 分组(`section.groups`)+ 字段字典(`section.fields`),两样都只是**数据**:
|
|
263
|
+
分组是条目表单里的小标题,**顺序就是它们出现的先后**。面板「编辑栏目」里能直接增删改序,
|
|
264
|
+
CLI 用同一批 action —— 不存在「哪一边功能少一点」。
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
nx-sk section update job --add-group '{"id":"contact","title":"联系信息"}'
|
|
268
|
+
nx-sk section update job --rename-group '{"id":"contact","title":"联系方式"}'
|
|
269
|
+
nx-sk section update job --rename-group '{"id":"contact","title":"联系方式","newId":"reach"}' # 换 id:组里字段的 group 跟着改
|
|
270
|
+
nx-sk section update job --move-group '{"id":"reach","dir":"up"}'
|
|
271
|
+
nx-sk section update job --remove-group reach # 空组才能删
|
|
272
|
+
nx-sk section update job --set-field '{"key":"phone","label":"手机号","group":"reach"}'
|
|
273
|
+
nx-sk section update job --move-field '{"key":"phone","dir":"up"}' # 在自己的分组内换位置
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
每次改动都会把**新旧值**打进输出(`字段 phone:分组 contact → reach`)——
|
|
277
|
+
「已更新栏目 job:fields」这种输出等于没说,改错了没法回溯。
|
|
278
|
+
|
|
279
|
+
三条规矩,全是**拦下来**而不是悄悄做:
|
|
280
|
+
|
|
281
|
+
| 规矩 | 为什么 |
|
|
282
|
+
| --- | --- |
|
|
283
|
+
| 分组 id 唯一 | 重复报 CONFLICT,绝不覆盖同名分组(改名用 `--rename-group`) |
|
|
284
|
+
| 非空分组不给删 | 字段上挂着每个条目的值;先把字段挪走(改它的「分组」)或删掉 |
|
|
285
|
+
| 字段 key 不可改 | 值按 key 存在条目里,改 key = 已有值读不出来。要换就新增字段再搬值 |
|
|
286
|
+
|
|
287
|
+
另外两条边界:内置的「原始资料」分组与字段**删不掉**(每个栏目自带,删了下回读又补回来);
|
|
288
|
+
清单类字段(`entrylist` / `table`)的列是字段自己的结构,所以**不提供就地换类型**——
|
|
289
|
+
要建就在「新增字段」里连列一起建。
|
|
290
|
+
|
|
201
291
|
### 填写策略 `fill`:有些格子填了反而减分
|
|
202
292
|
|
|
203
293
|
不是每个格子都值得填。应届生的「上一家公司」、政治面貌为「群众」时的「入党时间」——
|
|
@@ -235,13 +325,24 @@ nx-sk entry get 张三
|
|
|
235
325
|
> ② 两者都**不要**出现在「还缺什么」的清单里;③ 用户说「这个不用填」时,
|
|
236
326
|
> 用 `--fill <字段>=avoid` 记下来,而不是靠下一轮上下文记住。
|
|
237
327
|
|
|
238
|
-
##
|
|
239
|
-
|
|
240
|
-
`nx-sk serve`
|
|
241
|
-
|
|
242
|
-
-
|
|
243
|
-
|
|
328
|
+
## 八、Web 面板
|
|
329
|
+
|
|
330
|
+
`nx-sk serve` 打开的是**可停靠的工作区**(Notion 式壳),不是一栏一页:
|
|
331
|
+
|
|
332
|
+
- **目录树**(左侧面板):栏目 + 分组 + 条目子节点。拖拽重排与嵌套——第一次拖动会把
|
|
333
|
+
「从旧导航配置推断出的树」固化成显式 `settings.navTree`(`settings get` 里
|
|
334
|
+
`navTreeSource: explicit`,之前始终 `inferred` 只读不写回);隐藏的栏目收在树底,一键恢复。
|
|
335
|
+
- **布局 = FlexLayout 模型**:数据库 / 资料 / 文件 / 导出 / 设置都是面板,可拖到任意一边拆分、
|
|
336
|
+
弹出成浮窗;布局存 localStorage(`nx-sk-ui.workspace`),跨会话恢复,顶栏可撤销/重做。
|
|
337
|
+
- **两段深链**:`#/栏目` 开该栏目的数据库面板,`#/栏目/条目` 再定位到条目;
|
|
338
|
+
栏目 / 条目缺失时明确提示是哪一级没了。
|
|
339
|
+
- 栏目页(数据库面板)顶部是**视图 chips**(表格 / 精简 / 看板 / KV,见 §六);
|
|
340
|
+
左侧条目列表带完整度,右侧按分组渲染的字段表单。
|
|
341
|
+
- 密文字段默认显示原文;顶栏「打码」等价于 `--mask`(选择持久化在 localStorage);
|
|
342
|
+
- **栏目结构自己能搭**:栏目面板里点某个栏目的「编辑」,弹窗里分组可新增 / 改名(含换 id)/
|
|
343
|
+
上下挪 / 删(空的才让删),字段可改标签 / 类型 / 分组 / 提示 / 必填策略、组内上下挪、删除
|
|
344
|
+
—— 一次改动一次落盘,与 CLI 的 `--add-group` 等是同一批 action(见 §七「栏目结构」)。
|
|
244
345
|
- 保存是 **PATCH**:只有被你改动过的字段才会发出去(面板会显示「N 处改动」);
|
|
245
|
-
-
|
|
346
|
+
- 详细页下半部分是**块面板**(见 §五),AI 提议是评审条:渲染 diff + 应用 / 拒绝。
|
|
246
347
|
|
|
247
348
|
面板上没有 CLI 做不到的事——两边共享同一套 action 声明。
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
| type | 值形状 | 结构(列)归谁 | 面板怎么编 | 读它 |
|
|
15
15
|
| --- | --- | --- | --- | --- |
|
|
16
|
-
| `table` | `{v, columns, rows, totals?, summary?}` | **数据自己**(字段定义只给初始列) | 表格控件 +
|
|
17
|
-
| `entrylist` | `[{列key: 值}, …]` | 字段字典的 `fields` |
|
|
16
|
+
| `table` | `{v, columns, rows, totals?, summary?}` | **数据自己**(字段定义只给初始列) | 表格控件 + 「列设置」;**拖表头搬列、拖行尾 ⠿ 搬行** | `entry table` / `entry get` |
|
|
17
|
+
| `entrylist` | `[{列key: 值}, …]` | 字段字典的 `fields` | 密集迷你表(直接增删行,**拖 ⠿ 调行序**) | `entry get` |
|
|
18
18
|
| `md` | 一段 markdown 字符串 | — | 图文编辑器(Ctrl+V 贴图) | `entry get`(图片还原成路径) |
|
|
19
19
|
| `file` | `[附件id, …]` | 字段字典的 `kind` / `maxItems` | 附件选择器(上传 / 从库里选) | `entry get`(附件名 + 物理路径) |
|
|
20
20
|
| `source` | `[{id, kind, name, text\|fileId\|url, addedAt, …}]` | 内置(每个栏目自带一个) | 底部折叠条 → 右侧抽屉 | `source get` |
|
|
@@ -120,6 +120,21 @@
|
|
|
120
120
|
默认值只在**新增行**时用(面板「添加行」按列的 `default` 预填),不会回头改已有行;
|
|
121
121
|
字段级的默认是:初始列 + 表尾自动统计(数字 sum / 勾选 countTrue)+ 列类型默认 text。
|
|
122
122
|
|
|
123
|
+
### 面板上怎么调顺序(拖)
|
|
124
|
+
|
|
125
|
+
排序是表格最常见的编辑动作之一,所以**行与列都能拖**,不用去点箭头:
|
|
126
|
+
|
|
127
|
+
- **搬列**:直接拖**表头**(表头里没有输入框,不会跟选词打架);「列设置」里每列前面的 ⠿
|
|
128
|
+
也能拖,效果一样。
|
|
129
|
+
- **搬行**:拖行尾的 ⠿。整行 `draggable` 不这么做——那会跟单元格里选文本冲突
|
|
130
|
+
(想选一段字结果把行拖走了),所以把手是唯一的拖拽入口。
|
|
131
|
+
- 松手前会有一条**插入线**落在上/下半(行)或左/右半(列)的位置:线在哪就插到哪。
|
|
132
|
+
只高亮「这一行」是做不到这件事的。
|
|
133
|
+
- ↑↓ / ←→ 保留(鼠标悬停在行上才浮出来),键盘与「就挪一格」的场景仍然走得通;
|
|
134
|
+
行内操作键统一是 16px 的小图标键——密集表里的按钮不该抢内容的注意力。
|
|
135
|
+
|
|
136
|
+
`entrylist` 的行同理(拖 ⠿),删除键也从「一颗正常大小的按钮」换成了小图标键。
|
|
137
|
+
|
|
123
138
|
### 常见错 → 改法
|
|
124
139
|
|
|
125
140
|
| 报错 | 改法 |
|
|
@@ -156,6 +171,10 @@
|
|
|
156
171
|
## `source` 原始资料袋
|
|
157
172
|
|
|
158
173
|
- 每个非 KV 栏目自带一个:杂乱资料先**原样**入袋(不改写),之后按字段字典整理。
|
|
174
|
+
- **`text` 的底层是 markdown**——聊天记录图文混排,图和文不能分开存:
|
|
175
|
+
正文里直接写 ``(附件库的图)或 ``(外链图);
|
|
176
|
+
面板抽屉里图片 Ctrl+V / 拖进编辑器就自动上传并插入引用。
|
|
177
|
+
普通文字本来就是合法 markdown,老数据不用迁移。
|
|
159
178
|
- 不计完整度(`fill: optional`);袋里是明文,**敏感内容不要进袋**。
|
|
160
179
|
|
|
161
180
|
## `secret` 密文
|
|
@@ -74,9 +74,14 @@ bundle.json 包清单:包含哪些栏目、整体口令说明
|
|
|
74
74
|
sections/<id>.zip 每个栏目一个上面的单栏目包(逐栏目复用同一套逻辑)
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
- 只打包**条目实际引用**的附件:`file` 字段的 id + `md` 正文里的 `
|
|
77
|
+
- 只打包**条目实际引用**的附件:`file` 字段的 id + `md` 正文里的 `` + `file` **块**里的 id。
|
|
78
78
|
- 导入时附件按 **sha256 去重**:本机已有同一份文件就复用,不会存两份。
|
|
79
|
-
- 条目里的旧 file id 会**自动重映射**到新 id(`file`
|
|
79
|
+
- 条目里的旧 file id 会**自动重映射**到新 id(`file` 字段数组、`file` 块与 md 图片引用都改)。
|
|
80
|
+
- 包版本(`manifest.formatVersion`):**2** 起,包里多 `entries[].body`(块正文)与
|
|
81
|
+
`section.views`(视图配置)。老版本读到 2 号包会明确报「版本过新」,
|
|
82
|
+
不会把这两个键静默丢掉;manifest 里**没有这个键**(0.20 及更早的包)视为 1 号包——
|
|
83
|
+
无正文/无视图,照常导入。
|
|
84
|
+
- 导入时视图配置只在「新建栏目」或「目标没配过视图」时应用——目标已有自己的视图就不覆盖(预览里会提示)。
|
|
80
85
|
- `import-new` 会看包结构自动判断是单栏目包还是整库包,不用手动挑命令。
|
|
81
86
|
|
|
82
87
|
## 口令加密(transfer 层)
|
|
@@ -102,8 +107,9 @@ sections/<id>.zip 每个栏目一个上面的单栏目包(逐栏目复
|
|
|
102
107
|
包里**恒为明文**,保护只靠口令 —— 没有「打码 / 明文」二选(0.11.0 之前的 `--no-secrets` 已移除)。
|
|
103
108
|
要发给别人的包就设口令;不设口令的包自己收好,别当分享件用。
|
|
104
109
|
|
|
105
|
-
读取侧仍兼容旧打码包:遇到 `******`
|
|
106
|
-
|
|
110
|
+
读取侧仍兼容旧打码包:遇到 `******` 会跳过该字段并计数,不会把打码串当新密码存进去;
|
|
111
|
+
**密钥块**同一条口径——打码的密钥块保留、值清空待重填(预览会统计「密钥块打码 N 个」)。
|
|
112
|
+
导入后敏感字段与密钥块用**本机 vaultKey** 重新加密落盘,与手填的没有区别。
|
|
107
113
|
|
|
108
114
|
## 限制与坑
|
|
109
115
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nx-sk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "nx-sk · 本机个人资源管理器(server-cli-web 骨架):栏目化存储个人信息与大模型密钥,CLI 与 Web 同源,敏感字段密文落盘。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/
|
|
21
|
+
"url": "https://github.com/On-DevPlan/nx-sk.git"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/
|
|
23
|
+
"homepage": "https://github.com/On-DevPlan/nx-sk#readme",
|
|
24
24
|
"bugs": {
|
|
25
|
-
"url": "https://github.com/
|
|
25
|
+
"url": "https://github.com/On-DevPlan/nx-sk/issues"
|
|
26
26
|
},
|
|
27
27
|
"packageManager": "pnpm@9.12.0",
|
|
28
28
|
"nxSk": {
|
|
@@ -37,12 +37,23 @@
|
|
|
37
37
|
"dev:serve": "node bin/nx-sk.mjs serve --no-open",
|
|
38
38
|
"build": "vite build && node scripts/link-local.mjs --auto",
|
|
39
39
|
"start": "pnpm run build && node bin/nx-sk.mjs serve",
|
|
40
|
+
"proto:dev": "vite --config plan/block-refactor/prototype/vite.config.js",
|
|
41
|
+
"proto:build": "vite build --config plan/block-refactor/prototype/vite.config.js",
|
|
42
|
+
"loop:on": "node scripts/rp-loop.mjs on",
|
|
43
|
+
"loop:off": "node scripts/rp-loop.mjs off",
|
|
44
|
+
"loop:status": "node scripts/rp-loop.mjs status",
|
|
45
|
+
"loop:log": "node scripts/rp-loop.mjs log",
|
|
46
|
+
"loop:cancel": "node scripts/rp-loop.mjs cancel",
|
|
40
47
|
"lint": "eslint .",
|
|
41
48
|
"test:unit": "node --test",
|
|
42
49
|
"test:smoke": "node tests/smoke.mjs",
|
|
43
50
|
"test": "pnpm run lint && pnpm run build && pnpm run test:smoke && pnpm run test:unit"
|
|
44
51
|
},
|
|
45
52
|
"dependencies": {
|
|
53
|
+
"@dnd-kit/core": "^6.3.1",
|
|
54
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
55
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
56
|
+
"flexlayout-react": "0.11.1",
|
|
46
57
|
"react": "^18.3.1",
|
|
47
58
|
"react-dom": "^18.3.1",
|
|
48
59
|
"react-markdown": "^10.1.0",
|
package/src/core/attachments.js
CHANGED
|
@@ -136,6 +136,24 @@ export function normalizeFileRecord(raw) {
|
|
|
136
136
|
* 0.8.0 之前 `--set` 不拆逗号),所以字符串统一按分隔符拆开。
|
|
137
137
|
* 去空、去重、保序:同一个附件在一个字段里出现两次没有意义。
|
|
138
138
|
*/
|
|
139
|
+
/** 块正文里的附件引用:file 块的 id 列表 + md 块正文里的图片 id。 */
|
|
140
|
+
function blockFileRefs(entry) {
|
|
141
|
+
const out = [];
|
|
142
|
+
const walk = (blocks) => {
|
|
143
|
+
for (const b of blocks || []) {
|
|
144
|
+
if (b.type === 'file' && Array.isArray(b.files)) {
|
|
145
|
+
for (const id of fileIdsOf(b.files)) out.push(id);
|
|
146
|
+
}
|
|
147
|
+
if (b.type === 'md' && typeof b.text === 'string') {
|
|
148
|
+
for (const id of mdImageIds(b.text)) out.push(id);
|
|
149
|
+
}
|
|
150
|
+
if (b.children) walk(b.children);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
walk(entry.body);
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
export function fileIdsOf(value) {
|
|
140
158
|
const raw = value === null || value === undefined
|
|
141
159
|
? []
|
|
@@ -173,7 +191,11 @@ export function findFileRecord(store, ref) {
|
|
|
173
191
|
* 图片是**内嵌在 markdown 里**的,和 file 字段一样是正式引用。
|
|
174
192
|
* 3. `source` 字段 —— 原始资料袋里 kind='file' 的 item(袋是溯源的锚点:
|
|
175
193
|
* 字段值是从这份原件整理出来的,原件被删,溯源就成了空话)。
|
|
176
|
-
*
|
|
194
|
+
* 4. **块正文**(v4)—— `file` 块里挂的附件、`md` 块正文里贴的图。
|
|
195
|
+
* 块是条目正文的一部分,正式程度与字段相同;漏扫它 = 「只在块里挂过」的附件
|
|
196
|
+
* 会被 cleanup 判成未引用然后删掉(真实事故的块版)。
|
|
197
|
+
* 块引用出一条(fieldKey 记块 id);其余同一条(条目, 字段)只出一条。
|
|
198
|
+
* 附件中心据此显示「被哪些栏目 / 条目 / 字段 / 块用到」。
|
|
177
199
|
*/
|
|
178
200
|
export function fileReferences(store, rec) {
|
|
179
201
|
if (!rec) return [];
|
|
@@ -221,6 +243,17 @@ export function fileReferences(store, rec) {
|
|
|
221
243
|
.map((x) => x.fileId);
|
|
222
244
|
if (ids.length && hits(ids)) push(f);
|
|
223
245
|
}
|
|
246
|
+
// ④ 块正文(v4):file 块 + md 块。命中就出一条,fieldKey 记块 id、fieldLabel 记块类型。
|
|
247
|
+
const blockHits = blockFileRefs(e);
|
|
248
|
+
if (blockHits.length && hits(blockHits)) {
|
|
249
|
+
const which = (e.body || []).some((b) => b.type === 'file'
|
|
250
|
+
&& fileIdsOf(b.files).some((ref) => hits([ref]))) ? 'file 块' : 'md 块';
|
|
251
|
+
out.push({
|
|
252
|
+
sectionId: sec.id, sectionTitle: sec.title,
|
|
253
|
+
entryId: e.id, entryTitle: e.title,
|
|
254
|
+
fieldKey: 'body', fieldLabel: which,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
224
257
|
}
|
|
225
258
|
}
|
|
226
259
|
return out;
|
package/src/core/paths.js
CHANGED
|
@@ -61,6 +61,17 @@ export function migrationDir() {
|
|
|
61
61
|
return join(appHome(), 'migrate');
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 块级 AI 的 pending 目录:`~/nx-sk/pending/`(一个 pending 一个文件)。
|
|
66
|
+
*
|
|
67
|
+
* **运行态,不是数据**:不进 store.json、不进导出包、不进迁移副本。
|
|
68
|
+
* 理由见设计 §14.4——pending 里可能有明文密钥(AI 提议的 secret 块),
|
|
69
|
+
* 落进 store 就等于把它写进了备份与导出面。删掉这个目录即清空全部待审提议。
|
|
70
|
+
*/
|
|
71
|
+
export function pendingDir() {
|
|
72
|
+
return join(appHome(), 'pending');
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
export function vaultKeyFile() {
|
|
65
76
|
return join(appHome(), '.vaultkey');
|
|
66
77
|
}
|
package/src/core/render.js
CHANGED
|
@@ -108,6 +108,15 @@ export function displaySensitive(raw, { mask = false, decrypt } = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/** 块正文(v4)的展示形态:secret 块的值走与敏感字段**同一条** displaySensitive 边界。 */
|
|
112
|
+
export function displayBlocks(blocks, { mask = false, decrypt } = {}) {
|
|
113
|
+
return (blocks || []).map((b) => {
|
|
114
|
+
if (b.type === 'secret') return { ...b, value: displaySensitive(b.value, { mask, decrypt }) };
|
|
115
|
+
if (Array.isArray(b.children)) return { ...b, children: displayBlocks(b.children, { mask, decrypt }) };
|
|
116
|
+
return b;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
/**
|
|
112
121
|
* 条目的**唯一序列化出口**(面板、`entry list`、`section dump` 共用一份)。
|
|
113
122
|
* 两个出口各写一遍序列化,迟早会分叉成「CLI 看得到、面板看不到」。
|
|
@@ -147,6 +156,10 @@ export function serializeEntry(section, entry, { mask = false, decrypt } = {}) {
|
|
|
147
156
|
tags: entry.tags || [],
|
|
148
157
|
createdAt: entry.createdAt,
|
|
149
158
|
updatedAt: entry.updatedAt,
|
|
159
|
+
// 块正文(v4):可选键——没有块就不带这个键(别让老条目凭空多出一个空数组)
|
|
160
|
+
...(Array.isArray(entry.body) && entry.body.length
|
|
161
|
+
? { body: displayBlocks(entry.body, { mask, decrypt }) }
|
|
162
|
+
: {}),
|
|
150
163
|
completeness: {
|
|
151
164
|
filled: c.filled,
|
|
152
165
|
total: c.total,
|