taskchef 7.16.0 → 7.17.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/package.json
CHANGED
package/src/dashboard/app.js
CHANGED
|
@@ -16,7 +16,12 @@ import {
|
|
|
16
16
|
turnPresentation,
|
|
17
17
|
} from "./state.js";
|
|
18
18
|
import { openTaskFromControl } from "./actions.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
githubReferenceAccessibleLabel,
|
|
21
|
+
githubReferenceDisplayLabels,
|
|
22
|
+
groupRelatedGitHubLinks,
|
|
23
|
+
referenceSegments,
|
|
24
|
+
} from "./github-links.js";
|
|
20
25
|
import { formatRelativeTime, RelativeTimeController, parsedTimestamp } from "./time.js";
|
|
21
26
|
|
|
22
27
|
const state = {
|
|
@@ -63,23 +68,15 @@ const elements = {
|
|
|
63
68
|
};
|
|
64
69
|
let notificationDescriptionSerial = 0;
|
|
65
70
|
|
|
66
|
-
function
|
|
67
|
-
if (!typePrefix) return label;
|
|
68
|
-
if (link.type === "pull") return `PR ${label}`;
|
|
69
|
-
if (link.type === "issue") return `Issue ${label}`;
|
|
70
|
-
return label;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function referenceLink(link, { compact = false, displayLabel, typePrefix = false } = {}) {
|
|
71
|
+
function referenceLink(link, { compact = false, displayLabel } = {}) {
|
|
74
72
|
const anchor = document.createElement("a");
|
|
75
73
|
anchor.className = compact ? "github-link github-link-compact" : "github-link";
|
|
76
74
|
anchor.href = link.url;
|
|
77
75
|
anchor.target = "_blank";
|
|
78
76
|
anchor.rel = "noopener noreferrer";
|
|
79
77
|
const label = link.label ?? link.text;
|
|
80
|
-
|
|
81
|
-
const accessibleLabel =
|
|
82
|
-
anchor.textContent = visibleLabel;
|
|
78
|
+
anchor.textContent = displayLabel ?? label;
|
|
79
|
+
const accessibleLabel = githubReferenceAccessibleLabel(link);
|
|
83
80
|
const githubKind = link.type === "issue"
|
|
84
81
|
? ", GitHub issue"
|
|
85
82
|
: link.type === "pull"
|
|
@@ -94,12 +91,20 @@ function referenceLink(link, { compact = false, displayLabel, typePrefix = false
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
function appendLinkedText(container, text, task) {
|
|
97
|
-
const
|
|
94
|
+
const segments = referenceSegments(text, {
|
|
98
95
|
projectRepositories: task.project?.githubRepos,
|
|
99
96
|
taskRepository: task.relatedGitHubRepository,
|
|
100
|
-
})
|
|
97
|
+
});
|
|
98
|
+
const links = segments.filter((segment) => segment.kind === "link");
|
|
99
|
+
const displayLabels = githubReferenceDisplayLabels(links);
|
|
100
|
+
let linkIndex = 0;
|
|
101
|
+
const children = segments.map((segment) => {
|
|
101
102
|
if (segment.kind === "text") return document.createTextNode(segment.text);
|
|
102
|
-
if (segment.kind === "link")
|
|
103
|
+
if (segment.kind === "link") {
|
|
104
|
+
const displayLabel = displayLabels[linkIndex];
|
|
105
|
+
linkIndex += 1;
|
|
106
|
+
return referenceLink(segment, { displayLabel });
|
|
107
|
+
}
|
|
103
108
|
const ambiguous = document.createElement("span");
|
|
104
109
|
ambiguous.className = "github-reference-ambiguous";
|
|
105
110
|
ambiguous.textContent = segment.text;
|
|
@@ -112,26 +117,25 @@ function appendLinkedText(container, text, task) {
|
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
function relatedGitHubLinks(task, { compact = false } = {}) {
|
|
115
|
-
const
|
|
116
|
-
const links =
|
|
117
|
-
if (link.type === "repository" || seen.has(link.label)) return false;
|
|
118
|
-
seen.add(link.label);
|
|
119
|
-
return true;
|
|
120
|
-
});
|
|
120
|
+
const groups = groupRelatedGitHubLinks(task.relatedGitHubLinks);
|
|
121
|
+
const links = groups.flat();
|
|
121
122
|
const container = document.createElement("nav");
|
|
122
123
|
container.className = `github-links${compact ? " github-links-compact" : ""}`;
|
|
123
124
|
container.setAttribute("aria-label", `Related GitHub links for ${task.title}`);
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
const displayLabels = githubReferenceDisplayLabels(links, { related: true });
|
|
126
|
+
let linkIndex = 0;
|
|
127
|
+
const children = groups.map((group) => {
|
|
128
|
+
const line = document.createElement("div");
|
|
129
|
+
line.className = "github-links-group";
|
|
130
|
+
line.replaceChildren(...group.map((link) => {
|
|
131
|
+
const anchor = referenceLink(link, {
|
|
132
|
+
compact,
|
|
133
|
+
displayLabel: displayLabels[linkIndex],
|
|
134
|
+
});
|
|
135
|
+
linkIndex += 1;
|
|
136
|
+
return anchor;
|
|
137
|
+
}));
|
|
138
|
+
return line;
|
|
135
139
|
});
|
|
136
140
|
if (task.relatedGitHubLinksTruncated) {
|
|
137
141
|
const more = document.createElement("span");
|
|
@@ -139,7 +143,9 @@ function relatedGitHubLinks(task, { compact = false } = {}) {
|
|
|
139
143
|
more.textContent = "+ more";
|
|
140
144
|
more.title = "Additional related GitHub references are omitted from this bounded dashboard view.";
|
|
141
145
|
more.setAttribute("aria-label", more.title);
|
|
142
|
-
children.
|
|
146
|
+
const finalGroup = children.at(-1);
|
|
147
|
+
if (finalGroup) finalGroup.append(more);
|
|
148
|
+
else children.push(more);
|
|
143
149
|
}
|
|
144
150
|
container.hidden = children.length === 0;
|
|
145
151
|
container.replaceChildren(...children);
|
|
@@ -160,7 +166,7 @@ function renderProject(container, task) {
|
|
|
160
166
|
repository: repositoryName,
|
|
161
167
|
type: "repository",
|
|
162
168
|
url: `https://github.com/${repository}`,
|
|
163
|
-
}),
|
|
169
|
+
}, { displayLabel: repositoryName }),
|
|
164
170
|
);
|
|
165
171
|
}
|
|
166
172
|
container.replaceChildren(...children);
|
|
@@ -3,6 +3,85 @@ const REFERENCE_PATTERN = /https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:[A-Z
|
|
|
3
3
|
const ABSOLUTE_HTTP_PATTERN = /https?:\/\/[^\s<>"'`]+/gi;
|
|
4
4
|
export const MAX_RELATED_GITHUB_LINKS = 20;
|
|
5
5
|
|
|
6
|
+
function repositoryIdentity(link) {
|
|
7
|
+
if (!link?.owner || !link?.repository) return null;
|
|
8
|
+
return `${link.owner.toLowerCase()}/${link.repository.toLowerCase()}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function collidingRepositoryBasenames(links) {
|
|
12
|
+
const repositoriesByBasename = new Map();
|
|
13
|
+
for (const link of links) {
|
|
14
|
+
const identity = repositoryIdentity(link);
|
|
15
|
+
if (!identity) continue;
|
|
16
|
+
const basename = link.repository.toLowerCase();
|
|
17
|
+
const repositories = repositoriesByBasename.get(basename) ?? new Set();
|
|
18
|
+
repositories.add(identity);
|
|
19
|
+
repositoriesByBasename.set(basename, repositories);
|
|
20
|
+
}
|
|
21
|
+
return new Set(
|
|
22
|
+
[...repositoriesByBasename]
|
|
23
|
+
.filter(([, repositories]) => repositories.size > 1)
|
|
24
|
+
.map(([basename]) => basename),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function visibleRepositoryLabel(link, collisions) {
|
|
29
|
+
return collisions.has(link.repository.toLowerCase())
|
|
30
|
+
? `${link.owner}/${link.repository}`
|
|
31
|
+
: link.repository;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function githubReferenceDisplayLabels(links, { related = false } = {}) {
|
|
35
|
+
const collisions = collidingRepositoryBasenames(links);
|
|
36
|
+
let previousRepository = null;
|
|
37
|
+
return links.map((link) => {
|
|
38
|
+
if (link.provider !== "github" && !link.owner) return link.label ?? link.text;
|
|
39
|
+
const repository = repositoryIdentity(link);
|
|
40
|
+
const repositoryLabel = visibleRepositoryLabel(link, collisions);
|
|
41
|
+
if (link.type === "repository") return repositoryLabel;
|
|
42
|
+
if (related) {
|
|
43
|
+
const label = repository === previousRepository
|
|
44
|
+
? `#${link.number}`
|
|
45
|
+
: `${repositoryLabel} #${link.number}`;
|
|
46
|
+
previousRepository = repository;
|
|
47
|
+
return label;
|
|
48
|
+
}
|
|
49
|
+
if (link.type === "pull") return `${repositoryLabel} PR #${link.number}`;
|
|
50
|
+
if (link.type === "issue") return `${repositoryLabel} Issue #${link.number}`;
|
|
51
|
+
return `${repositoryLabel} #${link.number}`;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function compareReferenceNumbers(left, right) {
|
|
56
|
+
const leftNumber = String(left.number ?? "");
|
|
57
|
+
const rightNumber = String(right.number ?? "");
|
|
58
|
+
return leftNumber.length - rightNumber.length || leftNumber.localeCompare(rightNumber);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function groupRelatedGitHubLinks(links) {
|
|
62
|
+
const groups = new Map();
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const link of links ?? []) {
|
|
65
|
+
if (link.type === "repository" || seen.has(link.label)) continue;
|
|
66
|
+
seen.add(link.label);
|
|
67
|
+
const repository = repositoryIdentity(link);
|
|
68
|
+
if (!repository) continue;
|
|
69
|
+
const group = groups.get(repository) ?? [];
|
|
70
|
+
group.push(link);
|
|
71
|
+
groups.set(repository, group);
|
|
72
|
+
}
|
|
73
|
+
return [...groups.values()].map((group) => [...group].sort(compareReferenceNumbers));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function githubReferenceAccessibleLabel(link) {
|
|
77
|
+
const repository = repositoryIdentity(link);
|
|
78
|
+
if (!repository) return link.label ?? link.text;
|
|
79
|
+
if (link.type === "repository") return repository;
|
|
80
|
+
if (link.type === "pull") return `${repository} PR #${link.number}`;
|
|
81
|
+
if (link.type === "issue") return `${repository} Issue #${link.number}`;
|
|
82
|
+
return `${repository} #${link.number}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
6
85
|
function canonicalRepository(owner, repository) {
|
|
7
86
|
return `${owner.toLowerCase()}/${repository.toLowerCase()}`;
|
|
8
87
|
}
|
package/src/dashboard/styles.css
CHANGED
|
@@ -97,9 +97,10 @@ select { min-width: 180px; min-height: 36px; padding: 7px 32px 7px 10px; border:
|
|
|
97
97
|
.task-title, .task-summary { overflow-wrap: anywhere; }
|
|
98
98
|
.task-summary { display: grid; max-width: 82ch; margin-bottom: 12px; gap: 2px; font-size: 0.9rem; line-height: 1.45; }
|
|
99
99
|
.task-summary strong { margin-top: 4px; color: var(--muted); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
100
|
-
.github-links { display:
|
|
100
|
+
.github-links { display: grid; gap: 7px; margin: 0 0 18px; }
|
|
101
101
|
.github-links[hidden] { display: none; }
|
|
102
102
|
.github-links-compact { margin: -2px 0 12px; gap: 5px; }
|
|
103
|
+
.github-links-group { display: flex; min-width: 0; flex-wrap: wrap; gap: inherit; }
|
|
103
104
|
.github-link { color: var(--accent); font-weight: 700; text-decoration-thickness: 1px; text-underline-offset: 3px; overflow-wrap: anywhere; }
|
|
104
105
|
.github-link-compact { padding: 2px 7px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface-muted); font-size: 0.72rem; line-height: 1.35; text-decoration: none; }
|
|
105
106
|
.github-link:hover { text-decoration-thickness: 2px; }
|