@nanocollective/roster 0.1.0-alpha.39 → 0.1.0-alpha.40
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/dist/cli.js +53 -45
- package/package.json +1 -1
- package/templates/portal/js/views/inbox.js +48 -4
package/dist/cli.js
CHANGED
|
@@ -5552,7 +5552,6 @@ var TIMELINE_PR = `
|
|
|
5552
5552
|
}
|
|
5553
5553
|
`;
|
|
5554
5554
|
var CLOSED_FIRST = 30;
|
|
5555
|
-
var CLOSED_TIMELINE = 40;
|
|
5556
5555
|
var CLOSED_DAYS = 45;
|
|
5557
5556
|
var ISSUE_TYPES = `[ISSUE_COMMENT, CROSS_REFERENCED_EVENT, REFERENCED_EVENT, CLOSED_EVENT,
|
|
5558
5557
|
REOPENED_EVENT, LABELED_EVENT, UNLABELED_EVENT, ASSIGNED_EVENT, UNASSIGNED_EVENT,
|
|
@@ -5561,58 +5560,46 @@ var PR_TYPES = `[ISSUE_COMMENT, CROSS_REFERENCED_EVENT, REFERENCED_EVENT, CLOSED
|
|
|
5561
5560
|
REOPENED_EVENT, MERGED_EVENT, LABELED_EVENT, UNLABELED_EVENT, ASSIGNED_EVENT,
|
|
5562
5561
|
UNASSIGNED_EVENT, RENAMED_TITLE_EVENT, PULL_REQUEST_REVIEW, READY_FOR_REVIEW_EVENT,
|
|
5563
5562
|
REVIEW_REQUESTED_EVENT]`;
|
|
5563
|
+
var LIGHT = `number title url state createdAt updatedAt
|
|
5564
|
+
author { login }
|
|
5565
|
+
labels(first:12) { nodes { name } }
|
|
5566
|
+
assignees(first:8) { nodes { login } }
|
|
5567
|
+
comments { totalCount }`;
|
|
5568
|
+
var LIGHT_PR = `${LIGHT} isDraft mergeable
|
|
5569
|
+
commits(last:1) { nodes { commit { statusCheckRollup { state } } } }`;
|
|
5564
5570
|
var QUERY = `
|
|
5565
5571
|
query($owner:String!, $name:String!) {
|
|
5566
5572
|
repository(owner:$owner, name:$name) {
|
|
5567
|
-
issues(states:OPEN, first:60, orderBy:{field:UPDATED_AT, direction:DESC}) {
|
|
5568
|
-
|
|
5573
|
+
issues(states:OPEN, first:60, orderBy:{field:UPDATED_AT, direction:DESC}) { nodes { ${LIGHT} } }
|
|
5574
|
+
pullRequests(states:OPEN, first:60, orderBy:{field:UPDATED_AT, direction:DESC}) { nodes { ${LIGHT_PR} } }
|
|
5575
|
+
closedIssues: issues(states:CLOSED, first:${CLOSED_FIRST}, orderBy:{field:UPDATED_AT, direction:DESC}) {
|
|
5576
|
+
nodes { ${LIGHT} }
|
|
5577
|
+
}
|
|
5578
|
+
closedPullRequests: pullRequests(
|
|
5579
|
+
states:[CLOSED, MERGED], first:${CLOSED_FIRST}, orderBy:{field:UPDATED_AT, direction:DESC}
|
|
5580
|
+
) { nodes { ${LIGHT_PR} } }
|
|
5581
|
+
}
|
|
5582
|
+
}`;
|
|
5583
|
+
var THREAD_QUERY = `
|
|
5584
|
+
query($owner:String!, $name:String!, $number:Int!) {
|
|
5585
|
+
repository(owner:$owner, name:$name) {
|
|
5586
|
+
issueOrPullRequest(number:$number) {
|
|
5587
|
+
... on Issue {
|
|
5569
5588
|
number title body url state createdAt updatedAt
|
|
5570
5589
|
author { login }
|
|
5571
5590
|
${REACTIONS}
|
|
5572
5591
|
labels(first:12) { nodes { name } }
|
|
5573
5592
|
assignees(first:8) { nodes { login } }
|
|
5574
|
-
timelineItems(last:
|
|
5593
|
+
timelineItems(last:100, itemTypes:${ISSUE_TYPES}) { nodes { ${TIMELINE_COMMON} } }
|
|
5575
5594
|
}
|
|
5576
|
-
|
|
5577
|
-
pullRequests(states:OPEN, first:60, orderBy:{field:UPDATED_AT, direction:DESC}) {
|
|
5578
|
-
nodes {
|
|
5595
|
+
... on PullRequest {
|
|
5579
5596
|
number title body url state createdAt updatedAt isDraft mergeable
|
|
5580
5597
|
author { login }
|
|
5581
5598
|
${REACTIONS}
|
|
5582
5599
|
labels(first:12) { nodes { name } }
|
|
5583
5600
|
assignees(first:8) { nodes { login } }
|
|
5584
5601
|
commits(last:1) { nodes { commit { statusCheckRollup { state } } } }
|
|
5585
|
-
timelineItems(last:
|
|
5586
|
-
nodes { ${TIMELINE_COMMON} ${TIMELINE_PR} }
|
|
5587
|
-
}
|
|
5588
|
-
}
|
|
5589
|
-
}
|
|
5590
|
-
closedIssues: issues(
|
|
5591
|
-
states:CLOSED, first:${CLOSED_FIRST}, orderBy:{field:UPDATED_AT, direction:DESC}
|
|
5592
|
-
) {
|
|
5593
|
-
nodes {
|
|
5594
|
-
number title body url state createdAt updatedAt
|
|
5595
|
-
author { login }
|
|
5596
|
-
${REACTIONS}
|
|
5597
|
-
labels(first:12) { nodes { name } }
|
|
5598
|
-
assignees(first:8) { nodes { login } }
|
|
5599
|
-
timelineItems(last:${CLOSED_TIMELINE}, itemTypes:${ISSUE_TYPES}) {
|
|
5600
|
-
nodes { ${TIMELINE_COMMON} }
|
|
5601
|
-
}
|
|
5602
|
-
}
|
|
5603
|
-
}
|
|
5604
|
-
closedPullRequests: pullRequests(
|
|
5605
|
-
states:[CLOSED, MERGED], first:${CLOSED_FIRST}, orderBy:{field:UPDATED_AT, direction:DESC}
|
|
5606
|
-
) {
|
|
5607
|
-
nodes {
|
|
5608
|
-
number title body url state createdAt updatedAt isDraft
|
|
5609
|
-
author { login }
|
|
5610
|
-
${REACTIONS}
|
|
5611
|
-
labels(first:12) { nodes { name } }
|
|
5612
|
-
assignees(first:8) { nodes { login } }
|
|
5613
|
-
timelineItems(last:${CLOSED_TIMELINE}, itemTypes:${PR_TYPES}) {
|
|
5614
|
-
nodes { ${TIMELINE_COMMON} ${TIMELINE_PR} }
|
|
5615
|
-
}
|
|
5602
|
+
timelineItems(last:100, itemTypes:${PR_TYPES}) { nodes { ${TIMELINE_COMMON} ${TIMELINE_PR} } }
|
|
5616
5603
|
}
|
|
5617
5604
|
}
|
|
5618
5605
|
}
|
|
@@ -5680,6 +5667,9 @@ function shape(n, repo, role, kind) {
|
|
|
5680
5667
|
url: n.url,
|
|
5681
5668
|
state: n.state ?? "OPEN",
|
|
5682
5669
|
comments: events.filter((e) => e.type === "comment").map((e) => ({ author: e.actor, createdAt: e.createdAt, body: e.body ?? "" })),
|
|
5670
|
+
// From the list, the count comes without the comments; from a thread, they are counted.
|
|
5671
|
+
commentCount: n.comments?.totalCount ?? events.filter((e) => e.type === "comment").length,
|
|
5672
|
+
partial: !n.timelineItems,
|
|
5683
5673
|
events,
|
|
5684
5674
|
reactions: reactions(n)
|
|
5685
5675
|
};
|
|
@@ -5794,13 +5784,31 @@ function event(e) {
|
|
|
5794
5784
|
}
|
|
5795
5785
|
async function fetchThread(repo, number, kind) {
|
|
5796
5786
|
const [owner, name] = repo.split("/");
|
|
5797
|
-
const
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5787
|
+
const { stdout } = await run4(
|
|
5788
|
+
"gh",
|
|
5789
|
+
[
|
|
5790
|
+
"api",
|
|
5791
|
+
"graphql",
|
|
5792
|
+
"-f",
|
|
5793
|
+
`query=${THREAD_QUERY}`,
|
|
5794
|
+
"-F",
|
|
5795
|
+
`owner=${owner}`,
|
|
5796
|
+
"-F",
|
|
5797
|
+
`name=${name}`,
|
|
5798
|
+
"-F",
|
|
5799
|
+
`number=${number}`
|
|
5800
|
+
],
|
|
5801
|
+
{ maxBuffer: 64 * 1024 * 1024 }
|
|
5802
|
+
);
|
|
5803
|
+
const n = JSON.parse(stdout)?.data?.repository?.issueOrPullRequest;
|
|
5804
|
+
if (!n) throw new Error(`${repo}#${number} was not found in ${repo}`);
|
|
5805
|
+
const item = shape(n, repo, "", kind);
|
|
5806
|
+
if (kind === "pr") {
|
|
5807
|
+
item.draft = n.isDraft;
|
|
5808
|
+
item.checks = rollup(n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state);
|
|
5809
|
+
item.mergeable = n.mergeable;
|
|
5810
|
+
}
|
|
5811
|
+
return item;
|
|
5804
5812
|
}
|
|
5805
5813
|
function rollup(state) {
|
|
5806
5814
|
if (!state) return "none";
|
package/package.json
CHANGED
|
@@ -92,6 +92,10 @@ export function belongsTo(item, s) {
|
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/* Threads read so far, by repo#number, kept for the session. Each is reused while its
|
|
96
|
+
updatedAt matches the list's, so a thread is fetched again only when it has changed. */
|
|
97
|
+
const THREADS = new Map();
|
|
98
|
+
|
|
95
99
|
export const viewInbox = (m) => inboxScreen(m, {});
|
|
96
100
|
|
|
97
101
|
/**
|
|
@@ -337,9 +341,9 @@ function inboxScreen(m, opts) {
|
|
|
337
341
|
: "") +
|
|
338
342
|
// How much conversation is on a thread, which is most of what tells a live one from
|
|
339
343
|
// something that was filed and never answered.
|
|
340
|
-
(i.comments.length
|
|
341
|
-
? '<span class="cc" title="' + i.comments.length + ' comments">' +
|
|
342
|
-
iconHTML("review") + i.comments.length + "</span>"
|
|
344
|
+
((i.commentCount ?? i.comments.length)
|
|
345
|
+
? '<span class="cc" title="' + (i.commentCount ?? i.comments.length) + ' comments">' +
|
|
346
|
+
iconHTML("review") + (i.commentCount ?? i.comments.length) + "</span>"
|
|
343
347
|
: "") +
|
|
344
348
|
chips +
|
|
345
349
|
'<span class="when">' + ago(i.updatedAt) + "</span>" +
|
|
@@ -378,6 +382,36 @@ function inboxScreen(m, opts) {
|
|
|
378
382
|
post({ thread, ref: item.repo + "#" + item.number }, "/api/notifications/read").catch(() => {});
|
|
379
383
|
}
|
|
380
384
|
|
|
385
|
+
/* The list carries no bodies or timelines (carrying them cost most of an hour's GitHub
|
|
386
|
+
allowance per load), so a thread is read when it is opened, and kept until GitHub says
|
|
387
|
+
it has changed. Reopening one costs nothing. */
|
|
388
|
+
if (item.partial) {
|
|
389
|
+
const key = item.repo + "#" + item.number;
|
|
390
|
+
const kept = THREADS.get(key);
|
|
391
|
+
const keep = { role: item.role, unread: item.unread };
|
|
392
|
+
if (kept && kept.updatedAt === item.updatedAt) {
|
|
393
|
+
Object.assign(item, kept, keep, { partial: false });
|
|
394
|
+
} else {
|
|
395
|
+
viewer.dataset.want = key;
|
|
396
|
+
viewer.replaceChildren(...skeleton("head", 1), ...skeleton("line", 8));
|
|
397
|
+
getThread(item.repo, item.number, item.kind)
|
|
398
|
+
.then((fresh) => {
|
|
399
|
+
if (!fresh || fresh.error) throw new Error(fresh?.error ?? "could not read it");
|
|
400
|
+
THREADS.set(key, fresh);
|
|
401
|
+
Object.assign(item, fresh, keep, { partial: false });
|
|
402
|
+
})
|
|
403
|
+
.catch((e) => {
|
|
404
|
+
item.partial = false;
|
|
405
|
+
item.loadError = String(e.message || e);
|
|
406
|
+
})
|
|
407
|
+
.finally(() => {
|
|
408
|
+
// Somebody may have opened another thread while this one was loading.
|
|
409
|
+
if (viewer.dataset.want === key) openThread(ref);
|
|
410
|
+
});
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
381
415
|
const head = el("div", { className: "thead" });
|
|
382
416
|
head.innerHTML =
|
|
383
417
|
'<div class="meta">' + esc(item.repo) + " · " + (item.kind === "pr" ? "PR " : "") + "#" + item.number +
|
|
@@ -406,6 +440,11 @@ function inboxScreen(m, opts) {
|
|
|
406
440
|
for a wrapper that would only ever hold one thing. */
|
|
407
441
|
const pane = item.kind === "pr" ? el("div") : viewer;
|
|
408
442
|
if (item.kind === "pr") viewer.append(prTabs(item, pane), pane);
|
|
443
|
+
if (item.loadError) {
|
|
444
|
+
pane.append(el("p", { className: "err", textContent: "Couldn't load the conversation: " + item.loadError }));
|
|
445
|
+
delete item.loadError;
|
|
446
|
+
item.partial = true; // so opening it again tries again
|
|
447
|
+
}
|
|
409
448
|
conversation(pane, item);
|
|
410
449
|
viewer.scrollTop = 0;
|
|
411
450
|
}
|
|
@@ -762,7 +801,12 @@ function inboxScreen(m, opts) {
|
|
|
762
801
|
const at = S.inbox.items.findIndex(
|
|
763
802
|
(i) => i.repo === item.repo && i.number === item.number,
|
|
764
803
|
);
|
|
765
|
-
if (at >= 0)
|
|
804
|
+
if (at >= 0) {
|
|
805
|
+
const old = S.inbox.items[at];
|
|
806
|
+
THREADS.set(item.repo + "#" + item.number, fresh);
|
|
807
|
+
// A single thread does not know which part of the org it is in, or whether it is unread.
|
|
808
|
+
S.inbox.items[at] = { ...old, ...fresh, role: old.role, unread: old.unread, partial: false };
|
|
809
|
+
}
|
|
766
810
|
}
|
|
767
811
|
} catch {
|
|
768
812
|
/* the comment posted; a stale pane is not worth an error */
|