@dsh-sparrow/dsh-file-manage 0.1.0-alpha.1 → 0.1.0-alpha.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.
package/CHANGELOG.md CHANGED
@@ -8,3 +8,7 @@ English | [简体中文](CHANGELOG.zh-CN.md)
8
8
  - Sidebar "Cloud Files" entry + panel (styled like the official Settings / Archive): list pages of 20, Load more cursor pagination, per-file delete (stronger notice for `dsh-` auto-uploaded files), one-click copy file_id
9
9
  - Total count + drive-style quota bar: used / 25 GiB, adaptive-precision percentage, striped empty area, "Loaded X / N" synced with pagination
10
10
  - Reuses the official DeepSeekFilesClient (connection facts from the llm-deepseek settings section + ctx.credentials); no local persistence
11
+
12
+ ## 0.1.0-alpha.2 (2026-09-01 · pre-release)
13
+
14
+ - Panel opens with a full-panel loading state: content is revealed only after both the file list and the total-count request settle, eliminating the loading flicker
@@ -8,3 +8,7 @@
8
8
  - 侧边栏「云端文件」入口 + 面板(样式对齐官方设置 / 归档面板):每页 20 条列表、Load more 游标翻页、单条删除(`dsh-` 自动上传文件给更强提示)、一键复制 file_id
9
9
  - 总数 + 网盘式配额条:已用 / 25 GiB、自适应精度百分比、条纹空白区、「已加载 X / N」随翻页同步
10
10
  - 复用官方 DeepSeekFilesClient(连接事实取自 llm-deepseek 设置节 + ctx.credentials);无本地持久化
11
+
12
+ ## 0.1.0-alpha.2 (2026-09-01 · pre-release)
13
+
14
+ - 面板打开改为整页 loading:文件列表与总数统计都就绪后才展示内容,消除加载闪动
@@ -17,14 +17,19 @@ export function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
17
17
  const [busyDelete, setBusyDelete] = useState(false);
18
18
  const [copied, setCopied] = useState(null);
19
19
  const [summary, setSummary] = useState(null);
20
+ /** 总数统计请求尚未落定:与 loading 一起构成首屏 ready 门(2026-09-01 整页 loading)。 */
21
+ const [summaryPending, setSummaryPending] = useState(false);
20
22
  const closeButtonRef = useRef(null);
21
23
  /** 刷新代际:快速关/开面板产生并发请求时,只让最新一次的结果落地(陈旧响应竞态,archive 同款)。 */
22
24
  const refreshSeqRef = useRef(0);
23
25
  /** 「已复制」反馈 2s 复位(archive 同款)。 */
24
26
  const copiedTimerRef = useRef(null);
25
- const loadFirst = useCallback(() => {
27
+ /** 首屏 / 重试共用:列表与总数统计并发拉取,两者都落定才揭开内容(整页 loading 防闪动,2026-09-01)。 */
28
+ const reload = useCallback(() => {
26
29
  const seq = ++refreshSeqRef.current;
27
30
  setLoading(true);
31
+ setSummaryPending(true);
32
+ setSummary(null);
28
33
  setError(null);
29
34
  listFiles()
30
35
  .then(page => {
@@ -43,7 +48,18 @@ export function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
43
48
  if (seq === refreshSeqRef.current)
44
49
  setLoading(false);
45
50
  });
46
- }, [listFiles]);
51
+ // 总数统计为 best-effort:接口失败只隐藏统计行,不影响列表;同样受刷新代际约束。
52
+ void countFiles()
53
+ .then(next => {
54
+ if (seq === refreshSeqRef.current)
55
+ setSummary(next);
56
+ })
57
+ .catch(() => { })
58
+ .finally(() => {
59
+ if (seq === refreshSeqRef.current)
60
+ setSummaryPending(false);
61
+ });
62
+ }, [listFiles, countFiles]);
47
63
  // 官方弹窗行为:打开时聚焦关闭按钮,Esc 关闭。
