@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.
- package/app.tsx +11 -2
- package/dist/app.css +1 -1
- package/dist/app.js +29 -29
- package/dist/app.meta.json +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.meta.json +1 -1
- package/features/thread-list/components/FoldRows.tsx +1 -1
- package/features/thread-list/components/GroupSection.tsx +13 -15
- package/features/thread-list/components/{Toolbar.tsx → SettingsPanel.tsx} +19 -36
- package/features/thread-list/components/ThreadList.tsx +4 -6
- package/features/thread-list/components/ThreadRowView.tsx +1 -1
- package/features/thread-list/components/glyphs.tsx +13 -1
- package/features/thread-list/data/same-window.ts +34 -0
- package/features/thread-list/data/useAttentionHold.ts +7 -7
- package/features/thread-list/data/useClientPreferences.ts +15 -7
- package/features/thread-list/data/usePreferences.ts +16 -7
- package/features/thread-list/icons.ts +0 -1
- package/features/thread-list/index.ts +1 -0
- package/features/thread-list/model/attention.ts +29 -9
- package/features/thread-list/model/layout.ts +5 -2
- package/features/thread-list/model/settings.ts +1 -1
- package/features/thread-list/model/state.ts +6 -3
- package/features/thread-list/model/view.ts +34 -15
- package/features/thread-list/testing/fixtures.ts +5 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
153
|
-
|
|
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
|
-
|
|
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 = {
|
|
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
|
|
485
|
-
|
|
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,
|
|
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
|
}
|