nx-sk 0.20.0 → 0.23.1
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 +106 -0
- package/README.md +2 -0
- package/assets/nx-sk/SKILL.md +3 -1
- package/assets/nx-sk/references/10-sections-entries.md +36 -1
- package/assets/nx-sk/references/15-special-fields.md +21 -2
- package/package.json +4 -4
- package/src/core/source.js +47 -7
- package/src/modules/entries/service.js +18 -6
- package/src/modules/sections/index.js +7 -1
- package/src/modules/sections/service.js +192 -6
- package/src/modules/sections/view.jsx +337 -31
- package/src/modules/skill/index.js +2 -1
- package/src/modules/skill/service.js +38 -1
- package/src/modules/system/service.js +4 -1
- package/src/modules/transfer/service.js +22 -8
- package/src/web/frontend/components/fieldEditor.jsx +16 -3
- package/src/web/frontend/components/listDrag.jsx +82 -0
- package/src/web/frontend/components/sourcePanel.jsx +223 -24
- package/src/web/frontend/components/tableField.jsx +33 -7
- package/src/web/frontend/listSort.js +18 -0
- package/src/web/frontend/store.jsx +4 -1
- package/src/web/frontend/styles.css +49 -0
- package/src/web/public/assets/{files-vbBBJ5q_.js → files-Cr-oMOeX.js} +1 -1
- package/src/web/public/assets/index-BiDzPPaq.css +1 -0
- package/src/web/public/assets/{index-Czcw5Khj.js → index-DPjpWSaw.js} +2 -2
- package/src/web/public/assets/{view-DzlhjD91.js → view-7t9TLUNM.js} +1 -1
- package/src/web/public/assets/view-C8iiWh9-.js +3 -0
- package/src/web/public/assets/{view-B3IpBaPq.js → view-CpkUF3Xr.js} +1 -1
- package/src/web/public/assets/{view-C2PCebaC.js → view-DYFqX9mq.js} +1 -1
- package/src/web/public/assets/view-qaSrOBcK.js +42 -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/view-Dlnt7qN2.js +0 -40
- package/src/web/public/assets/view-PNU16Ybm.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,112 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式参考 [Keep a Changelog](https://keepachangelog.com/),中文正文。
|
|
4
4
|
|
|
5
|
+
## 0.23.1 (2026-11-05)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`skill install --all`:一次装齐 `assets/` 下全部 skill,且直接覆盖**。范围取目录
|
|
10
|
+
扫描(事实源 `assets/<x>/SKILL.md`),不依赖分组表;覆盖隐式生效,不用再补 `--force`。
|
|
11
|
+
与 `[name]` 互斥;`--mode symlink` / `--dry-run` 对整批同样适用。
|
|
12
|
+
|
|
13
|
+
## 0.23.0 (2026-10-31)
|
|
14
|
+
|
|
15
|
+
**栏目结构在面板里可自己搭了:分组能增删改序,字段能改名 / 换组 / 换类型 / 换位置。**
|
|
16
|
+
|
|
17
|
+
过去面板只能「加字段」,连建一个分组都要回 CLI 或换模板——于是「这个栏目长什么样」
|
|
18
|
+
这件事实际上只有命令行能控制,面板就不是控制面板。这次把结构的四个分组操作与两个字段操作
|
|
19
|
+
做成 action(面板与 CLI 同一份),面板「编辑栏目」里直接可改。
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **分组骨架**:`section update --add-group / --rename-group / --move-group / --remove-group`。
|
|
24
|
+
改名可以**连 id 一起换**(组里字段的 `group` 引用跟着改,不会整组从表单里消失)。
|
|
25
|
+
三条规矩全是**拦下来**而不是悄悄做:id 重复报 `CONFLICT`(不覆盖同名分组)、
|
|
26
|
+
**非空分组不给删**(报 `BLOCKED` 并列出是哪几个字段)、内置「原始资料」分组不给删。
|
|
27
|
+
- **字段级改动**:`section update --set-field`(标签 / 类型 / 分组 / 提示 / 必填策略)
|
|
28
|
+
与 `--move-field`(在自己分组内上下挪)。**key 不可改**——值按 key 存在条目里,
|
|
29
|
+
改 key 等于让已有值读不出来;要换就新增字段再搬值。
|
|
30
|
+
- **面板**:「编辑栏目」弹窗里两块——「分组」(id / 名字就地改、↑↓ 换序、空组才能删)
|
|
31
|
+
与「字段字典」(每行可改标签 / 类型 / 分组 / 提示 / 必填策略,组内 ↑↓,删除)。
|
|
32
|
+
一次改动一次落盘(每次改动都要过后端校验,出错时知道是哪一步),改完立刻回读。
|
|
33
|
+
清单类字段(`entrylist` / `table`)的**类型置灰**:列是字段自己的结构,
|
|
34
|
+
就地换类型等于换一个字段——要建就在「新增字段」里连列一起建。
|
|
35
|
+
- 每次改动都把**新旧值**打进输出(`字段 phone:分组 contact → reach`),
|
|
36
|
+
不再只说「已更新栏目 job:fields」——那种输出改错了没法回溯。
|
|
37
|
+
|
|
38
|
+
- **表格 / 清单:行与列都能拖**(Notion 那种手感):拖**表头**搬列、拖行尾的 ⠿ 搬行,
|
|
39
|
+
松手前一条**插入线**告诉你落在哪(只高亮整行说不清「会插到哪」)。排序算法单独一个
|
|
40
|
+
`reorderAt`(插入位语义,有单测)——差一的表现是「拖到第二行结果跑到第三行」。
|
|
41
|
+
整行不做 `draggable`:那会跟单元格里选文本打架,把手是唯一的拖拽入口。
|
|
42
|
+
↑↓ / ←→ 保留但**悬停才浮出**,行内操作键统一收成 16px 小图标键(密集表里的按钮
|
|
43
|
+
不该抢内容的注意力);entrylist 那颗「正常大小的删除按钮」也换成了小图标键。
|
|
44
|
+
|
|
45
|
+
## 0.22.0 (2026-10-31)
|
|
46
|
+
|
|
47
|
+
**原始资料的 text 底层改为 markdown:图片内嵌在正文里,图文不再分家。**
|
|
48
|
+
|
|
49
|
+
聊天记录是图文混排的——把文字存成一条、图存成另一个附件条目,等于把一段话拆得没头没尾。
|
|
50
|
+
所以 `text` 直接以 markdown 为底层:`` 内嵌附件库的图、`` 直接外链,
|
|
51
|
+
正文照旧原样存(面板上的修改是人显式点了「编辑」)。
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **抽屉收集即排版**:粘贴框变成 markdown 收集器——图片直接 **Ctrl+V** 或**拖进编辑器**(自动上传
|
|
56
|
+
附件库并在光标处插入引用)、「插入图片」按钮选文件;图片内嵌显示,其它文件以可点链接内嵌。
|
|
57
|
+
拖拽两条路语义不同:拖**编辑器** = 插进正文,拖**左栏清单** = 入袋成附件条目。
|
|
58
|
+
- **text 条目可显式编辑**:正文面板加「编辑 / 保存修改」(就地更新——是人点的保存,
|
|
59
|
+
「原文不改写」的纪律不破);正文渲染走与 md 字段同一台机器(图片解析 / 外链 / 点击放大 / 任务勾选)。
|
|
60
|
+
- 导出/导入:资料袋正文里内嵌的图片引用计入**引用扫描**(随包带走)并在导入时**重映射**到新库 id
|
|
61
|
+
(`sourceImageIds` 一处实现,打包与完整性检查共用)。
|
|
62
|
+
- CLI:`source get` 的 text 用 `rewriteMdImages` 还原成 `[图片: 说明] 本地路径`(外链标注「外链」);
|
|
63
|
+
标题推断与摘要压掉图片语法(`` 不再当标题、摘要里是 `[图: 说明]`)。
|
|
64
|
+
|
|
65
|
+
### Compatibility
|
|
66
|
+
|
|
67
|
+
- **老数据不用迁移**:普通文字本来就是合法 markdown(段落 + 换行),渲染层无缝接管;
|
|
68
|
+
`--set source='随手记'`、`source add --text` 的用法全部不变。
|
|
69
|
+
|
|
70
|
+
## 0.21.0 (2026-10-30)
|
|
71
|
+
|
|
72
|
+
**条目有了块正文(`entry.body`);栏目有了数据库视图(`section.views`)。**
|
|
73
|
+
|
|
74
|
+
块是「递归的内容单元」,视图是「同一批数据的多种投影」。两者都**只加可选键**,
|
|
75
|
+
老 store 一个字节不用改;反方向的降级被显式拦住(见 Changed)。
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- **块正文**:`entry.body: Block[]`,七种叶块(段落 / 标题 / 图文 / 表格 / 清单 / 附件 / 密钥)
|
|
80
|
+
与两种容器块(内嵌栏目 `section` / 分栏 `column`,容器块的编辑随下一版)。
|
|
81
|
+
- CLI:`entry block add | update | move | remove | remove-many`、`entry body <ref> [--mask]`;
|
|
82
|
+
HTTP 同名路由,**一条 action 双端**(面板与 CLI 走同一份 service)。
|
|
83
|
+
- 详细页下半部分是块面板:双击编辑文本块、表格块复用表格字段的同一台机器(公式/统计照旧)、
|
|
84
|
+
密钥块与 secret 字段同一条 AES-256-GCM 路径,打码态**只读**(把星号写回去等于把星号存成密钥)。
|
|
85
|
+
- 块随导出包往返;file 块里的附件 id 计入引用扫描(`file cleanup` 不会误删);
|
|
86
|
+
md 块里的贴图随包重映射。
|
|
87
|
+
- **数据库视图**:`section.views` / `section.defaultView`。
|
|
88
|
+
- CLI:`section view list | add | update | remove | set-default`(HTTP 同名路由)。
|
|
89
|
+
- **缺省视图是推断的、不落盘**:非 KV 栏目给「表格 / 精简 / 看板」,KV 栏目给「KV」;
|
|
90
|
+
第一次改视图才固化(并把当时生效的那套一起写进去,chips 不会凭空消失)。
|
|
91
|
+
- 投影只存 `key/width/hidden`,标签与类型回查字段字典;引用的字段被删 → 列还在,
|
|
92
|
+
表头写「已删字段」、单元格「—」(不炸、也不静默消失)。
|
|
93
|
+
- 面板:栏目页顶部视图 chips + 表格视图(横向投影,密文列在表格里一律打码)。
|
|
94
|
+
看板**渲染**随下一版(配置与 action 已可用)。
|
|
95
|
+
|
|
96
|
+
### Changed
|
|
97
|
+
|
|
98
|
+
- **`STORE_VERSION` 3 → 4**。只加可选键、无迁移函数;但**老版本读 v4 store 后写入会被拒**
|
|
99
|
+
(`BLOCKED`:「store 由更新版本的 nx-sk 写入」)。不拦的话,老版本的白名单归一会把
|
|
100
|
+
`body` / `views` 静默抹掉,且版本号相同、无从警告。
|
|
101
|
+
- **导出包 `formatVersion` 1 → 2**(包里多了 `entries[].body` 与 `section.views`)。
|
|
102
|
+
老版本读到新包会明确报「版本过新,请先升级」,而不是把两个键静默丢掉。
|
|
103
|
+
导入时视图只在「新建栏目」或「目标没配过视图」时应用——目标已有视图说明用户自己配过,不覆盖。
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- **表格字段的敏感列导出为明文**(0.19 起的老缺陷):`plainValues` 没有 table 分支,
|
|
108
|
+
导出包里是密文 blob,跨机器导入永远解不开。现在与其它敏感字段同一口径(包里明文,保护靠口令)。
|
|
109
|
+
- 块面板在 `bootstrap` 后渲染崩溃(注释与函数声明粘连导致 TDZ)。
|
|
110
|
+
|
|
5
111
|
## 0.20.0 (2026-10-30)
|
|
6
112
|
|
|
7
113
|
**栏目管理重构成树;初始化不再播种一堆场景栏目,改播一个「字段样例」说明书。**
|
package/README.md
CHANGED
|
@@ -168,6 +168,8 @@ 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]` | 字段字典 —— 填之前先看这个,别猜字段名 |
|
|
173
175
|
| `nx-sk file list \| get \| add \| update \| remove` | 附件 CRUD(原件在 `~/nx-sk/files/`,同内容去重) |
|
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 # 各栏目的袋概况
|
|
@@ -151,7 +151,8 @@ nx-sk section update job --fields @fields.json
|
|
|
151
151
|
> `type: 'entrylist'` 是**结构清单字段**:值 = 行对象的**数组**,行数不限(变长);列(子字段)
|
|
152
152
|
> 在字段定义的 `fields` 里声明。适合「一个条目挂多组 链接/账号/密码」这类密集结构——
|
|
153
153
|
> tags 存不了结构,拆成多个字段又不知道会有几组。声明 `sensitive: true` 的列与顶层敏感字段
|
|
154
|
-
> 同一条边界:密文落盘、默认原文、`--mask`
|
|
154
|
+
> 同一条边界:密文落盘、默认原文、`--mask` 打码。面板渲染成密集迷你表,直接增删行,
|
|
155
|
+
> **拖行尾的 ⠿ 调行序**(行内操作键是小图标键,不抢内容的注意力)。
|
|
155
156
|
>
|
|
156
157
|
> ```bash
|
|
157
158
|
> # 建字段(面板「新增字段」选「结构清单」会预填 名称/链接/账号/密码(密文) 四列)
|
|
@@ -170,6 +171,7 @@ nx-sk section update job --fields @fields.json
|
|
|
170
171
|
> 还能有**计算列**(JS 表达式)与**表尾统计**。最根本的差别是**结构归数据**:列定义、行、公式、
|
|
171
172
|
> 统计配置全部存在**条目值里**(`{v, columns, rows, totals?, summary?}`),字段定义里的 `columns`
|
|
172
173
|
> 只是「新建时的初始列」。所以「这一条想多一列」不必改 schema,也不影响同栏目其它条目。
|
|
174
|
+
> 面板上**行与列都能拖**:拖表头搬列、拖行尾的 ⠿ 搬行(松手前有插入线)。
|
|
173
175
|
>
|
|
174
176
|
> ```bash
|
|
175
177
|
> nx-sk section update mysec --add-field '{"key":"tasks","label":"任务表","type":"table"}'
|
|
@@ -198,6 +200,37 @@ nx-sk section update job --fields @fields.json
|
|
|
198
200
|
|
|
199
201
|
> `select` 的 `options` 是**候选项不是白名单**:真实值(如某个少数民族)不在候选里也允许写入。
|
|
200
202
|
|
|
203
|
+
### 栏目结构:分组也是数据,面板里就能改
|
|
204
|
+
|
|
205
|
+
栏目 = 分组(`section.groups`)+ 字段字典(`section.fields`),两样都只是**数据**:
|
|
206
|
+
分组是条目表单里的小标题,**顺序就是它们出现的先后**。面板「编辑栏目」里能直接增删改序,
|
|
207
|
+
CLI 用同一批 action —— 不存在「哪一边功能少一点」。
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
nx-sk section update job --add-group '{"id":"contact","title":"联系信息"}'
|
|
211
|
+
nx-sk section update job --rename-group '{"id":"contact","title":"联系方式"}'
|
|
212
|
+
nx-sk section update job --rename-group '{"id":"contact","title":"联系方式","newId":"reach"}' # 换 id:组里字段的 group 跟着改
|
|
213
|
+
nx-sk section update job --move-group '{"id":"reach","dir":"up"}'
|
|
214
|
+
nx-sk section update job --remove-group reach # 空组才能删
|
|
215
|
+
nx-sk section update job --set-field '{"key":"phone","label":"手机号","group":"reach"}'
|
|
216
|
+
nx-sk section update job --move-field '{"key":"phone","dir":"up"}' # 在自己的分组内换位置
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
每次改动都会把**新旧值**打进输出(`字段 phone:分组 contact → reach`)——
|
|
220
|
+
「已更新栏目 job:fields」这种输出等于没说,改错了没法回溯。
|
|
221
|
+
|
|
222
|
+
三条规矩,全是**拦下来**而不是悄悄做:
|
|
223
|
+
|
|
224
|
+
| 规矩 | 为什么 |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| 分组 id 唯一 | 重复报 CONFLICT,绝不覆盖同名分组(改名用 `--rename-group`) |
|
|
227
|
+
| 非空分组不给删 | 字段上挂着每个条目的值;先把字段挪走(改它的「分组」)或删掉 |
|
|
228
|
+
| 字段 key 不可改 | 值按 key 存在条目里,改 key = 已有值读不出来。要换就新增字段再搬值 |
|
|
229
|
+
|
|
230
|
+
另外两条边界:内置的「原始资料」分组与字段**删不掉**(每个栏目自带,删了下回读又补回来);
|
|
231
|
+
清单类字段(`entrylist` / `table`)的列是字段自己的结构,所以**不提供就地换类型**——
|
|
232
|
+
要建就在「新增字段」里连列一起建。
|
|
233
|
+
|
|
201
234
|
### 填写策略 `fill`:有些格子填了反而减分
|
|
202
235
|
|
|
203
236
|
不是每个格子都值得填。应届生的「上一家公司」、政治面貌为「群众」时的「入党时间」——
|
|
@@ -240,6 +273,8 @@ nx-sk entry get 张三
|
|
|
240
273
|
`nx-sk serve` → 每个栏目一个 tab,共用同一个条目视图:
|
|
241
274
|
|
|
242
275
|
- 左侧条目列表(带完整度),右侧按分组渲染的字段表单;
|
|
276
|
+
- **「栏目」页的「编辑」弹窗能改整个结构**:分组可新增 / 改名(含换 id)/ 上下挪 / 删(空的才让删),
|
|
277
|
+
字段可改标签、类型、分组、提示、必填策略、组内上下挪、删除 —— 一次改动一次落盘;
|
|
243
278
|
- 密文字段默认显示原文;「打码显示(投屏/截图时用)」勾选等价于 `--mask`(选择持久化在 localStorage);
|
|
244
279
|
- 保存是 **PATCH**:只有被你改动过的字段才会发出去(面板会显示「N 处改动」);
|
|
245
280
|
- 每个页面底部会派生显示该模块的 CLI 等价命令(可点击复制)。
|
|
@@ -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` 密文
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nx-sk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
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": {
|
package/src/core/source.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// { id, kind, name, text?, fileId?, url?, addedAt, at?, note?, mapped?,
|
|
8
8
|
// superseded?, supersededBy?, supersedes? }
|
|
9
9
|
//
|
|
10
|
-
// kind text =
|
|
10
|
+
// kind text = 粘贴进来的**markdown**(图片内嵌在正文里,`` / 外链
|
|
11
|
+
// ``——聊天记录图文混排,图和文不能分开存);
|
|
12
|
+
// file = 附件库里的原件(fileId);url = 一个网址
|
|
11
13
|
// name 人读的名字(用户可改),默认从内容推断
|
|
12
14
|
// mapped 整理去向:这条资料已经被同步到哪些字段 key(双向同步的锚点)
|
|
13
15
|
// at **资料自身的时间**(如邮件时间);缺省用 addedAt(入袋时间)
|
|
@@ -15,11 +17,12 @@
|
|
|
15
17
|
//
|
|
16
18
|
// 三条纪律写在代码里,不只是文档:
|
|
17
19
|
// 1. **只增不删**——袋里的东西是原始证据,整理完也留着(要删由人显式做)。
|
|
18
|
-
// 2. **原文不改写**——text
|
|
19
|
-
//
|
|
20
|
+
// 2. **原文不改写**——text 原样存(底层是 markdown;面板上改是**人显式**点了「编辑」),
|
|
21
|
+
// 整理后的结果写进**字段**或写进新的一条,不就地改写原件(否则「原始」这个词就没意义了)。
|
|
20
22
|
// 3. **冲突以新的为准,但旧的留痕**——同名即冲突(同一件事有了新版本):
|
|
21
23
|
// 时间新的那条是「当前版本」,旧的被标 `superseded` 但**仍留在袋里**。
|
|
22
24
|
import { newId, nowIso } from './ids.js';
|
|
25
|
+
import { mdImageIds, parseMdImages } from './mdimages.js';
|
|
23
26
|
|
|
24
27
|
export const SOURCE_KINDS = ['text', 'file', 'url'];
|
|
25
28
|
|
|
@@ -47,12 +50,24 @@ export function sourceItemTime(item) {
|
|
|
47
50
|
return Number.isFinite(t) ? t : 0;
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
/**
|
|
54
|
+
* 从一段 markdown 里推断人读标题:逐个非空行去掉图片语法后取第一个还有字的——
|
|
55
|
+
* 聊天记录的第一行常常**只是一张图**(``),跳过它才能落到人话上。
|
|
56
|
+
*/
|
|
57
|
+
function firstLineTitle(text, fallback = '一段文字') {
|
|
58
|
+
for (const line of String(text || '').split('\n')) {
|
|
59
|
+
if (!line.trim()) continue;
|
|
60
|
+
const stripped = line.replace(/!\[([^\]]*)\]\(\s*[^)\s]+\s*\)/g, '').trim();
|
|
61
|
+
if (stripped) return stripped.slice(0, 40);
|
|
62
|
+
}
|
|
63
|
+
return fallback;
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
function inferName(item) {
|
|
51
67
|
if (item.name) return String(item.name).trim();
|
|
52
68
|
if (item.kind === 'url') return String(item.url || '').slice(0, 60);
|
|
53
69
|
if (item.kind === 'file') return String(item.fileId || '附件');
|
|
54
|
-
|
|
55
|
-
return first.trim().slice(0, 40) || '一段文字';
|
|
70
|
+
return firstLineTitle(item.text);
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
/**
|
|
@@ -170,15 +185,40 @@ export function currentSourceItems(items) {
|
|
|
170
185
|
return (items || []).filter((x) => !x.superseded);
|
|
171
186
|
}
|
|
172
187
|
|
|
173
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* 一条 item 的人读摘要(CLI 一行一条、面板清单副标题都用它)。
|
|
190
|
+
*
|
|
191
|
+
* text 的底层是 **markdown**(图片内嵌在正文里,`` / ``):
|
|
192
|
+
* 摘要里把图片语法压成 `[图: 说明]`——一行摘要里出现一串 `` 没人读得下去。
|
|
193
|
+
*/
|
|
174
194
|
export function sourceItemSummary(item) {
|
|
175
195
|
if (!isSourceItem(item)) return '';
|
|
176
196
|
if (item.kind === 'url') return item.url || '';
|
|
177
197
|
if (item.kind === 'file') return `附件 ${item.fileId}`;
|
|
178
|
-
const t = String(item.text || '').replace(/\s+/g, ' ').trim();
|
|
198
|
+
const t = String(item.text || '').replace(/!\[([^\]]*)\]\(\s*[^)\s]+\s*\)/g, (_m, alt) => `[图: ${alt || '图'}]`).replace(/\s+/g, ' ').trim();
|
|
179
199
|
return t.length > 80 ? `${t.slice(0, 80)}…` : t;
|
|
180
200
|
}
|
|
181
201
|
|
|
202
|
+
/** 这条(或这袋)资料里内嵌的附件图片 id —— 打包导出与完整性检查都用它。 */
|
|
203
|
+
export function sourceImageIds(items) {
|
|
204
|
+
const out = [];
|
|
205
|
+
const seen = new Set();
|
|
206
|
+
for (const item of items || []) {
|
|
207
|
+
if (!isSourceItem(item) || item.kind !== 'text' || !item.text) continue;
|
|
208
|
+
for (const id of mdImageIds(item.text)) {
|
|
209
|
+
if (seen.has(id)) continue;
|
|
210
|
+
seen.add(id);
|
|
211
|
+
out.push(id);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 正文里的 `` 引用(保序,含外链)。面板解析图片、CLI 渲染都用它。 */
|
|
218
|
+
export function sourceItemImages(text) {
|
|
219
|
+
return parseMdImages(text);
|
|
220
|
+
}
|
|
221
|
+
|
|
182
222
|
/** 人读时间:优先资料自身的时间(at),否则入袋时间。 */
|
|
183
223
|
export function sourceItemTimeLabel(item) {
|
|
184
224
|
const raw = item?.at || item?.addedAt || '';
|
|
@@ -878,17 +878,26 @@ function sourceItemsOf(entry) {
|
|
|
878
878
|
}
|
|
879
879
|
|
|
880
880
|
/** 袋里引用的附件解析(名字 / 物理路径 / 链接)——否则只剩一串 f_xxx。 */
|
|
881
|
+
/**
|
|
882
|
+
* source 涉及的附件:file item 指向的 + **text(markdown)正文里内嵌的** ``。
|
|
883
|
+
* 渲染(面板解析图片 / CLI 把 id 还原成路径)与完整性检查都以这份为准。
|
|
884
|
+
*/
|
|
881
885
|
function sourceFiles(store, items) {
|
|
882
886
|
const files = {};
|
|
883
|
-
|
|
884
|
-
if (
|
|
885
|
-
const rec = att.findFileRecord(store,
|
|
886
|
-
if (!rec)
|
|
887
|
+
const put = (id) => {
|
|
888
|
+
if (!id || files[id]) return;
|
|
889
|
+
const rec = att.findFileRecord(store, id);
|
|
890
|
+
if (!rec) return;
|
|
887
891
|
const links = att.fileLinks(rec);
|
|
888
|
-
files[
|
|
892
|
+
files[id] = {
|
|
889
893
|
id: rec.id, name: rec.name, ext: rec.ext, isImage: att.isImageExt(rec.ext),
|
|
890
894
|
path: displayPath(att.fileRecordAbs(rec)), url: links.url, downloadUrl: links.downloadUrl,
|
|
891
895
|
};
|
|
896
|
+
};
|
|
897
|
+
for (const item of items) {
|
|
898
|
+
if (item.kind === 'file' && item.fileId) put(item.fileId);
|
|
899
|
+
// text 的底层是 markdown:正文里内嵌的 `` 也是正式引用
|
|
900
|
+
else if (item.kind === 'text' && item.text) for (const id of mdImageIds(item.text)) put(id);
|
|
892
901
|
}
|
|
893
902
|
return files;
|
|
894
903
|
}
|
|
@@ -1123,7 +1132,10 @@ export function renderSourceGet(d) {
|
|
|
1123
1132
|
const f = d.files?.[item.fileId];
|
|
1124
1133
|
lines.push(` ${f ? `${f.name} ${f.path}` : `附件 ${item.fileId}(库里已不存在)`}`);
|
|
1125
1134
|
} else if (item.kind === 'text') {
|
|
1126
|
-
|
|
1135
|
+
// text 的底层是 markdown:图片引用还原成「[图片: 说明] 本地路径」,外链标注「外链」——
|
|
1136
|
+
// CLI 没法显示图,但读者要知道这里本来有图、图在哪。
|
|
1137
|
+
const text = rewriteMdImages(item.text, (id) => d.files?.[id] || null);
|
|
1138
|
+
for (const line of String(text).split('\n')) lines.push(` ${line}`);
|
|
1127
1139
|
} else if (item.kind === 'url') {
|
|
1128
1140
|
lines.push(` ${item.url}`);
|
|
1129
1141
|
}
|
|
@@ -77,7 +77,7 @@ export default {
|
|
|
77
77
|
id: 'section.update',
|
|
78
78
|
cli: ['section', 'update'],
|
|
79
79
|
http: ['PATCH', '/api/sections/:ref'],
|
|
80
|
-
summary: '改栏目(PATCH
|
|
80
|
+
summary: '改栏目(PATCH 语义:只改传入项)。**整个结构都能改**:分组用 add-group / rename-group / move-group / remove-group,字段用 add-field / set-field / move-field / remove-field / fields / fill;--kv 标成 KV 表',
|
|
81
81
|
args: ['ref'],
|
|
82
82
|
flags: {
|
|
83
83
|
title: { type: 'string' },
|
|
@@ -89,7 +89,13 @@ export default {
|
|
|
89
89
|
fields: { type: 'json', hint: '整体替换字段字典' },
|
|
90
90
|
groups: { type: 'json', hint: '整体替换分组' },
|
|
91
91
|
'add-field': { type: 'json', hint: '{"key":"x","label":"X","type":"text"},可重复' },
|
|
92
|
+
'set-field': { type: 'json', hint: '改已有字段:{"key":"phone","label":"手机号"}(key 不可改),可重复' },
|
|
93
|
+
'move-field': { type: 'json', hint: '{"key":"phone","dir":"up|down"}:在自己的分组内换位置,可重复' },
|
|
92
94
|
'remove-field': { type: 'array', hint: '字段 key,可重复传(一次删一个)' },
|
|
95
|
+
'add-group': { type: 'json', hint: '{"id":"contact","title":"联系信息"},可重复' },
|
|
96
|
+
'rename-group': { type: 'json', hint: '{"id":"contact","title":"联系方式"},换 id 再加 "newId",可重复' },
|
|
97
|
+
'move-group': { type: 'json', hint: '{"id":"contact","dir":"up|down"},可重复' },
|
|
98
|
+
'remove-group': { type: 'array', hint: '分组 id,可重复。**非空不给删**,内置「原始资料」不可删' },
|
|
93
99
|
fill: { type: 'kv', hint: '字段=normal|optional|avoid,可重复。optional 不必填、avoid 填了可能减分,两者都不计入完整度;改回照常填传 normal' },
|
|
94
100
|
'dry-run': { type: 'boolean' },
|
|
95
101
|
},
|