@gperezmz/bb-plugin-thread-glance 0.2.2 → 0.2.4

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.
@@ -20,7 +20,7 @@ import {
20
20
  } from "./groups";
21
21
  import { addCounters, countFamilies, EMPTY_COUNTERS, type Counters } from "./counters";
22
22
  import { comparePinned, effectiveSortField, makeComparator, type SortKey } from "./sort";
23
- import { inAttention } from "./attention";
23
+ import { inAttention, isAttended, type SectionFamily } from "./attention";
24
24
  import { mostUrgent, type Flag } from "./state";
25
25
  import type { Targets } from "./expansion";
26
26
  import type { RowNote } from "./notes";
@@ -50,9 +50,12 @@ export interface ThreadRow {
50
50
  /** Title of the thread this one attaches to, for tooltips and labels. */
51
51
  parentTitle: string | null;
52
52
  chip: Chip | null;
53
+ /** The title is bold: the thread is unread and its family is not attended. */
54
+ bold: boolean;
53
55
  /**
54
56
  * The line under the title. In Needs attention, a row that itself needs
55
- * attention says why; any other row says why it waits on you or failed.
57
+ * attention says why, and an attended family's rows have none; any other
58
+ * row says why it waits on you or failed.
56
59
  */
57
60
  note: RowNote | null;
58
61
  /** The title, and the chip with it, step back: see `isDimmed`. */
@@ -122,7 +125,7 @@ export const ATTENTION_GROUP_ID = "attention";
122
125
 
123
126
  /** The Needs attention section: every family with a thread that needs attention, or held there while one of its threads is open. */
124
127
  export interface AttentionView {
125
- /** How many families the section holds, for its header. */
128
+ /** How many families the section holds, attended ones left out, for its header. */
126
129
  familyCount: number;
127
130
  rows: Row[];
128
131
  /** The home group of each thread drawn here, by thread id: a drop on its row acts there. */
@@ -149,12 +152,13 @@ export interface ViewInputs {
149
152
  sections: readonly PluginSidebarSection[];
150
153
  prefs: Preferences;
151
154
  activeThreadId: string | null;
152
- /** The root of the family Needs attention keeps while one of its threads is open. */
153
- heldRootId: string | null;
155
+ /** The family Needs attention keeps while one of its threads is open, as it was when opened: the section orders it by this. */
156
+ held: SectionFamily | null;
154
157
  targets: Targets;
155
158
  }
156
159
 
157
160
  interface Context extends ViewInputs {
161
+ heldRootId: string | null;
158
162
  compare: (a: SortKey, b: SortKey) => number;
159
163
  expandedChildren: ReadonlySet<string>;
160
164
  expandedOlder: ReadonlySet<string>;
@@ -168,6 +172,8 @@ interface Context extends ViewInputs {
168
172
  sectionNames: ReadonlyMap<string, string>;
169
173
  /** The rows are drawn in Needs attention. */
170
174
  drawnInAttention: boolean;
175
+ /** The rows are an attended family's: they draw as in its home group, with no bold title or why line. */
176
+ attended: boolean;
171
177
  }
172
178
 
173
179
  function titleOf(context: Context, id: string | null): string | null {
@@ -219,7 +225,8 @@ function threadRow(
219
225
  nested: options.nested,
220
226
  parentTitle: titleOf(context, info.parentId),
221
227
  chip: options.chip,
222
- note: context.drawnInAttention && needsAttention ? info.attentionNote : info.note,
228
+ bold: info.unread && !context.attended,
229
+ note: context.attended ? null : context.drawnInAttention && needsAttention ? info.attentionNote : info.note,
223
230
  dimmed: isDimmed(context, info, options.chip),
224
231
  hiddenBadge: info.thread.isHidden,
225
232
  crossGroupLabel: crossGroupLabel(context, info, root),
@@ -421,7 +428,7 @@ function familyUnit(context: Context, family: Family): Unit {
421
428
  }
422
429
 
423
430
  /** Waiting on you, then failed, then offline, then unread, then the rest. */
424
- export function urgency(family: Family): number {
431
+ export function urgency(family: Pick<Family, "attentionFlags">): number {
425
432
  const flags = family.attentionFlags;
426
433
  if (flags.has("waits-on-you")) return 0;
427
434
  if (flags.has("unread-failed") || flags.has("queue-failed")) return 1;
@@ -437,7 +444,11 @@ export function urgency(family: Family): number {
437
444
  * with the root's chip, it draws as in its home group and keeps the path.
438
445
  */
439
446
  function attentionFamilyRows(groupContext: Context, family: Family, homeGroupLabel: string): Row[] {
440
- const context: Context = { ...groupContext, drawnInAttention: true };
447
+ const context: Context = {
448
+ ...groupContext,
449
+ drawnInAttention: true,
450
+ attended: isAttended(family, groupContext.heldRootId),
451
+ };
441
452
  const root = family.root;
442
453
  const onPath = new Set<string>([root.thread.id]);
443
454
  for (const info of family.descendants) {
@@ -475,20 +486,26 @@ function buildAttention(
475
486
  ): AttentionView | null {
476
487
  if (families.length === 0) return null;
477
488
  // Most urgent first, then the chosen field in its natural direction: the
478
- // sort direction orders the groups only.
489
+ // sort direction orders the groups only. The held family sorts as it was
490
+ // when opened, so nothing inside it moves it.
479
491
  const compare = makeComparator({
480
492
  field: context.prefs.chronologicalSort,
481
493
  direction: "default",
482
494
  workingFirst: context.prefs.workingFirst,
483
495
  });
484
- const sorted = [...families].sort(
485
- (a, b) =>
496
+ const slotOf = (family: Family): SectionFamily =>
497
+ context.held !== null && family.root.thread.id === context.heldRootId ? context.held : family;
498
+ const sorted = [...families].sort((familyA, familyB) => {
499
+ const a = slotOf(familyA);
500
+ const b = slotOf(familyB);
501
+ return (
486
502
  urgency(a) - urgency(b) ||
487
503
  compare(
488
504
  { thread: a.root.thread, familyAttention: a.latestAttentionAt },
489
505
  { thread: b.root.thread, familyAttention: b.latestAttentionAt },
490
- ),
491
- );
506
+ )
507
+ );
508
+ });
492
509
  const homeGroupIds: Record<string, string> = {};
493
510
  const rows = sorted.flatMap((family) => {
494
511
  const home = homeGroupOf(family);
@@ -497,7 +514,7 @@ function buildAttention(
497
514
  return familyRows;
498
515
  });
499
516
  return {
500
- familyCount: sorted.length,
517
+ familyCount: sorted.filter((family) => !isAttended(family, context.heldRootId)).length,
501
518
  rows,
502
519
  homeGroupIds,
503
520
  };
@@ -601,6 +618,7 @@ export function buildListView(inputs: ViewInputs): ListView {
601
618
  }
602
619
  const context: Context = {
603
620
  ...inputs,
621
+ heldRootId: inputs.held?.root.thread.id ?? null,
604
622
  compare: makeComparator({
605
623
  field: prefs.chronologicalSort,
606
624
  direction: prefs.sortDirection,
@@ -615,6 +633,7 @@ export function buildListView(inputs: ViewInputs): ListView {
615
633
  projectNames: new Map(inputs.projects.map((project) => [project.id, project.name])),
616
634
  sectionNames: new Map(inputs.sections.map((section) => [section.id, section.name])),
617
635
  drawnInAttention: false,
636
+ attended: false,
618
637
  };
619
638
 
620
639
  const byGroup = new Map<string, Family[]>();
@@ -630,7 +649,7 @@ export function buildListView(inputs: ViewInputs): ListView {
630
649
  list.push(family);
631
650
  byGroup.set(id, list);
632
651
  homeOf.set(family, id);
633
- if (inAttention(family, inputs.heldRootId, openRootId)) section.push(family);
652
+ if (inAttention(family, context.heldRootId, openRootId)) section.push(family);
634
653
  }
635
654
  const inSection = new Set(section);
636
655
 
@@ -8,6 +8,7 @@ import { defaultPreferences, type Preferences } from "@/shared/preferences";
8
8
  import { buildForest, type Forest } from "../model/families";
9
9
  import { buildListView, type ListView, type Row } from "../model/view";
10
10
  import type { Targets } from "../model/expansion";
11
+ import type { SectionFamily } from "../model/attention";
11
12
 
12
13
  export const T0 = 1_780_000_000_000;
13
14
 
@@ -108,7 +109,10 @@ export interface Scenario {
108
109
  sections?: PluginSidebarSection[];
109
110
  prefs?: Partial<Preferences>;
110
111
  activeThreadId?: string | null;
112
+ /** The root of the held family, held as it stands now. */
111
113
  heldRootId?: string | null;
114
+ /** The held family as it was when opened; wins over `heldRootId`. */
115
+ held?: SectionFamily | null;
112
116
  targets?: Targets;
113
117
  finishedAt?: Record<string, number>;
114
118
  seenAt?: Record<string, number>;
@@ -141,7 +145,7 @@ export function viewOf(scenario: Scenario): ListView {
141
145
  sections: scenario.sections ?? [],
142
146
  prefs: { ...defaultPreferences(), ...scenario.prefs },
143
147
  activeThreadId: scenario.activeThreadId ?? null,
144
- heldRootId: scenario.heldRootId ?? null,
148
+ held: scenario.held ?? (scenario.heldRootId == null ? null : (forest.familyOf.get(scenario.heldRootId) ?? null)),
145
149
  targets: scenario.targets ?? new Map(),
146
150
  });
147
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gperezmz/bb-plugin-thread-glance",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bb": ">=0.43",