@zaclinley/dsh-mailharbor 0.21.0 → 0.22.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
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.22.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 2a996e5: Hide empty recipient lines in the reader.
8
+
9
+ - Expanded conversation cards only render the 收件人/抄送 block when addresses
10
+ exist, and fall back to the loaded detail when the summary has none.
11
+ - The single-message reader no longer prints "收件人:—" either.
12
+
13
+ ## 0.22.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 696333f: Virtualized conversation list.
18
+
19
+ - The grouped list renders only the visible window (plus a 600px overscan)
20
+ once there are more than 30 groups, using cumulative height estimates and
21
+ top/bottom spacers.
22
+ - Scrolling near the bottom loads the next 40 messages automatically while
23
+ preserving the scroll position; the manual 加载更多 button remains.
24
+ - Expanded conversations are measured into the virtual height so they do not
25
+ overlap the following groups.
26
+
3
27
  ## 0.21.0
4
28
 
5
29
  ### Minor Changes
package/lib/client.js CHANGED
@@ -2410,10 +2410,10 @@ function Reader({
2410
2410
  (0, import_react8.createElement)(Icon, { name: detailOpen ? "chevronLeft" : "chevronRight", size: 12 })
2411
2411
  )
2412
2412
  ),
2413
- threaded || !detailOpen ? null : (0, import_react8.createElement)(
2413
+ threaded || !detailOpen || detail.to.length === 0 && detail.cc.length === 0 ? null : (0, import_react8.createElement)(
2414
2414
  "div",
2415
2415
  { className: "mailharbor-reader-recipients" },
2416
- (0, import_react8.createElement)("span", null, `\u6536\u4EF6\u4EBA\uFF1A${addressLine(detail.to) || "\u2014"}`),
2416
+ detail.to.length === 0 ? null : (0, import_react8.createElement)("span", null, `\u6536\u4EF6\u4EBA\uFF1A${addressLine(detail.to)}`),
2417
2417
  detail.cc.length === 0 ? null : (0, import_react8.createElement)("span", null, `\u6284\u9001\uFF1A${addressLine(detail.cc)}`)
2418
2418
  ),
2419
2419
  (0, import_react8.createElement)(
@@ -2534,6 +2534,8 @@ function Reader({
2534
2534
  const sentItem = isSentMailbox(mailbox) && item.to.length > 0;
2535
2535
  const who = sentItem ? item.to[0] ?? item.from : item.from;
2536
2536
  const selectedItem = item.id === detail.id;
2537
+ const toLine = addressLine(item.to) || (itemDetail === void 0 ? "" : addressLine(itemDetail.to));
2538
+ const ccLine = itemDetail === void 0 ? "" : addressLine(itemDetail.cc);
2537
2539
  return (0, import_react8.createElement)(
2538
2540
  "div",
2539
2541
  {
@@ -2568,11 +2570,11 @@ function Reader({
2568
2570
  expandedItem ? (0, import_react8.createElement)(
2569
2571
  "div",
2570
2572
  { className: "mailharbor-thread-body" },
2571
- (0, import_react8.createElement)(
2573
+ toLine === "" && ccLine === "" ? null : (0, import_react8.createElement)(
2572
2574
  "div",
2573
2575
  { className: "mailharbor-thread-recipients small muted" },
2574
- (0, import_react8.createElement)("span", null, `\u6536\u4EF6\u4EBA\uFF1A${addressLine(item.to) || "\u2014"}`),
2575
- itemDetail !== void 0 && itemDetail.cc.length > 0 ? (0, import_react8.createElement)("span", null, `\u6284\u9001\uFF1A${addressLine(itemDetail.cc)}`) : null
2576
+ toLine === "" ? null : (0, import_react8.createElement)("span", null, `\u6536\u4EF6\u4EBA\uFF1A${toLine}`),
2577
+ ccLine === "" ? null : (0, import_react8.createElement)("span", null, `\u6284\u9001\uFF1A${ccLine}`)
2576
2578
  ),
2577
2579
  itemDetail === void 0 ? (0, import_react8.createElement)(
2578
2580
  "div",
@@ -2758,6 +2760,10 @@ function MailPanel() {
2758
2760
  const [selectedKeys, setSelectedKeys] = (0, import_react9.useState)([]);
2759
2761
  const searchRef = (0, import_react9.useRef)(null);
2760
2762
  const threadCache = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
2763
+ const rowsRef = (0, import_react9.useRef)(null);
2764
+ const autoLoadRef = (0, import_react9.useRef)(0);
2765
+ const [listScroll, setListScroll] = (0, import_react9.useState)(0);
2766
+ const [listViewport, setListViewport] = (0, import_react9.useState)(800);
2761
2767
  const [projectOptions, setProjectOptions] = (0, import_react9.useState)([]);
2762
2768
  const [targetProject, setTargetProject] = (0, import_react9.useState)("");
2763
2769
  const [extraction, setExtraction] = (0, import_react9.useState)(null);
@@ -3144,6 +3150,10 @@ ${projectPicker.subject}`,
3144
3150
  setAiInfo(null);
3145
3151
  setSearch("");
3146
3152
  }, [accountScope, source]);
3153
+ (0, import_react9.useEffect)(() => {
3154
+ if (rowsRef.current !== null) rowsRef.current.scrollTop = 0;
3155
+ setListScroll(0);
3156
+ }, [accountScope, mailbox, search, unreadOnly, fromFilter, sinceFilter, beforeFilter, attachmentOnly]);
3147
3157
  (0, import_react9.useEffect)(() => {
3148
3158
  if (selected === null) {
3149
3159
  setThreadItems([]);
@@ -3686,6 +3696,28 @@ ${projectPicker.subject}`,
3686
3696
  };
3687
3697
  const isSent = isSentFolder(mailbox);
3688
3698
  const groups = groupMessages(items);
3699
+ const virtualEnabled = !selectMode && !showingDrafts && groups.length > 30;
3700
+ const offsets = [];
3701
+ let virtualTotal = 0;
3702
+ for (const group of groups) {
3703
+ offsets.push(virtualTotal);
3704
+ const expanded = expandedGroups.includes(group.key);
3705
+ virtualTotal += 64 + (expanded ? Math.max(0, group.items.length - 1) * 64 : 0);
3706
+ }
3707
+ let virtualStart = 0;
3708
+ let virtualEnd = groups.length;
3709
+ if (virtualEnabled) {
3710
+ const top = Math.max(0, listScroll - 600);
3711
+ const bottom = listScroll + listViewport + 600;
3712
+ virtualStart = 0;
3713
+ while (virtualStart < groups.length && (offsets[virtualStart] ?? 0) < top) virtualStart += 1;
3714
+ virtualEnd = virtualStart;
3715
+ while (virtualEnd < groups.length && (offsets[virtualEnd] ?? 0) < bottom) virtualEnd += 1;
3716
+ virtualStart = Math.max(0, virtualStart - 3);
3717
+ virtualEnd = Math.min(groups.length, virtualEnd + 3);
3718
+ }
3719
+ const topSpacer = virtualEnabled ? offsets[virtualStart] ?? 0 : 0;
3720
+ const bottomSpacer = virtualEnabled ? virtualTotal - (offsets[virtualEnd] ?? virtualTotal) : 0;
3689
3721
  const renderMessage = (item, meta = {}) => {
3690
3722
  const sent = isSent && item.to.length > 0;
3691
3723
  const first = sent ? item.to[0] : item.from;
@@ -3758,6 +3790,21 @@ ${projectPicker.subject}`,
3758
3790
  item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, highlight(item.snippet, aiInfo?.keywords[0] ?? search))
3759
3791
  );
3760
3792
  };
3793
+ const renderGroup = (group) => {
3794
+ const latest = group.items[0];
3795
+ if (latest === void 0) return null;
3796
+ const expanded = expandedGroups.includes(group.key);
3797
+ return (0, import_react9.createElement)(
3798
+ "div",
3799
+ { className: "mailharbor-thread", key: group.key },
3800
+ renderMessage(latest, {
3801
+ groupCount: group.items.length,
3802
+ groupKey: group.key,
3803
+ expanded
3804
+ }),
3805
+ expanded ? group.items.slice(1).map((item) => renderMessage(item, { compact: true })) : null
3806
+ );
3807
+ };
3761
3808
  return (0, import_react9.createElement)(
3762
3809
  "div",
3763
3810
  { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
@@ -4283,7 +4330,20 @@ ${projectPicker.subject}`,
4283
4330
  ) : null,
4284
4331
  (0, import_react9.createElement)(
4285
4332
  "div",
4286
- { className: "mailharbor-rows" },
4333
+ {
4334
+ className: "mailharbor-rows",
4335
+ ref: rowsRef,
4336
+ onScroll: (event) => {
4337
+ const target = event.currentTarget;
4338
+ setListScroll(target.scrollTop);
4339
+ setListViewport(target.clientHeight);
4340
+ const now = Date.now();
4341
+ if (now - autoLoadRef.current > 800 && target.scrollTop + target.clientHeight > target.scrollHeight - 220 && page.total > items.length && !loading && aiInfo === null) {
4342
+ autoLoadRef.current = now;
4343
+ setLimit((current) => current + 40);
4344
+ }
4345
+ }
4346
+ },
4287
4347
  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
4348
  "div",
4289
4349
  { className: "mailharbor-empty" },
@@ -4376,21 +4436,17 @@ ${projectPicker.subject}`,
4376
4436
  },
4377
4437
  "\u6E05\u9664\u641C\u7D22"
4378
4438
  ) : 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
- })
4439
+ ) : selectMode ? items.map((item) => renderMessage(item)) : virtualEnabled ? [
4440
+ (0, import_react9.createElement)("div", {
4441
+ key: "virtual-top",
4442
+ style: { height: `${String(topSpacer)}px` }
4443
+ }),
4444
+ ...groups.slice(virtualStart, virtualEnd).map(renderGroup),
4445
+ (0, import_react9.createElement)("div", {
4446
+ key: "virtual-bottom",
4447
+ style: { height: `${String(bottomSpacer)}px` }
4448
+ })
4449
+ ] : groups.map(renderGroup)
4394
4450
  ),
4395
4451
  !showingDrafts && items.length > 0 ? (0, import_react9.createElement)(
4396
4452
  "div",
@@ -6314,7 +6370,7 @@ function TaskEditor({
6314
6370
  }
6315
6371
 
6316
6372
  // src/client/index.tsx
6317
- var VERSION = "0.21.0";
6373
+ var VERSION = "0.22.1";
6318
6374
  var inject = ["slots"];
6319
6375
  var PANEL_ID = "mailharbor";
6320
6376
  var TASKS_PANEL_ID = "mailharbor-tasks";