clay-server 4.8.0-beta.4 → 4.8.0-beta.5
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.
|
@@ -1,26 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
/* Linear workbench: mirrors #issues-panel / #terminal-container shell, header and controls. */
|
|
2
|
+
#linear-panel { position: relative; width: 50%; max-width: 720px; min-width: 360px; height: calc(100% - 16px); margin: 8px 8px 8px 10px; min-height: 0; align-self: center; display: flex; flex-direction: column; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; flex-shrink: 0; }
|
|
2
3
|
#linear-panel.hidden { display: none; }
|
|
3
|
-
|
|
4
|
-
.linear-
|
|
5
|
-
|
|
6
|
-
#linear-panel
|
|
7
|
-
#linear-panel button
|
|
8
|
-
#linear-panel button:
|
|
9
|
-
#linear-panel button:
|
|
10
|
-
|
|
11
|
-
.linear-
|
|
12
|
-
|
|
13
|
-
.linear-notice { margin: 0; padding: 0
|
|
14
|
-
.linear-notice:not(:empty) { padding
|
|
4
|
+
#linear-panel .linear-topbar { height: 48px; box-sizing: border-box; display: flex; align-items: center; gap: 12px; padding: 0 16px; border-bottom: 1px solid var(--border-subtle); flex: 0 0 auto; }
|
|
5
|
+
#linear-panel .linear-title { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; flex: 1; }
|
|
6
|
+
|
|
7
|
+
#linear-panel .linear-window-actions { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
|
|
8
|
+
#linear-panel button { font: inherit; color: var(--text-secondary); background: var(--bg-alt); border: 1px solid var(--border); border-radius: 6px; padding: 6px 10px; cursor: pointer; }
|
|
9
|
+
#linear-panel button:hover { background: var(--bg-hover, rgba(128,128,128,.12)); color: var(--text); }
|
|
10
|
+
#linear-panel button:disabled { opacity: .5; cursor: default; }
|
|
11
|
+
#linear-panel :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
12
|
+
#linear-panel .linear-icon-btn { width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; padding: 0; border-radius: 7px; }
|
|
13
|
+
#linear-panel .linear-icon-btn .lucide { display: block; width: 15px; height: 15px; }
|
|
14
|
+
.linear-notice { margin: 0; padding: 0 16px; color: var(--text-muted); font-size: 12px; }
|
|
15
|
+
.linear-notice:not(:empty) { padding: 8px 16px; }
|
|
15
16
|
.linear-content { overflow: auto; min-height: 0; flex: 1; }
|
|
16
|
-
.linear-empty { padding:
|
|
17
|
-
.linear-
|
|
17
|
+
.linear-empty { max-width: 420px; margin: 0 auto; padding: 48px 28px; text-align: center; color: var(--text-muted); font-size: 13px; line-height: 1.6; }
|
|
18
|
+
.linear-empty strong { display: block; margin-bottom: 6px; color: var(--text); font-size: 14px; }
|
|
19
|
+
.linear-empty p { margin: 0; }
|
|
20
|
+
.linear-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; font-size: 12px; }
|
|
21
|
+
.linear-actions button { font-size: 12px; line-height: normal; }
|
|
18
22
|
#linear-panel a { color: var(--link); }
|
|
19
23
|
#linear-panel .issue-row { width: 100%; text-align: left; }
|
|
20
|
-
#linear-panel .issue-row small, #linear-panel .issue-row strong { display: block; }
|
|
21
24
|
#linear-panel .issue-discussion article { padding: 14px 0; border-top: 1px solid var(--border-subtle); }
|
|
22
|
-
#linear-panel .issue-
|
|
23
|
-
#linear-panel .issue-doc-body
|
|
25
|
+
#linear-panel .issue-discussion article > strong { display: block; margin-bottom: 4px; font-size: 13px; }
|
|
26
|
+
#linear-panel .issue-doc-body, #linear-panel .issue-doc-body .md-content { overflow-wrap: anywhere; }
|
|
27
|
+
#linear-panel .issue-doc-body .md-content { font-size: 14px; line-height: 1.7; }
|
|
28
|
+
#linear-panel .issue-discussion .md-content { font-size: 13px; line-height: 1.6; }
|
|
29
|
+
#linear-panel .issue-detail .issue-doc-title { font-size: clamp(20px, 2vw, 26px); line-height: 1.2; }
|
|
30
|
+
#linear-panel .md-content > :first-child { margin-top: 0; }
|
|
31
|
+
#linear-panel .md-content > :last-child { margin-bottom: 0; }
|
|
32
|
+
#linear-panel .md-content h1, #linear-panel .md-content h2, #linear-panel .md-content h3 { margin: 1.3em 0 .5em; font-family: var(--font-display); }
|
|
33
|
+
#linear-panel .md-content :is(ul, ol) { padding-left: 1.4em; }
|
|
34
|
+
#linear-panel .md-content li > :is(ul, ol) { margin-top: 4px; }
|
|
35
|
+
#linear-panel .md-content li.task-list-item { list-style: none; margin-left: -1.4em; }
|
|
36
|
+
#linear-panel .md-content pre { overflow-x: auto; max-width: 100%; }
|
|
37
|
+
#linear-panel .md-content table { display: block; max-width: 100%; overflow-x: auto; }
|
|
38
|
+
#linear-panel .md-content img { max-width: 100%; }
|
|
39
|
+
@media (max-width: 1023px) { #linear-panel { position: fixed; inset: 0; z-index: 300; width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; padding-top: var(--safe-top, 0px); padding-bottom: var(--safe-bottom, 0px); box-sizing: border-box; } #linear-panel button { min-height: 36px; } #linear-panel .linear-icon-btn { width: 36px; } }
|
|
40
|
+
@media (max-width: 767px) { #linear-panel .issue-detail { padding: 20px 16px 40px; } }
|
|
24
41
|
.session-linear-link { max-width: 360px; min-width: 0; flex-shrink: 1; }
|
|
25
42
|
.session-linear-link > span { white-space: nowrap; }
|
|
26
43
|
.session-linear-link .linear-work-id, .session-linear-link .linear-work-state { flex-shrink: 0; }
|
|
@@ -32,4 +49,44 @@
|
|
|
32
49
|
#header-github-links:has(.session-linear-link) { max-width: 170px; }
|
|
33
50
|
#header-github-links .linear-work-title, #header-github-links .linear-work-state { display: none; }
|
|
34
51
|
}
|
|
35
|
-
|
|
52
|
+
#linear-panel .linear-session-pills { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
53
|
+
#linear-panel .linear-session-pill { display: inline-flex; align-items: flex-start; gap: 7px; max-width: 100%; box-sizing: border-box; padding: 6px 12px; border-radius: 999px; text-align: left; font-size: 13px; line-height: 1.4; color: var(--text); background: var(--bg-alt); border: 1px solid var(--border); }
|
|
54
|
+
#linear-panel .linear-session-pill .lucide { width: 14px; height: 14px; flex: none; margin-top: 2px; color: var(--accent); }
|
|
55
|
+
#linear-panel .linear-session-pill > span { min-width: 0; overflow-wrap: anywhere; }
|
|
56
|
+
#linear-panel .linear-session-pill:hover { background: color-mix(in srgb, var(--accent) 10%, var(--bg-alt)); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
|
|
57
|
+
#linear-panel .linear-session-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
58
|
+
.linear-work-brand { display: inline-flex; align-items: center; flex: none; color: inherit; }
|
|
59
|
+
.linear-work-brand svg { width: 13px; height: 13px; }
|
|
60
|
+
|
|
61
|
+
#linear-panel .linear-title .linear-work-brand svg { width: 16px; height: 16px; }
|
|
62
|
+
#linear-panel a.linear-icon-btn { color: var(--text-secondary); text-decoration: none; border: 1px solid var(--border); background: var(--bg-alt); box-sizing: border-box; }
|
|
63
|
+
#linear-panel a.linear-icon-btn:hover { background: var(--bg-hover, rgba(128,128,128,.12)); color: var(--text); }
|
|
64
|
+
#linear-panel [data-linear-external][hidden] { display: none; }
|
|
65
|
+
#linear-panel .linear-conversations { margin: 22px 0 26px; }
|
|
66
|
+
#linear-panel .linear-conversations h3 { font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 0 0 10px; }
|
|
67
|
+
#linear-panel .linear-action-chip { background: transparent; border-style: dashed; color: var(--text-secondary); }
|
|
68
|
+
#linear-panel .linear-sharing-hint { font-size: 11px; line-height: 1.5; color: var(--text-muted); margin: 10px 0 0; }
|
|
69
|
+
@media (max-width: 1023px) { #linear-panel a.linear-icon-btn { height: 36px; } }
|
|
70
|
+
|
|
71
|
+
/* Compact metadata sits with the title, without the document-style divider. */
|
|
72
|
+
#linear-panel .linear-issue-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; font-size: 12px; line-height: 1.4; }
|
|
73
|
+
#linear-panel .linear-issue-id { padding: 4px 7px; border-radius: 5px; background: var(--bg-alt); color: var(--text-secondary); font-family: var(--font-mono); font-weight: 500; letter-spacing: .015em; overflow-wrap: anywhere; }
|
|
74
|
+
#linear-panel .linear-issue-status { padding: 3px 8px; border: 1px solid var(--border); border-radius: 999px; color: var(--text-secondary); overflow-wrap: anywhere; }
|
|
75
|
+
|
|
76
|
+
#linear-panel .linear-title { min-width: 0; gap: 9px; height: 30px; font-size: 13px; line-height: 20px; font-weight: 600; }
|
|
77
|
+
#linear-panel .linear-window-actions { flex-shrink: 0; }
|
|
78
|
+
#linear-panel .linear-header-issue { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; height: 30px; box-sizing: border-box; padding: 0 5px; border-radius: 5px; color: var(--text-secondary); text-decoration: none; font: inherit; }
|
|
79
|
+
#linear-panel .linear-header-issue .lucide { display: block; width: 12px; height: 12px; color: inherit; }
|
|
80
|
+
#linear-panel .linear-header-issue:hover { color: var(--text); background: var(--bg-alt); }
|
|
81
|
+
#linear-panel .linear-window-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; line-height: 20px; font-weight: 600; }
|
|
82
|
+
|
|
83
|
+
#linear-panel .issue-discussion .linear-comment { margin: 12px 0; padding: 16px; border: 1px solid var(--border-subtle); border-radius: 10px; background: var(--bg-alt); }
|
|
84
|
+
#linear-panel .linear-comment > header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; font-size: 13px; }
|
|
85
|
+
#linear-panel .linear-comment > header strong { font-weight: 600; }
|
|
86
|
+
#linear-panel .linear-comment time { color: var(--text-muted); font-size: 11px; }
|
|
87
|
+
#linear-panel .linear-comment .issue-doc-body { margin: 0; }
|
|
88
|
+
@media (max-width: 767px) { #linear-panel .issue-discussion .linear-comment { padding: 12px; } }
|
|
89
|
+
|
|
90
|
+
#linear-panel .linear-title > .linear-work-brand { height: 20px; align-items: center; }
|
|
91
|
+
#linear-panel .linear-title > .linear-work-brand svg { display: block; }
|
|
92
|
+
#linear-panel [data-linear-window-id] { display: block; line-height: 20px; }
|
|
@@ -3,18 +3,32 @@ import { getWs } from './ws-ref.js';
|
|
|
3
3
|
import { escapeHtml } from './utils.js';
|
|
4
4
|
import { renderMarkdown } from './markdown.js';
|
|
5
5
|
import { refreshIcons } from './icons.js';
|
|
6
|
+
import { linearIconMarkup } from './session-github.js';
|
|
6
7
|
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
7
|
-
import { openUserSettings } from './user-settings.js';
|
|
8
8
|
|
|
9
9
|
function esc(value) { return escapeHtml(String(value || '')).replace(/"/g, '"').replace(/'/g, '''); }
|
|
10
10
|
function panel() { return document.getElementById('linear-panel'); }
|
|
11
11
|
function content() { return panel().querySelector('.linear-content'); }
|
|
12
12
|
function notice(text) { panel().querySelector('[role="status"]').textContent = text; }
|
|
13
|
+
function updateExternalLink(issue) {
|
|
14
|
+
var link = panel() && panel().querySelector('[data-linear-external]');
|
|
15
|
+
if (!link) return;
|
|
16
|
+
var heading = panel().querySelector('[data-linear-window-title]');
|
|
17
|
+
var identifier = panel().querySelector('[data-linear-window-id]');
|
|
18
|
+
heading.textContent = issue ? issue.title || 'Linear issue' : 'Linear';
|
|
19
|
+
heading.title = heading.textContent;
|
|
20
|
+
identifier.textContent = issue ? issue.identifier || '' : '';
|
|
21
|
+
var url = issue && issue.url;
|
|
22
|
+
var valid = /^https:\/\/linear\.app\/[a-zA-Z0-9_-]+\/issue\/[A-Z][A-Z0-9_]*-[1-9][0-9]*$/.test(url || '');
|
|
23
|
+
link.hidden = !valid;
|
|
24
|
+
if (valid) link.setAttribute('href', url); else link.removeAttribute('href');
|
|
25
|
+
}
|
|
13
26
|
function request(action, args) {
|
|
14
27
|
var ws = getWs();
|
|
15
28
|
if (!ws || ws.readyState !== 1) { notice('Reconnect to use Linear.'); return; }
|
|
16
|
-
if (action === '
|
|
29
|
+
if (action === 'linear_read' && !args.cursor) {
|
|
17
30
|
store.set({ linearIssue: null, linearMatches: [] }); content().textContent = '';
|
|
31
|
+
updateExternalLink(null);
|
|
18
32
|
}
|
|
19
33
|
var id = crypto.randomUUID();
|
|
20
34
|
store.set({ linearRequest: { id: id, action: action, args: args, project: store.get('currentSlug'), socket: ws } });
|
|
@@ -22,6 +36,12 @@ function request(action, args) {
|
|
|
22
36
|
ws.send(JSON.stringify({ type: action, args: args, requestId: id }));
|
|
23
37
|
return id;
|
|
24
38
|
}
|
|
39
|
+
var SAFE_CLASS = /^(hljs[a-z0-9-]*|language-[a-z0-9+#-]+|task-list-item|contains-task-list)$/;
|
|
40
|
+
function keepSafeClasses(node) {
|
|
41
|
+
if (!node.getAttribute || !node.hasAttribute('class')) return;
|
|
42
|
+
var kept = node.getAttribute('class').split(/\s+/).filter(function (name) { return SAFE_CLASS.test(name); });
|
|
43
|
+
if (kept.length) node.setAttribute('class', kept.join(' ')); else node.removeAttribute('class');
|
|
44
|
+
}
|
|
25
45
|
function prose(text) {
|
|
26
46
|
var element = document.createElement('template');
|
|
27
47
|
element.innerHTML = renderMarkdown(text || '');
|
|
@@ -32,32 +52,53 @@ function prose(text) {
|
|
|
32
52
|
link.href = (store.get('linearIssue') || {}).url || 'https://linear.app'; link.target = '_blank'; link.rel = 'noopener noreferrer';
|
|
33
53
|
image.replaceWith(link);
|
|
34
54
|
});
|
|
35
|
-
|
|
55
|
+
// Task-list checkboxes are inputs (forbidden below); keep their state as text.
|
|
56
|
+
element.content.querySelectorAll('input[type="checkbox"]').forEach(function (box) { box.replaceWith(document.createTextNode(box.checked ? '\u2611 ' : '\u2610 ')); });
|
|
57
|
+
// Typography needs only code-highlight and task-list classes; ids and inline styles stay stripped.
|
|
58
|
+
DOMPurify.addHook('afterSanitizeAttributes', keepSafeClasses);
|
|
59
|
+
try {
|
|
60
|
+
return '<div class="md-content">' + DOMPurify.sanitize(element.innerHTML, { ALLOW_DATA_ATTR: false, FORBID_TAGS: ['form', 'button', 'input', 'select', 'textarea', 'iframe', 'video', 'audio', 'style'], FORBID_ATTR: ['id', 'style'] }) + '</div>';
|
|
61
|
+
} finally { DOMPurify.removeHook('afterSanitizeAttributes'); }
|
|
62
|
+
}
|
|
63
|
+
function chronologicalComments(comments) {
|
|
64
|
+
return (comments || []).slice().sort(function (a, b) {
|
|
65
|
+
var first = Date.parse(a.createdAt), second = Date.parse(b.createdAt);
|
|
66
|
+
if (!Number.isFinite(first) || !Number.isFinite(second)) return 0;
|
|
67
|
+
return first - second;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function commentMarkup(comment) {
|
|
71
|
+
var date = new Date(comment.createdAt);
|
|
72
|
+
var timestamp = Number.isFinite(date.getTime()) ? '<time datetime="' + esc(date.toISOString()) + '" title="' + esc(date.toLocaleString()) + '">' + esc(date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })) + '</time>' : '';
|
|
73
|
+
return '<article class="linear-comment"><header><strong>' + esc(comment.author) + '</strong>' + timestamp + '</header><div class="issue-doc-body">' + prose(comment.body) + '</div></article>';
|
|
36
74
|
}
|
|
37
75
|
function renderIssue() {
|
|
38
76
|
var issue = store.get('linearIssue'), sessions = store.get('linearMatches') || [];
|
|
39
77
|
if (!issue) return;
|
|
78
|
+
updateExternalLink(issue);
|
|
40
79
|
var active = (store.get('githubWorkSessions') || []).find(function (item) { return item.id === store.get('activeSessionId'); });
|
|
41
80
|
var linked = !!(active && (active.linearLinks || []).some(function (item) { return item.url === issue.url; }));
|
|
42
81
|
var prs = active && linked ? (active.githubLinks || []).filter(function (item) { return item.kind === 'pr' && /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/[1-9][0-9]*$/.test(item.url); }) : [];
|
|
43
|
-
content().innerHTML = '<article class="issue-detail"><div class="issue-
|
|
44
|
-
'<
|
|
45
|
-
|
|
46
|
-
'<
|
|
47
|
-
'<
|
|
48
|
-
|
|
82
|
+
content().innerHTML = '<article class="issue-detail"><div class="linear-issue-meta"><span class="linear-issue-id" aria-label="Issue identifier">' + esc(issue.identifier) + '</span>' + (issue.stateName ? '<span class="linear-issue-status" aria-label="Status">' + esc(issue.stateName) + '</span>' : '') + '</div><h2 class="issue-doc-title">' + esc(issue.title) + '</h2>' +
|
|
83
|
+
'<section class="linear-conversations"><h3>Linked conversations</h3><div class="linear-session-pills">' +
|
|
84
|
+
sessions.map(function (item) { return '<button type="button" class="linear-session-pill" data-linear-session="' + esc(item.sessionId) + '"><i data-lucide="message-square"></i><span>' + esc(item.title || 'Open conversation') + '</span></button>'; }).join('') +
|
|
85
|
+
'<button type="button" class="linear-session-pill linear-action-chip" data-linear-link' + (!store.get('activeSessionId') ? ' disabled' : '') + '><i data-lucide="' + (linked ? 'unlink' : 'plus') + '"></i><span>' + (linked ? 'Unlink this conversation' : 'Link this conversation') + '</span></button>' +
|
|
86
|
+
(!sessions.length ? '<button type="button" class="linear-session-pill linear-action-chip" data-linear-start><i data-lucide="plus"></i><span>Start work</span></button>' : '') +
|
|
87
|
+
'</div><p class="linear-sharing-hint">Linked issue titles and status are visible to conversation viewers.</p></section>' +
|
|
49
88
|
(prs.length ? '<section><h3>GitHub pull requests</h3>' + prs.map(function (pr) { return '<a target="_blank" rel="noopener noreferrer" href="' + esc(pr.url) + '">PR #' + esc(pr.number) + ' · ' + esc(pr.title) + '</a>'; }).join('<br>') + '</section>' : '') +
|
|
50
89
|
'<div class="issue-doc-body">' + prose(issue.description) + '</div><section class="issue-discussion"><h3>Comments</h3>' +
|
|
51
|
-
(issue.comments.length ? issue.comments.map(
|
|
90
|
+
(issue.comments.length ? chronologicalComments(issue.comments).map(commentMarkup).join('') : '<p>No comments yet.</p>') +
|
|
52
91
|
(issue.pageInfo && issue.pageInfo.hasNextPage ? '<button type="button" data-linear-more>More comments</button>' : '') + '</section></article>';
|
|
92
|
+
refreshIcons(content());
|
|
53
93
|
}
|
|
54
|
-
function
|
|
94
|
+
function showEmpty() {
|
|
95
|
+
updateExternalLink(null);
|
|
55
96
|
store.set({ linearIssue: null, linearRequest: null, linearMatches: [] });
|
|
56
|
-
content().innerHTML = '<
|
|
97
|
+
content().innerHTML = '<div class="linear-empty"><strong>No issue selected</strong><p>Open a linked Linear issue from a conversation badge, or ask the Driver to find and link one. Manage your connection in Settings \u2192 Integrations.</p></div>';
|
|
57
98
|
notice('');
|
|
58
|
-
panel().querySelector('input').focus();
|
|
59
99
|
}
|
|
60
100
|
export function closeLinear() {
|
|
101
|
+
updateExternalLink(null);
|
|
61
102
|
if (panel()) panel().classList.add('hidden');
|
|
62
103
|
store.set({ linearOpen: false, linearRequest: null, linearIssue: null, linearMatches: [] });
|
|
63
104
|
releaseRightWorkbench('linear');
|
|
@@ -67,8 +108,9 @@ export function openLinear(url) {
|
|
|
67
108
|
if (!panel() || store.get('isMate')) return;
|
|
68
109
|
claimRightWorkbench('linear');
|
|
69
110
|
panel().classList.remove('hidden'); store.set({ linearOpen: true });
|
|
111
|
+
updateExternalLink(null);
|
|
70
112
|
if (url) { content().textContent = ''; request('linear_read', { url: url }); }
|
|
71
|
-
else
|
|
113
|
+
else showEmpty();
|
|
72
114
|
}
|
|
73
115
|
export function handleLinearResult(msg) {
|
|
74
116
|
var pending = store.get('linearRequest');
|
|
@@ -78,12 +120,7 @@ export function handleLinearResult(msg) {
|
|
|
78
120
|
if (msg.error) { notice(msg.error); return; }
|
|
79
121
|
notice('');
|
|
80
122
|
var result = msg.result || {};
|
|
81
|
-
if (msg.action === '
|
|
82
|
-
store.set({ linearIssue: null });
|
|
83
|
-
content().innerHTML = (result.items || []).map(function (item) {
|
|
84
|
-
return '<button type="button" class="issue-row" data-linear-url="' + esc(item.url) + '"><small>' + esc(item.identifier) + ' · ' + esc(item.stateName) + '</small><strong>' + esc(item.title) + '</strong></button>';
|
|
85
|
-
}).join('') || '<p class="linear-empty">No matching issues.</p>';
|
|
86
|
-
} else if (msg.action === 'linear_read') {
|
|
123
|
+
if (msg.action === 'linear_read') {
|
|
87
124
|
var issue = result.issue, old = store.get('linearIssue');
|
|
88
125
|
if (pending.args.cursor && old && old.url === issue.url) issue.comments = old.comments.concat(issue.comments).filter(function (item, index, all) { return all.findIndex(function (other) { return item.id === other.id; }) === index; });
|
|
89
126
|
store.set({ linearIssue: issue, linearMatches: result.matchingSessions }); renderIssue();
|
|
@@ -94,16 +131,12 @@ export function initLinearPanel() {
|
|
|
94
131
|
var host = document.getElementById('main-panels');
|
|
95
132
|
if (!host || panel()) return;
|
|
96
133
|
var el = document.createElement('section'); el.id = 'linear-panel'; el.className = 'hidden'; el.setAttribute('aria-label', 'Linear issues');
|
|
97
|
-
el.innerHTML = '<header class="linear-topbar"><
|
|
134
|
+
el.innerHTML = '<header class="linear-topbar"><div class="linear-title">' + linearIconMarkup() + '<a class="linear-header-issue" data-linear-external hidden target="_blank" rel="noopener noreferrer" aria-label="Open issue in Linear" title="Open in Linear"><span data-linear-window-id></span><i data-lucide="external-link"></i></a><span class="linear-window-title" data-linear-window-title>Linear</span></div><div class="linear-window-actions"><button type="button" class="linear-icon-btn" data-linear-close aria-label="Close Linear" title="Close Linear"><i data-lucide="x"></i></button></div></header><p class="linear-notice" role="status"></p><div class="linear-content"></div>';
|
|
98
135
|
host.appendChild(el); registerRightWorkbench('linear', closeLinear); refreshIcons(el);
|
|
99
|
-
el.querySelector('form').addEventListener('submit', function (event) { event.preventDefault(); request('linear_search', { query: el.querySelector('input').value }); });
|
|
100
136
|
el.addEventListener('click', function (event) {
|
|
101
137
|
var button = event.target.closest('button'); if (!button) return;
|
|
102
138
|
var issue = store.get('linearIssue');
|
|
103
139
|
if (button.hasAttribute('data-linear-close')) closeLinear();
|
|
104
|
-
else if (button.hasAttribute('data-linear-back')) showSearch();
|
|
105
|
-
else if (button.hasAttribute('data-linear-settings')) openUserSettings('us-integrations');
|
|
106
|
-
else if (button.dataset.linearUrl) { content().textContent = ''; request('linear_read', { url: button.dataset.linearUrl }); }
|
|
107
140
|
else if (issue && button.hasAttribute('data-linear-link')) {
|
|
108
141
|
var active = (store.get('githubWorkSessions') || []).find(function (item) { return item.id === store.get('activeSessionId'); });
|
|
109
142
|
var linked = active && (active.linearLinks || []).some(function (item) { return item.url === issue.url; });
|
|
@@ -115,6 +148,6 @@ export function initLinearPanel() {
|
|
|
115
148
|
document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && store.get('linearOpen')) closeLinear(); });
|
|
116
149
|
store.subscribe(function (state, previous) {
|
|
117
150
|
if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.dmMode !== previous.dmMode || (previous.connected && !state.connected)) closeLinear();
|
|
118
|
-
else if (state.linearOpen && state.githubWorkSessions !== previous.githubWorkSessions && state.linearIssue) renderIssue();
|
|
151
|
+
else if (state.linearOpen && (state.githubWorkSessions !== previous.githubWorkSessions || state.activeSessionId !== previous.activeSessionId) && state.linearIssue) renderIssue();
|
|
119
152
|
});
|
|
120
153
|
}
|
|
@@ -8,6 +8,9 @@ function escapeHtml(value) {
|
|
|
8
8
|
return escapeText(String(value)).replace(/"/g, '"').replace(/'/g, ''');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// Local Linear mark (no external asset): a disc cut by three diagonal bands.
|
|
12
|
+
var LINEAR_MARK = '<span class="linear-work-brand" aria-hidden="true"><svg viewBox="0 0 16 16" width="13" height="13" fill="currentColor" focusable="false"><path d="M1.2 9.7a6.9 6.9 0 0 0 5.1 5.1zM1 7.2l7.8 7.8a7 7 0 0 0 1.6-.4L1.4 5.6A7 7 0 0 0 1 7.2zm1-3 9.8 9.8a7 7 0 0 0 1.2-.9L2.9 3a7 7 0 0 0-.9 1.2zM4 2.2l9.8 9.8A7 7 0 0 0 8 1a7 7 0 0 0-4 1.2z"/></svg></span>';
|
|
13
|
+
export function linearIconMarkup() { return LINEAR_MARK; }
|
|
11
14
|
function safeLinks(session) {
|
|
12
15
|
return (session && Array.isArray(session.githubLinks) ? session.githubLinks : []).concat(session && Array.isArray(session.linearLinks) ? session.linearLinks : []).filter(function (link) {
|
|
13
16
|
if (link.provider === 'linear') return /^https:\/\/linear\.app\/[a-zA-Z0-9_-]+\/issue\/[A-Z][A-Z0-9_]*-[1-9][0-9]*$/.test(link.url);
|
|
@@ -23,13 +26,13 @@ function anchor(url, title, content, className) {
|
|
|
23
26
|
}
|
|
24
27
|
export function orderedGithubWork(session) {
|
|
25
28
|
var links = safeLinks(session).slice().reverse();
|
|
26
|
-
var latestIssue = links.find(function (link) { return link.kind === 'issue'; });
|
|
29
|
+
var latestIssue = links.find(function (link) { return link.kind === 'issue' || link.provider === 'linear'; });
|
|
27
30
|
if (latestIssue) links = [latestIssue].concat(links.filter(function (link) { return link !== latestIssue; }));
|
|
28
31
|
return links;
|
|
29
32
|
}
|
|
30
33
|
function compactGithubWork(links, sessionRow) {
|
|
31
34
|
if (!sessionRow && !links.some(function (link) { return link.provider === 'linear'; })) return links.slice(0, 1);
|
|
32
|
-
var latestIssue = links.find(function (link) { return link.kind === 'issue'; });
|
|
35
|
+
var latestIssue = links.find(function (link) { return link.kind === 'issue' || link.provider === 'linear'; });
|
|
33
36
|
var latestPr = links.find(function (link) { return link.kind === 'pr'; });
|
|
34
37
|
return [latestIssue, latestPr].filter(Boolean);
|
|
35
38
|
}
|
|
@@ -40,7 +43,7 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
40
43
|
return '<span class="session-github-links' + (detailed ? ' github-work-details' : '') + (sessionRow ? ' session-row-github-links' : '') + '">' + displayed.map(function (link) {
|
|
41
44
|
if (link.provider === 'linear') {
|
|
42
45
|
var description = link.identifier + ' · ' + link.title + ' · ' + link.stateName + (link.stale ? ' · Last known status' : '');
|
|
43
|
-
return anchor(link.url, description,
|
|
46
|
+
return anchor(link.url, description, LINEAR_MARK + '<span class="linear-work-id">' + escapeHtml(link.identifier) + '</span><span class="linear-work-title">' + escapeHtml(link.title) + '</span><span class="linear-work-state">' + escapeHtml(link.stateName) + '</span>', 'session-github-link session-linear-link');
|
|
44
47
|
}
|
|
45
48
|
var state = ['open', 'closed', 'merged', 'draft'].indexOf(link.state) >= 0 ? link.state : 'unknown';
|
|
46
49
|
var icon = link.kind === 'pr' ? (state === 'merged' ? 'git-merge' : state === 'closed' ? 'git-pull-request-closed' : state === 'draft' ? 'git-pull-request-draft' : 'git-pull-request') : (state === 'closed' ? 'circle-check' : 'circle-dot');
|
package/package.json
CHANGED