nx-sk 0.14.0 → 0.15.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 CHANGED
@@ -4,6 +4,15 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## 0.15.1 (2026-10-20)
8
+
9
+ ### Fixed
10
+
11
+ - **附件字段里的附件列表改成 5 行高的滚动窗口**:原先是**全部铺开**——20 个附件就把
12
+ 条目表单撑高近 1000px,填别的字段时得拖着整页上下滚。现在默认只露 5 行,
13
+ 窗口内上下滚动查看,下方标「共 N 个附件(窗口内上下滚动查看)」,需要看全时点
14
+ 「展开全部」/「收起到 5 行」。
15
+
7
16
  ## 0.15.0 (2026-10-20)
8
17
 
9
18
  **原始资料袋:每个栏目都自带一个「乱入区」**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx-sk",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "nx-sk · 本机个人资源管理器(server-cli-web 骨架):栏目化存储个人信息与大模型密钥,CLI 与 Web 同源,敏感字段密文落盘。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -81,6 +81,8 @@ export function FileFieldControl({ def, value, onChange }) {
81
81
  const { toast, guard, patchUi } = useStore();
82
82
  const [data, setData] = useState(null);
83
83
  const [dragOver, setDragOver] = useState(false);
84
+ // 附件多的时候不把表单撑长:默认只露 5 行,窗口内上下滚动;需要看全时一键展开
85
+ const [expanded, setExpanded] = useState(false);
84
86
  const uploadRef = useRef(null);
85
87
  // dragenter/dragleave 在子元素间穿梭时会成对触发,用深度计数避免「还没离开就熄掉高亮」
86
88
  const dragDepth = useRef(0);
@@ -137,29 +139,41 @@ export function FileFieldControl({ def, value, onChange }) {
137
139
  onDragLeave={() => { dragDepth.current -= 1; if (dragDepth.current <= 0) { dragDepth.current = 0; setDragOver(false); } }}
138
140
  onDrop={onDrop}
139
141
  >
140
- {ids.map((id) => {
141
- const rec = byId(id);
142
- if (!rec) {
143
- return (
144
- <div className="file-preview" key={id}>
145
- <span className="hint bad grow">引用的附件已不存在({id})—— 重新上传,或移除。</span>
146
- <button type="button" className="btn small ghost" onClick={() => removeAt(id)}>移除</button>
147
- </div>
148
- );
149
- }
150
- return (
151
- <div className="file-preview" key={id}>
152
- {rec.isImage
153
- ? <img src={rec.url} alt={rec.name} />
154
- : <span className="ext-badge" style={{ fontSize: 13, width: 40, textAlign: 'center' }}>{rec.ext ? rec.ext.slice(1).toUpperCase() : 'FILE'}</span>}
155
- <a className="file-preview-name grow" href={rec.url} target="_blank" rel="noreferrer" title={rec.displayPath}>{rec.name}</a>
156
- <a className="btn small ghost" href={rec.downloadUrl}>下载</a>
157
- <Copyable className="hint" text={rec.path} title="点击复制物理路径">路径</Copyable>
158
- <button type="button" className="btn small ghost" onClick={() => removeAt(id)}>移除</button>
159
- </div>
160
- );
161
- })}
142
+ {ids.length ? (
143
+ <div className={`file-picker-list${expanded ? ' expanded' : ''}`}>
144
+ {ids.map((id) => {
145
+ const rec = byId(id);
146
+ if (!rec) {
147
+ return (
148
+ <div className="file-preview" key={id}>
149
+ <span className="hint bad grow">引用的附件已不存在({id})—— 重新上传,或移除。</span>
150
+ <button type="button" className="btn small ghost" onClick={() => removeAt(id)}>移除</button>
151
+ </div>
152
+ );
153
+ }
154
+ return (
155
+ <div className="file-preview" key={id}>
156
+ {rec.isImage
157
+ ? <img src={rec.url} alt={rec.name} />
158
+ : <span className="ext-badge" style={{ fontSize: 13, width: 40, textAlign: 'center' }}>{rec.ext ? rec.ext.slice(1).toUpperCase() : 'FILE'}</span>}
159
+ <a className="file-preview-name grow" href={rec.url} target="_blank" rel="noreferrer" title={rec.displayPath}>{rec.name}</a>
160
+ <a className="btn small ghost" href={rec.downloadUrl}>下载</a>
161
+ <Copyable className="hint" text={rec.path} title="点击复制物理路径">路径</Copyable>
162
+ <button type="button" className="btn small ghost" onClick={() => removeAt(id)}>移除</button>
163
+ </div>
164
+ );
165
+ })}
166
+ </div>
167
+ ) : null}
162
168
  {!ids.length ? <span className="hint">还没有附件。</span> : null}
169
+ {ids.length > 5 ? (
170
+ <div className="file-pick-row">
171
+ <span className="hint">共 {ids.length} 个附件{expanded ? '(已展开全部)' : '(窗口内上下滚动查看)'}</span>
172
+ <button type="button" className="btn small ghost" onClick={() => setExpanded((v) => !v)}>
173
+ {expanded ? '收起到 5 行' : '展开全部'}
174
+ </button>
175
+ </div>
176
+ ) : null}
163
177
  <div className="file-pick-row">
164
178
  <select value="" disabled={full || !available.length} onChange={(e) => add(e.target.value)}>
165
179
  <option value="">{available.length ? '(从附件库选一个,选中即追加)' : '(库里没有可再选的附件)'}</option>
@@ -974,3 +974,16 @@ body.col-resizing, body.col-resizing * { cursor: col-resize !important; user-sel
974
974
  transition: background .15s ease;
975
975
  }
976
976
  .source-old-toggle:hover { background: var(--soft-2); color: var(--ink); }
977
+
978
+ /* 附件列表:**固定 5 行高的滚动窗口**。
979
+ 附件一多,全铺开会把整页表单撑得很长(每行 ~50px,20 个附件就是 1000px),
980
+ 填别的字段时得拖着整页上下滚。改成窗口内滚动,表单高度就稳住了。 */
981
+ .file-picker-list {
982
+ display: flex; flex-direction: column; gap: 4px;
983
+ max-height: 266px; /* ≈ 5 行(每行 ~50px)+ 间隙 */
984
+ overflow-y: auto; overflow-x: hidden;
985
+ padding-right: 2px; /* 给滚动条留位,滚动条不压住「移除」按钮 */
986
+ scrollbar-width: thin;
987
+ }
988
+ .file-picker-list.expanded { max-height: none; }
989
+ .file-picker-list .file-preview { flex-shrink: 0; }
@@ -1 +1 @@
1
- import{a as l}from"./index-CK6204A2.js";let n=null,o=0;const u=1500;function c(t=!1){return!t&&n&&Date.now()-o<u||(o=Date.now(),n=l("/api/files").then(e=>e).catch(e=>{throw n=null,e})),n}function d(){return n=null,c(!0)}function h(t,e){const a=Array.isArray(t)?t:[],r=(Array.isArray(e)?e:[]).filter(Boolean);if(!r.length)return a;const s=new Set(r.map(i=>i.id));return[...r,...a.filter(i=>!s.has(i.id))]}function m(t){return new Promise((e,a)=>{const r=new FileReader;r.onload=()=>e(r.result),r.onerror=()=>a(new Error("读取文件失败")),r.readAsDataURL(t)})}export{m as f,c as l,h as m,d as r};
1
+ import{a as l}from"./index-B1tuW-Bw.js";let n=null,o=0;const u=1500;function c(t=!1){return!t&&n&&Date.now()-o<u||(o=Date.now(),n=l("/api/files").then(e=>e).catch(e=>{throw n=null,e})),n}function d(){return n=null,c(!0)}function h(t,e){const a=Array.isArray(t)?t:[],r=(Array.isArray(e)?e:[]).filter(Boolean);if(!r.length)return a;const s=new Set(r.map(i=>i.id));return[...r,...a.filter(i=>!s.has(i.id))]}function m(t){return new Promise((e,a)=>{const r=new FileReader;r.onload=()=>e(r.result),r.onerror=()=>a(new Error("读取文件失败")),r.readAsDataURL(t)})}export{m as f,c as l,h as m,d as r};