@zaclinley/dsh-mailharbor 0.21.0 → 0.22.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 696333f: Virtualized conversation list.
8
+
9
+ - The grouped list renders only the visible window (plus a 600px overscan)
10
+ once there are more than 30 groups, using cumulative height estimates and
11
+ top/bottom spacers.
12
+ - Scrolling near the bottom loads the next 40 messages automatically while
13
+ preserving the scroll position; the manual 加载更多 button remains.
14
+ - Expanded conversations are measured into the virtual height so they do not
15
+ overlap the following groups.
16
+
3
17
  ## 0.21.0
4
18
 
5
19
  ### Minor Changes
package/lib/client.js CHANGED
@@ -2758,6 +2758,10 @@ function MailPanel() {
2758
2758
  const [selectedKeys, setSelectedKeys] = (0, import_react9.useState)([]);
2759
2759
  const searchRef = (0, import_react9.useRef)(null);
2760
2760
  const threadCache = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
2761
+ const rowsRef = (0, import_react9.useRef)(null);
2762
+ const autoLoadRef = (0, import_react9.useRef)(0);
2763
+ const [listScroll, setListScroll] = (0, import_react9.useState)(0);
2764
+ const [listViewport, setListViewport] = (0, import_react9.useState)(800);
2761
2765
  const [projectOptions, setProjectOptions] = (0, import_react9.useState)([]);
2762
2766
  const [targetProject, setTargetProject] = (0, import_react9.useState)("");
2763
2767
  const [extraction, setExtraction] = (0, import_react9.useState)(null);
@@ -3144,6 +3148,10 @@ ${projectPicker.subject}`,
3144
3148
  setAiInfo(null);
3145
3149
  setSearch("");
3146
3150
  }, [accountScope, source]);
3151
+ (0, import_react9.useEffect)(() => {
3152
+ if (rowsRef.current !== null) rowsRef.current.scrollTop = 0;
3153
+ setListScroll(0);
3154
+ }, [accountScope, mailbox, search, unreadOnly, fromFilter, sinceFilter, beforeFilter, attachmentOnly]);
3147
3155
  (0, import_react9.useEffect)(() => {
3148
3156
  if (selected === null) {
3149
3157
  setThreadItems([]);
@@ -3686,6 +3694,28 @@ ${projectPicker.subject}`,
3686
3694
  };
3687
3695
  const isSent = isSentFolder(mailbox);
3688
3696
  const groups = groupMessages(items);
3697
+ const virtualEnabled = !selectMode && !showingDrafts && groups.length > 30;
3698
+ const offsets = [];
3699
+ let virtualTotal = 0;
3700
+ for (const group of groups) {
3701
+ offsets.push(virtualTotal);
3702
+ const expanded = expandedGroups.includes(group.key);
3703
+ virtualTotal += 64 + (expanded ? Math.max(0, group.items.length - 1) * 64 : 0);
3704
+ }
3705
+ let virtualStart = 0;
3706
+ let virtualEnd = groups.length;
3707
+ if (virtualEnabled) {
3708
+ const top = Math.max(0, listScroll - 600);
3709
+ const bottom = listScroll + listViewport + 600;
3710
+ virtualStart = 0;
3711
+ while (virtualStart < groups.length && (offsets[virtualStart] ?? 0) < top) virtualStart += 1;
3712
+ virtualEnd = virtualStart;
3713
+ while (virtualEnd < groups.length && (offsets[virtualEnd] ?? 0) < bottom) virtualEnd += 1;
3714
+ virtualStart = Math.max(0, virtualStart - 3);
3715
+ virtualEnd = Math.min(groups.length, virtualEnd + 3);
3716
+ }
3717
+ const topSpacer = virtualEnabled ? offsets[virtualStart] ?? 0 : 0;
3718
+ const bottomSpacer = virtualEnabled ? virtualTotal - (offsets[virtualEnd] ?? virtualTotal) : 0;
3689
3719
  const renderMessage = (item, meta = {}) => {
3690
3720
  const sent = isSent && item.to.length > 0;
3691
3721
  const first = sent ? item.to[0] : item.from;
@@ -3758,6 +3788,21 @@ ${projectPicker.subject}`,
3758
3788
  item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, highlight(item.snippet, aiInfo?.keywords[0] ?? search))
3759
3789
  );
3760
3790
  };
3791
+ const renderGroup = (group) => {
3792
+ const latest = group.items[0];
3793
+ if (latest === void 0) return null;
3794
+ const expanded = expandedGroups.includes(group.key);
3795
+ return (0, import_react9.createElement)(
3796
+ "div",
3797
+ { className: "mailharbor-thread", key: group.key },
3798
+ renderMessage(latest, {
3799
+ groupCount: group.items.length,
3800
+ groupKey: group.key,
3801
+ expanded
3802
+ }),
3803
+ expanded ? group.items.slice(1).map((item) => renderMessage(item, { compact: true })) : null
3804
+ );
3805
+ };
3761
3806
  return (0, import_react9.createElement)(
3762
3807
  "div",
3763
3808
  { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
@@ -4283,7 +4328,20 @@ ${projectPicker.subject}`,
4283
4328
  ) : null,