48
64
  useEffect(() => {
49
65
  if (!open)
@@ -61,16 +77,11 @@ export function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
61
77
  return;
62
78
  setConfirming(null);
63
79
  setSummary(null);
64
- loadFirst();
65
- // 总数统计为 best-effort:接口失败只隐藏统计行,不影响列表;同样受刷新代际约束。
66
- const seq = refreshSeqRef.current;
67
- void countFiles()
68
- .then(next => {
69
- if (seq === refreshSeqRef.current)
70
- setSummary(next);
71
- })
72
- .catch(() => { });
73
- }, [open, loadFirst, countFiles]);
80
+ setRows([]);
81
+ setHasMore(false);
82
+ setLastId(undefined);
83
+ reload();
84
+ }, [open, reload]);
74
85
  useEffect(() => () => {
75
86
  if (copiedTimerRef.current !== null)
76
87
  window.clearTimeout(copiedTimerRef.current);
@@ -101,6 +112,8 @@ export function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
101
112
  setError(t('copyFailed'));
102
113
  }
103
114
  }, [t]);
115
+ /** 首屏 ready 门:列表 + 总数都落定前展示整页 loading(2026-09-01)。 */
116
+ const initialLoading = loading || summaryPending;
104
117
  const quotaRatio = summary === null ? 0 : storageUsageRatio(summary.totalBytes, summary.quotaBytes);
105
118
  const submitDelete = useCallback(() => {
106
119
  if (confirming === null || busyDelete)
@@ -126,7 +139,7 @@ export function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
126
139
  ? _jsx("div", { className: "dsh-file-manage-quota-fill", style: { width: `${Math.round(quotaRatio * 100)}%` } })
127
140
  : null] }), _jsx("p", { className: "dsh-file-manage-count", children: rows.length < summary.count
128
141
  ? t('summary.loaded', { loaded: rows.length, count: summary.count })
129
- : t('summary.count', { count: summary.count }) })] })) : null, _jsxs("div", { style: { ...styles.body, padding: summary !== null ? '0 24px 24px' : '12px 24px 24px' }, className: "dsh-file-manage-body", children: [error !== null ? (_jsxs("p", { role: "alert", style: { ...styles.secondarySmall, margin: '0 0 12px', color: 'var(--dsw-alias-state-error-primary, #c62828)' }, children: [error, ' ', _jsx("button", { type: "button", className: "dsh-file-manage-btn", onClick: loadFirst, children: t('retry') })] })) : null, loading ? _jsx("p", { style: styles.secondarySmall, children: t('loading') }) : null, !loading && error === null && rows.length === 0 ? _jsx("p", { style: styles.secondarySmall, children: t('empty') }) : null, rows.length > 0 ? (_jsx("div", { className: "dsh-file-manage-card", children: rows.map(row => (_jsxs("div", { style: styles.row, children: [_jsxs("div", { style: { minWidth: 0 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }, children: [_jsx("span", { style: styles.title, title: row.filename, children: row.filename }), row.dshOwned ? _jsx("span", { className: "dsh-file-manage-badge", children: t('dshBadge') }) : null] }), _jsxs("div", { style: styles.secondarySmall, children: [row.sizeLabel, " \u00B7 ", row.createdAtLabel, row.expiresAtLabel !== undefined ? ` · ${t('expires', { time: row.expiresAtLabel })}` : ''] })] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", className: "dsh-file-manage-btn", onClick: () => { void copyId(row); }, children: copied === row.id ? t('copied') : t('copy') }), _jsx("button", { type: "button", className: "dsh-file-manage-btn dsh-file-manage-btn-danger", onClick: () => { setConfirming(row); }, children: t('delete') })] })] }, row.id))) })) : null] }), hasMore && !loading ? (_jsx("div", { style: styles.footerBar, children: _jsx("button", { type: "button", className: "dsh-file-manage-btn", disabled: loadingMore, onClick: () => { void loadMore(); }, children: loadingMore ? t('loading') : t('loadMore') }) })) : null] }) })) : null, confirming !== null ? (_jsx("div", { className: "dsh-file-manage-confirm-overlay", role: "presentation", onMouseDown: (event) => {
142
+ : t('summary.count', { count: summary.count }) })] })) : null, _jsx("div", { style: { ...styles.body, padding: summary !== null ? '0 24px 24px' : '12px 24px 24px' }, className: "dsh-file-manage-body", children: initialLoading ? (_jsxs("div", { className: "dsh-file-manage-loading", role: "status", children: [_jsx("span", { className: "dsh-file-manage-spinner", "aria-hidden": true }), _jsx("span", { children: t('loading') })] })) : (_jsxs(_Fragment, { children: [error !== null ? (_jsxs("p", { role: "alert", style: { ...styles.secondarySmall, margin: '0 0 12px', color: 'var(--dsw-alias-state-error-primary, #c62828)' }, children: [error, ' ', _jsx("button", { type: "button", className: "dsh-file-manage-btn", onClick: () => { reload(); }, children: t('retry') })] })) : null, error === null && rows.length === 0 ? _jsx("p", { style: styles.secondarySmall, children: t('empty') }) : null, rows.length > 0 ? (_jsx("div", { className: "dsh-file-manage-card", children: rows.map(row => (_jsxs("div", { style: styles.row, children: [_jsxs("div", { style: { minWidth: 0 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }, children: [_jsx("span", { style: styles.title, title: row.filename, children: row.filename }), row.dshOwned ? _jsx("span", { className: "dsh-file-manage-badge", children: t('dshBadge') }) : null] }), _jsxs("div", { style: styles.secondarySmall, children: [row.sizeLabel, " \u00B7 ", row.createdAtLabel, row.expiresAtLabel !== undefined ? ` · ${t('expires', { time: row.expiresAtLabel })}` : ''] })] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", className: "dsh-file-manage-btn", onClick: () => { void copyId(row); }, children: copied === row.id ? t('copied') : t('copy') }), _jsx("button", { type: "button", className: "dsh-file-manage-btn dsh-file-manage-btn-danger", onClick: () => { setConfirming(row); }, children: t('delete') })] })] }, row.id))) })) : null] })) }), hasMore && !initialLoading ? (_jsx("div", { style: styles.footerBar, children: _jsx("button", { type: "button", className: "dsh-file-manage-btn", disabled: loadingMore, onClick: () => { void loadMore(); }, children: loadingMore ? t('loading') : t('loadMore') }) })) : null] }) })) : null, confirming !== null ? (_jsx("div", { className: "dsh-file-manage-confirm-overlay", role: "presentation", onMouseDown: (event) => {
130
143
  if (event.target === event.currentTarget && !busyDelete)
131
144
  setConfirming(null);
132
145
  }, children: _jsxs("div", { className: "dsh-file-manage-confirm-card", role: "alertdialog", "aria-modal": "true", "aria-label": t('delete'), onKeyDown: (event) => {
@@ -54,6 +54,31 @@ export function ensureFileManageStyles() {
54
54
  text-overflow: ellipsis;
55
55
  white-space: nowrap;
56
56
  }
57
+ /* 整页 loading:首屏数据(列表 + 总数)落定前占满内容区,避免「打开后加载闪动」(2026-09-01)。 */
58
+ .dsh-file-manage-loading {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ gap: 10px;
63
+ min-height: 240px;
64
+ color: var(--dsw-alias-label-secondary, #6b7280);
65
+ font-size: 14px;
66
+ line-height: 22px;
67
+ }
68
+ .dsh-file-manage-spinner {
69
+ width: 16px;
70
+ height: 16px;
71
+ border: 2px solid var(--dsw-alias-border-l1, #d4d8e0);
72
+ border-top-color: var(--dsw-alias-button-info-fill, #4d6bfe);
73
+ border-radius: 50%;
74
+ animation: dsh-file-manage-spin 0.8s linear infinite;
75
+ }
76
+ @keyframes dsh-file-manage-spin {
77
+ to { transform: rotate(360deg); }
78
+ }
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .dsh-file-manage-spinner { animation: none; }
81
+ }
57
82
  /* 面板滚动区:官方 settings 同款——elevated surface 重绑 l2 滚动条 token(base 默认 l1,浮层上对比度不对)。 */
58
83
  .dsh-file-manage-body {
59
84
  --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
package/lib/client.js CHANGED
@@ -151,6 +151,31 @@ function ensureFileManageStyles() {
151
151
  text-overflow: ellipsis;
152
152
  white-space: nowrap;
153
153
  }
154
+ /* \u6574\u9875 loading\uFF1A\u9996\u5C4F\u6570\u636E\uFF08\u5217\u8868 + \u603B\u6570\uFF09\u843D\u5B9A\u524D\u5360\u6EE1\u5185\u5BB9\u533A\uFF0C\u907F\u514D\u300C\u6253\u5F00\u540E\u52A0\u8F7D\u95EA\u52A8\u300D\uFF082026-09-01\uFF09\u3002 */
155
+ .dsh-file-manage-loading {
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ gap: 10px;
160
+ min-height: 240px;
161
+ color: var(--dsw-alias-label-secondary, #6b7280);
162
+ font-size: 14px;
163
+ line-height: 22px;
164
+ }
165
+ .dsh-file-manage-spinner {
166
+ width: 16px;
167
+ height: 16px;
168
+ border: 2px solid var(--dsw-alias-border-l1, #d4d8e0);
169
+ border-top-color: var(--dsw-alias-button-info-fill, #4d6bfe);
170
+ border-radius: 50%;
171
+ animation: dsh-file-manage-spin 0.8s linear infinite;
172
+ }
173
+ @keyframes dsh-file-manage-spin {
174
+ to { transform: rotate(360deg); }
175
+ }
176
+ @media (prefers-reduced-motion: reduce) {
177
+ .dsh-file-manage-spinner { animation: none; }
178
+ }
154
179
  /* \u9762\u677F\u6EDA\u52A8\u533A\uFF1A\u5B98\u65B9 settings \u540C\u6B3E\u2014\u2014elevated surface \u91CD\u7ED1 l2 \u6EDA\u52A8\u6761 token\uFF08base \u9ED8\u8BA4 l1\uFF0C\u6D6E\u5C42\u4E0A\u5BF9\u6BD4\u5EA6\u4E0D\u5BF9\uFF09\u3002 */
155
180
  .dsh-file-manage-body {
156
181
  --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
@@ -405,12 +430,15 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
405
430
  const [busyDelete, setBusyDelete] = (0, import_react.useState)(false);
406
431
  const [copied, setCopied] = (0, import_react.useState)(null);
407
432
  const [summary, setSummary] = (0, import_react.useState)(null);
433
+ const [summaryPending, setSummaryPending] = (0, import_react.useState)(false);
408
434
  const closeButtonRef = (0, import_react.useRef)(null);
409
435
  const refreshSeqRef = (0, import_react.useRef)(0);
410
436
  const copiedTimerRef = (0, import_react.useRef)(null);
411
- const loadFirst = (0, import_react.useCallback)(() => {
437
+ const reload = (0, import_react.useCallback)(() => {
412
438
  const seq = ++refreshSeqRef.current;
413
439
  setLoading(true);
440
+ setSummaryPending(true);
441
+ setSummary(null);
414
442
  setError(null);
415
443
  listFiles().then((page) => {
416
444
  if (seq !== refreshSeqRef.current) return;
@@ -423,7 +451,13 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
423
451
  }).finally(() => {
424
452
  if (seq === refreshSeqRef.current) setLoading(false);
425
453
  });
426
- }, [listFiles]);
454
+ void countFiles().then((next) => {
455
+ if (seq === refreshSeqRef.current) setSummary(next);
456
+ }).catch(() => {
457
+ }).finally(() => {
458
+ if (seq === refreshSeqRef.current) setSummaryPending(false);
459
+ });
460
+ }, [listFiles, countFiles]);
427
461
  (0, import_react.useEffect)(() => {
428
462
  if (!open) return;
429
463
  closeButtonRef.current?.focus();
@@ -439,13 +473,11 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
439
473
  if (!open) return;
440
474
  setConfirming(null);
441
475
  setSummary(null);
442
- loadFirst();
443
- const seq = refreshSeqRef.current;
444
- void countFiles().then((next) => {
445
- if (seq === refreshSeqRef.current) setSummary(next);
446
- }).catch(() => {
447
- });
448
- }, [open, loadFirst, countFiles]);
476
+ setRows([]);
477
+ setHasMore(false);
478
+ setLastId(void 0);
479
+ reload();
480
+ }, [open, reload]);
449
481
  (0, import_react.useEffect)(() => () => {
450
482
  if (copiedTimerRef.current !== null) window.clearTimeout(copiedTimerRef.current);
451
483
  }, []);
@@ -475,6 +507,7 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
475
507
  setError(t("copyFailed"));
476
508
  }
477
509
  }, [t]);
510
+ const initialLoading = loading || summaryPending;
478
511
  const quotaRatio = summary === null ? 0 : storageUsageRatio(summary.totalBytes, summary.quotaBytes);
479
512
  const submitDelete = (0, import_react.useCallback)(() => {
480
513
  if (confirming === null || busyDelete) return;
@@ -525,14 +558,18 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
525
558
  ] }),
526
559
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-file-manage-count", children: rows.length < summary.count ? t("summary.loaded", { loaded: rows.length, count: summary.count }) : t("summary.count", { count: summary.count }) })
527
560
  ] }) : null,
528
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...styles.body, padding: summary !== null ? "0 24px 24px" : "12px 24px 24px" }, className: "dsh-file-manage-body", children: [
561
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.body, padding: summary !== null ? "0 24px 24px" : "12px 24px 24px" }, className: "dsh-file-manage-body", children: initialLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-file-manage-loading", role: "status", children: [
562
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-file-manage-spinner", "aria-hidden": true }),
563
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("loading") })
564
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
529
565
  error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", style: { ...styles.secondarySmall, margin: "0 0 12px", color: "var(--dsw-alias-state-error-primary, #c62828)" }, children: [
530
566
  error,
531
567
  " ",
532
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-file-manage-btn", onClick: loadFirst, children: t("retry") })
568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-file-manage-btn", onClick: () => {
569
+ reload();
570
+ }, children: t("retry") })
533
571
  ] }) : null,
534
- loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.secondarySmall, children: t("loading") }) : null,
535
- !loading && error === null && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.secondarySmall, children: t("empty") }) : null,
572
+ error === null && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.secondarySmall, children: t("empty") }) : null,
536
573
  rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-file-manage-card", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.row, children: [
537
574
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 0 }, children: [
538
575
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, minWidth: 0 }, children: [
@@ -571,8 +608,8 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
571
608
  )
572
609
  ] })
573
610
  ] }, row.id)) }) : null
574
- ] }),
575
- hasMore && !loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.footerBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
611
+ ] }) }),
612
+ hasMore && !initialLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.footerBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
576
613
  "button",
577
614
  {
578
615
  type: "button",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dsh-sparrow/dsh-file-manage",
3
3
  "description": "DSH web plugin: manage DeepSeek Files API cloud files from the sidebar — list with cursor pagination, delete, and copy file_id (reuses the official DeepSeekFilesClient, no extra credentials)",
4
- "version": "0.1.0-alpha.1",
4
+ "version": "0.1.0-alpha.2",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",