4284
4329
  (0, import_react9.createElement)(
4285
4330
  "div",
4286
- { className: "mailharbor-rows" },
4331
+ {
4332
+ className: "mailharbor-rows",
4333
+ ref: rowsRef,
4334
+ onScroll: (event) => {
4335
+ const target = event.currentTarget;
4336
+ setListScroll(target.scrollTop);
4337
+ setListViewport(target.clientHeight);
4338
+ const now = Date.now();
4339
+ if (now - autoLoadRef.current > 800 && target.scrollTop + target.clientHeight > target.scrollHeight - 220 && page.total > items.length && !loading && aiInfo === null) {
4340
+ autoLoadRef.current = now;
4341
+ setLimit((current) => current + 40);
4342
+ }
4343
+ }
4344
+ },
4287
4345
  loading && items.length === 0 && drafts.length === 0 ? (0, import_react9.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react9.createElement)(
4288
4346
  "div",
4289
4347
  { className: "mailharbor-empty" },
@@ -4376,21 +4434,17 @@ ${projectPicker.subject}`,
4376
4434
  },
4377
4435
  "\u6E05\u9664\u641C\u7D22"
4378
4436
  ) : null
4379
- ) : selectMode ? items.map((item) => renderMessage(item)) : groups.map((group) => {
4380
- const latest = group.items[0];
4381
- if (latest === void 0) return null;
4382
- const expanded = expandedGroups.includes(group.key);
4383
- return (0, import_react9.createElement)(
4384
- "div",
4385
- { className: "mailharbor-thread", key: group.key },
4386
- renderMessage(latest, {
4387
- groupCount: group.items.length,
4388
- groupKey: group.key,
4389
- expanded
4390
- }),
4391
- expanded ? group.items.slice(1).map((item) => renderMessage(item, { compact: true })) : null
4392
- );
4393
- })
4437
+ ) : selectMode ? items.map((item) => renderMessage(item)) : virtualEnabled ? [
4438
+ (0, import_react9.createElement)("div", {
4439
+ key: "virtual-top",
4440
+ style: { height: `${String(topSpacer)}px` }
4441
+ }),
4442
+ ...groups.slice(virtualStart, virtualEnd).map(renderGroup),
4443
+ (0, import_react9.createElement)("div", {
4444
+ key: "virtual-bottom",
4445
+ style: { height: `${String(bottomSpacer)}px` }
4446
+ })
4447
+ ] : groups.map(renderGroup)
4394
4448
  ),
4395
4449
  !showingDrafts && items.length > 0 ? (0, import_react9.createElement)(
4396
4450
  "div",
@@ -6314,7 +6368,7 @@ function TaskEditor({
6314
6368
  }
6315
6369
 
6316
6370
  // src/client/index.tsx
6317
- var VERSION = "0.21.0";
6371
+ var VERSION = "0.22.0";
6318
6372
  var inject = ["slots"];
6319
6373
  var PANEL_ID = "mailharbor";
6320
6374
  var TASKS_PANEL_ID = "mailharbor-tasks";