@autocode-cli/autocode 0.1.32 → 0.1.34
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/README.md +3 -3
- package/dist/cli/commands/init.js +1 -1
- package/dist/server/api.js +2 -2
- package/dist/server/api.js.map +1 -1
- package/dist/server/dashboard/pages/changelog.js +2 -2
- package/dist/server/dashboard/pages/changelog.js.map +1 -1
- package/dist/server/dashboard/pages/index.d.ts +3 -0
- package/dist/server/dashboard/pages/index.d.ts.map +1 -1
- package/dist/server/dashboard/pages/index.js +3 -0
- package/dist/server/dashboard/pages/index.js.map +1 -1
- package/dist/server/dashboard/pages/issue-comments.d.ts +8 -0
- package/dist/server/dashboard/pages/issue-comments.d.ts.map +1 -0
- package/dist/server/dashboard/pages/issue-comments.js +283 -0
- package/dist/server/dashboard/pages/issue-comments.js.map +1 -0
- package/dist/server/dashboard/pages/issue-history.d.ts +8 -0
- package/dist/server/dashboard/pages/issue-history.d.ts.map +1 -0
- package/dist/server/dashboard/pages/issue-history.js +149 -0
- package/dist/server/dashboard/pages/issue-history.js.map +1 -0
- package/dist/server/dashboard/pages/issue-shared.d.ts +27 -0
- package/dist/server/dashboard/pages/issue-shared.d.ts.map +1 -0
- package/dist/server/dashboard/pages/issue-shared.js +365 -0
- package/dist/server/dashboard/pages/issue-shared.js.map +1 -0
- package/dist/server/dashboard/pages/issue-terminal.d.ts +8 -0
- package/dist/server/dashboard/pages/issue-terminal.d.ts.map +1 -0
- package/dist/server/dashboard/pages/issue-terminal.js +363 -0
- package/dist/server/dashboard/pages/issue-terminal.js.map +1 -0
- package/dist/server/dashboard/pages/issue-view.d.ts +2 -2
- package/dist/server/dashboard/pages/issue-view.d.ts.map +1 -1
- package/dist/server/dashboard/pages/issue-view.js +49 -1027
- package/dist/server/dashboard/pages/issue-view.js.map +1 -1
- package/dist/server/dashboard/pages/main-dashboard.js +1 -1
- package/dist/server/dashboard/pages/main-dashboard.js.map +1 -1
- package/dist/server/dashboard/pages/new-issue.d.ts.map +1 -1
- package/dist/server/dashboard/pages/new-issue.js +66 -2
- package/dist/server/dashboard/pages/new-issue.js.map +1 -1
- package/dist/server/dashboard/pages/new-issue.test.js +208 -3
- package/dist/server/dashboard/pages/new-issue.test.js.map +1 -1
- package/dist/server/dashboard.d.ts +1 -1
- package/dist/server/dashboard.d.ts.map +1 -1
- package/dist/server/dashboard.js +1 -1
- package/dist/server/dashboard.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +35 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/watcher.d.ts +1 -1
- package/dist/server/watcher.js +1 -1
- package/dist/services/issue-io.d.ts +1 -1
- package/dist/services/issue-io.js +1 -1
- package/dist/utils/config.js +2 -2
- package/dist/utils/config.js.map +1 -1
- package/package.json +4 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Issue view page generator
|
|
2
|
+
* Issue view page generator (Details tab)
|
|
3
3
|
*/
|
|
4
4
|
import { getConfig } from '../../../utils/config.js';
|
|
5
5
|
import { getIssue } from '../../../core/issue.js';
|
|
6
6
|
import { getColumns } from '../../../core/column.js';
|
|
7
7
|
import { escapeHtml } from '../utils.js';
|
|
8
8
|
import { generate404Page } from './shared.js';
|
|
9
|
+
import { generateIssueBaseStyles, generateTabsNav, generateIssueBaseScript, } from './issue-shared.js';
|
|
9
10
|
/**
|
|
10
|
-
* Generate the issue view page
|
|
11
|
+
* Generate the issue view page (Details tab)
|
|
11
12
|
*/
|
|
12
13
|
export function generateIssueViewPage(issueKey, lang) {
|
|
13
14
|
const config = getConfig();
|
|
@@ -17,8 +18,13 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
17
18
|
return generate404Page(issueKey, lang);
|
|
18
19
|
}
|
|
19
20
|
const currentColumn = columns.find(c => c.slug === issue.column_slug);
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
const pageData = {
|
|
22
|
+
issueKey,
|
|
23
|
+
title: issue.title,
|
|
24
|
+
commentsCount: issue.comments?.length || 0,
|
|
25
|
+
historyCount: issue.history?.length || 0,
|
|
26
|
+
lang,
|
|
27
|
+
};
|
|
22
28
|
return `<!DOCTYPE html>
|
|
23
29
|
<html lang="${lang}">
|
|
24
30
|
<head>
|
|
@@ -26,83 +32,8 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
26
32
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
33
|
<title>${escapeHtml(issue.title)} - ${issueKey} - AutoCode</title>
|
|
28
34
|
<style>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
--bg-secondary: #12121a;
|
|
32
|
-
--bg-tertiary: #1a1a24;
|
|
33
|
-
--text: #f1f5f9;
|
|
34
|
-
--muted: #94a3b8;
|
|
35
|
-
--border: #2a2a3a;
|
|
36
|
-
--accent: #6366f1;
|
|
37
|
-
--blue: #4dabf7;
|
|
38
|
-
--green: #4ade80;
|
|
39
|
-
--yellow: #facc15;
|
|
40
|
-
--orange: #fb923c;
|
|
41
|
-
--red: #f87171;
|
|
42
|
-
--purple: #a78bfa;
|
|
43
|
-
}
|
|
44
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
45
|
-
body {
|
|
46
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
47
|
-
background: var(--bg);
|
|
48
|
-
color: var(--text);
|
|
49
|
-
min-height: 100vh;
|
|
50
|
-
}
|
|
51
|
-
.header {
|
|
52
|
-
display: flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
gap: 24px;
|
|
55
|
-
padding: 16px 24px;
|
|
56
|
-
background: var(--bg-secondary);
|
|
57
|
-
border-bottom: 1px solid var(--border);
|
|
58
|
-
position: sticky;
|
|
59
|
-
top: 0;
|
|
60
|
-
z-index: 100;
|
|
61
|
-
}
|
|
62
|
-
.back-btn {
|
|
63
|
-
color: var(--muted);
|
|
64
|
-
text-decoration: none;
|
|
65
|
-
font-size: 14px;
|
|
66
|
-
display: flex;
|
|
67
|
-
align-items: center;
|
|
68
|
-
gap: 8px;
|
|
69
|
-
}
|
|
70
|
-
.back-btn:hover { color: var(--text); }
|
|
71
|
-
.issue-header-info {
|
|
72
|
-
flex: 1;
|
|
73
|
-
display: flex;
|
|
74
|
-
align-items: center;
|
|
75
|
-
gap: 16px;
|
|
76
|
-
}
|
|
77
|
-
.issue-key {
|
|
78
|
-
font-family: 'SF Mono', Monaco, monospace;
|
|
79
|
-
font-size: 12px;
|
|
80
|
-
color: var(--accent);
|
|
81
|
-
background: rgba(99, 102, 241, 0.15);
|
|
82
|
-
padding: 4px 10px;
|
|
83
|
-
border-radius: 4px;
|
|
84
|
-
font-weight: 600;
|
|
85
|
-
}
|
|
86
|
-
.issue-title {
|
|
87
|
-
font-size: 18px;
|
|
88
|
-
font-weight: 600;
|
|
89
|
-
}
|
|
90
|
-
.lang-selector {
|
|
91
|
-
display: flex;
|
|
92
|
-
gap: 4px;
|
|
93
|
-
}
|
|
94
|
-
.lang-btn {
|
|
95
|
-
background: transparent;
|
|
96
|
-
border: 1px solid var(--border);
|
|
97
|
-
color: var(--muted);
|
|
98
|
-
padding: 6px 12px;
|
|
99
|
-
border-radius: 4px;
|
|
100
|
-
cursor: pointer;
|
|
101
|
-
font-size: 12px;
|
|
102
|
-
font-weight: 500;
|
|
103
|
-
}
|
|
104
|
-
.lang-btn:hover { border-color: var(--accent); color: var(--text); }
|
|
105
|
-
.lang-btn.active { background: var(--accent); border-color: var(--accent); color: white; }
|
|
35
|
+
${generateIssueBaseStyles()}
|
|
36
|
+
|
|
106
37
|
.issue-title-input {
|
|
107
38
|
flex: 1;
|
|
108
39
|
background: transparent;
|
|
@@ -120,10 +51,38 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
120
51
|
border-color: var(--accent);
|
|
121
52
|
background: var(--bg-tertiary);
|
|
122
53
|
}
|
|
123
|
-
.
|
|
54
|
+
.issue-details { display: flex; flex-direction: column; gap: 24px; }
|
|
55
|
+
.issue-meta {
|
|
124
56
|
display: flex;
|
|
125
|
-
|
|
126
|
-
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
gap: 12px;
|
|
59
|
+
}
|
|
60
|
+
.meta-badge {
|
|
61
|
+
font-size: 11px;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
letter-spacing: 0.5px;
|
|
65
|
+
padding: 5px 12px;
|
|
66
|
+
border-radius: 6px;
|
|
67
|
+
}
|
|
68
|
+
.priority-P0 { background: rgba(248, 113, 113, 0.2); color: var(--red); }
|
|
69
|
+
.priority-P1 { background: rgba(251, 146, 60, 0.2); color: var(--orange); }
|
|
70
|
+
.priority-P2 { background: rgba(250, 204, 21, 0.2); color: var(--yellow); }
|
|
71
|
+
.priority-P3 { background: rgba(148, 163, 184, 0.2); color: var(--muted); }
|
|
72
|
+
.column-badge { background: rgba(77, 171, 247, 0.15); color: var(--blue); }
|
|
73
|
+
.semver-badge { background: rgba(167, 139, 250, 0.15); color: var(--purple); }
|
|
74
|
+
.labels-list {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-wrap: wrap;
|
|
77
|
+
gap: 8px;
|
|
78
|
+
}
|
|
79
|
+
.label-tag {
|
|
80
|
+
font-size: 11px;
|
|
81
|
+
padding: 4px 10px;
|
|
82
|
+
border-radius: 12px;
|
|
83
|
+
background: var(--bg-tertiary);
|
|
84
|
+
color: var(--text);
|
|
85
|
+
border: 1px solid var(--border);
|
|
127
86
|
}
|
|
128
87
|
.btn-edit-toggle {
|
|
129
88
|
background: transparent;
|
|
@@ -179,76 +138,6 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
179
138
|
outline: none;
|
|
180
139
|
border-color: var(--accent);
|
|
181
140
|
}
|
|
182
|
-
.main-content {
|
|
183
|
-
display: flex;
|
|
184
|
-
flex-direction: column;
|
|
185
|
-
gap: 24px;
|
|
186
|
-
padding: 24px 48px;
|
|
187
|
-
}
|
|
188
|
-
.issue-details { display: flex; flex-direction: column; gap: 24px; }
|
|
189
|
-
.issue-bottom {
|
|
190
|
-
display: flex;
|
|
191
|
-
flex-direction: column;
|
|
192
|
-
gap: 24px;
|
|
193
|
-
}
|
|
194
|
-
.section {
|
|
195
|
-
background: var(--bg-secondary);
|
|
196
|
-
border: 1px solid var(--border);
|
|
197
|
-
border-radius: 12px;
|
|
198
|
-
padding: 20px;
|
|
199
|
-
}
|
|
200
|
-
.section-title {
|
|
201
|
-
font-size: 12px;
|
|
202
|
-
font-weight: 600;
|
|
203
|
-
text-transform: uppercase;
|
|
204
|
-
letter-spacing: 0.5px;
|
|
205
|
-
color: var(--muted);
|
|
206
|
-
margin-bottom: 16px;
|
|
207
|
-
}
|
|
208
|
-
.issue-meta {
|
|
209
|
-
display: flex;
|
|
210
|
-
flex-wrap: wrap;
|
|
211
|
-
gap: 12px;
|
|
212
|
-
}
|
|
213
|
-
.meta-badge {
|
|
214
|
-
font-size: 11px;
|
|
215
|
-
font-weight: 600;
|
|
216
|
-
text-transform: uppercase;
|
|
217
|
-
letter-spacing: 0.5px;
|
|
218
|
-
padding: 5px 12px;
|
|
219
|
-
border-radius: 6px;
|
|
220
|
-
}
|
|
221
|
-
.priority-P0 { background: rgba(248, 113, 113, 0.2); color: var(--red); }
|
|
222
|
-
.priority-P1 { background: rgba(251, 146, 60, 0.2); color: var(--orange); }
|
|
223
|
-
.priority-P2 { background: rgba(250, 204, 21, 0.2); color: var(--yellow); }
|
|
224
|
-
.priority-P3 { background: rgba(148, 163, 184, 0.2); color: var(--muted); }
|
|
225
|
-
.column-badge { background: rgba(77, 171, 247, 0.15); color: var(--blue); }
|
|
226
|
-
.semver-badge { background: rgba(167, 139, 250, 0.15); color: var(--purple); }
|
|
227
|
-
.labels-list {
|
|
228
|
-
display: flex;
|
|
229
|
-
flex-wrap: wrap;
|
|
230
|
-
gap: 8px;
|
|
231
|
-
}
|
|
232
|
-
.label-tag {
|
|
233
|
-
font-size: 11px;
|
|
234
|
-
padding: 4px 10px;
|
|
235
|
-
border-radius: 12px;
|
|
236
|
-
background: var(--bg-tertiary);
|
|
237
|
-
color: var(--text);
|
|
238
|
-
border: 1px solid var(--border);
|
|
239
|
-
}
|
|
240
|
-
.description-content {
|
|
241
|
-
line-height: 1.7;
|
|
242
|
-
color: var(--text);
|
|
243
|
-
}
|
|
244
|
-
.description-content p { margin-bottom: 12px; }
|
|
245
|
-
.description-content code {
|
|
246
|
-
background: var(--bg-tertiary);
|
|
247
|
-
padding: 2px 6px;
|
|
248
|
-
border-radius: 4px;
|
|
249
|
-
font-family: 'SF Mono', Monaco, monospace;
|
|
250
|
-
font-size: 13px;
|
|
251
|
-
}
|
|
252
141
|
.criteria-list { list-style: none; }
|
|
253
142
|
.criteria-item {
|
|
254
143
|
padding: 12px 16px;
|
|
@@ -263,99 +152,6 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
263
152
|
content: '☐';
|
|
264
153
|
color: var(--muted);
|
|
265
154
|
}
|
|
266
|
-
.history-list { list-style: none; }
|
|
267
|
-
.history-item {
|
|
268
|
-
padding: 12px 0;
|
|
269
|
-
border-bottom: 1px solid var(--border);
|
|
270
|
-
display: flex;
|
|
271
|
-
align-items: center;
|
|
272
|
-
gap: 12px;
|
|
273
|
-
font-size: 13px;
|
|
274
|
-
}
|
|
275
|
-
.history-item:last-child { border-bottom: none; }
|
|
276
|
-
.history-action {
|
|
277
|
-
font-weight: 600;
|
|
278
|
-
text-transform: capitalize;
|
|
279
|
-
}
|
|
280
|
-
.history-from, .history-to {
|
|
281
|
-
padding: 2px 8px;
|
|
282
|
-
background: var(--bg-tertiary);
|
|
283
|
-
border-radius: 4px;
|
|
284
|
-
font-size: 11px;
|
|
285
|
-
}
|
|
286
|
-
.history-date { color: var(--muted); margin-left: auto; font-size: 12px; }
|
|
287
|
-
.btn-prompt {
|
|
288
|
-
background: none;
|
|
289
|
-
border: none;
|
|
290
|
-
cursor: pointer;
|
|
291
|
-
font-size: 14px;
|
|
292
|
-
padding: 2px 6px;
|
|
293
|
-
opacity: 0.6;
|
|
294
|
-
transition: opacity 0.2s;
|
|
295
|
-
}
|
|
296
|
-
.btn-prompt:hover { opacity: 1; }
|
|
297
|
-
.prompt-modal {
|
|
298
|
-
display: none;
|
|
299
|
-
position: fixed;
|
|
300
|
-
top: 0;
|
|
301
|
-
left: 0;
|
|
302
|
-
right: 0;
|
|
303
|
-
bottom: 0;
|
|
304
|
-
background: rgba(0, 0, 0, 0.7);
|
|
305
|
-
z-index: 1000;
|
|
306
|
-
align-items: center;
|
|
307
|
-
justify-content: center;
|
|
308
|
-
}
|
|
309
|
-
.prompt-modal.visible { display: flex; }
|
|
310
|
-
.prompt-modal-content {
|
|
311
|
-
background: var(--bg-primary);
|
|
312
|
-
border-radius: 12px;
|
|
313
|
-
max-width: 900px;
|
|
314
|
-
max-height: 80vh;
|
|
315
|
-
width: 90%;
|
|
316
|
-
display: flex;
|
|
317
|
-
flex-direction: column;
|
|
318
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
|
319
|
-
}
|
|
320
|
-
.prompt-modal-header {
|
|
321
|
-
display: flex;
|
|
322
|
-
justify-content: space-between;
|
|
323
|
-
align-items: center;
|
|
324
|
-
padding: 16px 20px;
|
|
325
|
-
border-bottom: 1px solid var(--border);
|
|
326
|
-
}
|
|
327
|
-
.prompt-modal-header h3 { margin: 0; }
|
|
328
|
-
.prompt-modal-close {
|
|
329
|
-
background: none;
|
|
330
|
-
border: none;
|
|
331
|
-
font-size: 24px;
|
|
332
|
-
cursor: pointer;
|
|
333
|
-
color: var(--muted);
|
|
334
|
-
}
|
|
335
|
-
.prompt-modal-close:hover { color: var(--text); }
|
|
336
|
-
.prompt-modal-body {
|
|
337
|
-
padding: 20px;
|
|
338
|
-
overflow-y: auto;
|
|
339
|
-
flex: 1;
|
|
340
|
-
}
|
|
341
|
-
.prompt-modal-body pre {
|
|
342
|
-
white-space: pre-wrap;
|
|
343
|
-
word-wrap: break-word;
|
|
344
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
345
|
-
font-size: 13px;
|
|
346
|
-
line-height: 1.5;
|
|
347
|
-
margin: 0;
|
|
348
|
-
background: var(--bg-secondary);
|
|
349
|
-
padding: 16px;
|
|
350
|
-
border-radius: 8px;
|
|
351
|
-
}
|
|
352
|
-
.log-container {
|
|
353
|
-
max-height: 70vh;
|
|
354
|
-
overflow-y: auto;
|
|
355
|
-
display: flex;
|
|
356
|
-
flex-direction: column;
|
|
357
|
-
gap: 8px;
|
|
358
|
-
}
|
|
359
155
|
.actions-bar {
|
|
360
156
|
display: flex;
|
|
361
157
|
gap: 12px;
|
|
@@ -380,222 +176,6 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
380
176
|
.btn-secondary:hover { border-color: var(--accent); }
|
|
381
177
|
.btn-danger { background: rgba(248, 113, 113, 0.15); color: var(--red); border: 1px solid transparent; }
|
|
382
178
|
.btn-danger:hover { border-color: var(--red); }
|
|
383
|
-
.comments-list {
|
|
384
|
-
display: flex;
|
|
385
|
-
flex-direction: column;
|
|
386
|
-
gap: 12px;
|
|
387
|
-
margin-bottom: 16px;
|
|
388
|
-
}
|
|
389
|
-
.comment {
|
|
390
|
-
padding: 16px;
|
|
391
|
-
background: var(--bg-tertiary);
|
|
392
|
-
border-radius: 8px;
|
|
393
|
-
border-left: 3px solid var(--border);
|
|
394
|
-
transition: all 0.2s ease;
|
|
395
|
-
}
|
|
396
|
-
.comment:hover {
|
|
397
|
-
border-left-color: var(--blue);
|
|
398
|
-
}
|
|
399
|
-
.comment-meta {
|
|
400
|
-
display: flex;
|
|
401
|
-
align-items: center;
|
|
402
|
-
flex-wrap: wrap;
|
|
403
|
-
gap: 8px;
|
|
404
|
-
cursor: pointer;
|
|
405
|
-
user-select: none;
|
|
406
|
-
}
|
|
407
|
-
.comment-meta::before {
|
|
408
|
-
content: '▶';
|
|
409
|
-
font-size: 10px;
|
|
410
|
-
color: var(--muted);
|
|
411
|
-
transition: transform 0.2s ease;
|
|
412
|
-
}
|
|
413
|
-
.comment.expanded .comment-meta::before {
|
|
414
|
-
transform: rotate(90deg);
|
|
415
|
-
}
|
|
416
|
-
.comment-source {
|
|
417
|
-
font-size: 10px;
|
|
418
|
-
padding: 3px 8px;
|
|
419
|
-
border-radius: 4px;
|
|
420
|
-
text-transform: uppercase;
|
|
421
|
-
font-weight: 600;
|
|
422
|
-
letter-spacing: 0.5px;
|
|
423
|
-
}
|
|
424
|
-
.comment-source.user { background: #3b82f6; color: white; }
|
|
425
|
-
.comment-source.claude { background: #8b5cf6; color: white; }
|
|
426
|
-
.comment-column {
|
|
427
|
-
font-size: 10px;
|
|
428
|
-
font-weight: 600;
|
|
429
|
-
text-transform: uppercase;
|
|
430
|
-
letter-spacing: 0.5px;
|
|
431
|
-
color: var(--blue);
|
|
432
|
-
padding: 3px 8px;
|
|
433
|
-
background: rgba(77,171,247,0.15);
|
|
434
|
-
border-radius: 4px;
|
|
435
|
-
}
|
|
436
|
-
.comment-date {
|
|
437
|
-
font-size: 11px;
|
|
438
|
-
color: var(--muted);
|
|
439
|
-
}
|
|
440
|
-
.comment-text {
|
|
441
|
-
font-size: 14px;
|
|
442
|
-
line-height: 1.6;
|
|
443
|
-
color: var(--text);
|
|
444
|
-
max-height: 0;
|
|
445
|
-
overflow: hidden;
|
|
446
|
-
transition: max-height 0.3s ease, margin-top 0.3s ease, padding-top 0.3s ease;
|
|
447
|
-
margin-top: 0;
|
|
448
|
-
padding-top: 0;
|
|
449
|
-
}
|
|
450
|
-
.comment.expanded .comment-text {
|
|
451
|
-
max-height: 500px;
|
|
452
|
-
margin-top: 12px;
|
|
453
|
-
padding-top: 12px;
|
|
454
|
-
border-top: 1px solid var(--border);
|
|
455
|
-
}
|
|
456
|
-
.comment-text code {
|
|
457
|
-
background: var(--bg);
|
|
458
|
-
padding: 2px 6px;
|
|
459
|
-
border-radius: 4px;
|
|
460
|
-
font-family: 'SF Mono', Monaco, monospace;
|
|
461
|
-
font-size: 12px;
|
|
462
|
-
}
|
|
463
|
-
.add-comment {
|
|
464
|
-
display: flex;
|
|
465
|
-
flex-direction: column;
|
|
466
|
-
gap: 8px;
|
|
467
|
-
}
|
|
468
|
-
.add-comment textarea {
|
|
469
|
-
width: 100%;
|
|
470
|
-
min-height: 80px;
|
|
471
|
-
padding: 12px;
|
|
472
|
-
background: var(--bg-tertiary);
|
|
473
|
-
border: 1px solid var(--border);
|
|
474
|
-
border-radius: 8px;
|
|
475
|
-
color: var(--text);
|
|
476
|
-
font-family: inherit;
|
|
477
|
-
font-size: 14px;
|
|
478
|
-
resize: vertical;
|
|
479
|
-
}
|
|
480
|
-
.add-comment textarea:focus {
|
|
481
|
-
outline: none;
|
|
482
|
-
border-color: var(--accent);
|
|
483
|
-
}
|
|
484
|
-
.btn-comment {
|
|
485
|
-
align-self: flex-end;
|
|
486
|
-
padding: 8px 16px;
|
|
487
|
-
background: var(--accent);
|
|
488
|
-
color: white;
|
|
489
|
-
border: none;
|
|
490
|
-
border-radius: 6px;
|
|
491
|
-
cursor: pointer;
|
|
492
|
-
font-size: 13px;
|
|
493
|
-
font-weight: 500;
|
|
494
|
-
}
|
|
495
|
-
.btn-comment:hover { opacity: 0.9; }
|
|
496
|
-
.btn-comment:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
497
|
-
.no-comments {
|
|
498
|
-
text-align: center;
|
|
499
|
-
color: var(--muted);
|
|
500
|
-
padding: 24px;
|
|
501
|
-
font-size: 14px;
|
|
502
|
-
}
|
|
503
|
-
.notification {
|
|
504
|
-
position: fixed;
|
|
505
|
-
bottom: 24px;
|
|
506
|
-
right: 24px;
|
|
507
|
-
padding: 12px 20px;
|
|
508
|
-
background: var(--green);
|
|
509
|
-
color: #000;
|
|
510
|
-
border-radius: 8px;
|
|
511
|
-
font-weight: 500;
|
|
512
|
-
transform: translateY(100px);
|
|
513
|
-
opacity: 0;
|
|
514
|
-
transition: all 0.3s ease;
|
|
515
|
-
z-index: 1000;
|
|
516
|
-
}
|
|
517
|
-
.notification.show { transform: translateY(0); opacity: 1; }
|
|
518
|
-
.notification.error { background: var(--red); color: white; }
|
|
519
|
-
|
|
520
|
-
/* Claude Terminal */
|
|
521
|
-
.claude-section .section-title {
|
|
522
|
-
display: flex;
|
|
523
|
-
align-items: center;
|
|
524
|
-
justify-content: space-between;
|
|
525
|
-
}
|
|
526
|
-
.claude-status {
|
|
527
|
-
font-size: 11px;
|
|
528
|
-
padding: 3px 10px;
|
|
529
|
-
border-radius: 12px;
|
|
530
|
-
background: var(--bg-tertiary);
|
|
531
|
-
color: var(--muted);
|
|
532
|
-
}
|
|
533
|
-
.claude-status.processing {
|
|
534
|
-
color: var(--yellow);
|
|
535
|
-
animation: pulse 1s infinite;
|
|
536
|
-
}
|
|
537
|
-
.claude-status.success { color: var(--green); }
|
|
538
|
-
.claude-status.error { color: var(--red); }
|
|
539
|
-
.claude-log {
|
|
540
|
-
background: #0d1117;
|
|
541
|
-
border: 1px solid var(--border);
|
|
542
|
-
border-radius: 8px;
|
|
543
|
-
padding: 16px;
|
|
544
|
-
max-height: 400px;
|
|
545
|
-
overflow-y: auto;
|
|
546
|
-
font-family: 'SF Mono', Monaco, 'Consolas', monospace;
|
|
547
|
-
font-size: 12px;
|
|
548
|
-
line-height: 1.6;
|
|
549
|
-
white-space: pre-wrap;
|
|
550
|
-
word-break: break-word;
|
|
551
|
-
color: var(--text);
|
|
552
|
-
margin: 0;
|
|
553
|
-
margin-top: 12px;
|
|
554
|
-
}
|
|
555
|
-
/* Formatted log entries */
|
|
556
|
-
.log-entry { margin-bottom: 8px; padding: 8px 12px; border-radius: 4px; border-left: 3px solid transparent; flex-shrink: 0; }
|
|
557
|
-
.log-entry.timestamp { color: #8b949e; font-size: 11px; border-left-color: #484f58; background: transparent; padding: 4px 12px; }
|
|
558
|
-
.log-entry.system { color: #8b949e; border-left-color: #484f58; background: rgba(139,148,158,0.1); }
|
|
559
|
-
.log-entry.user { color: #58a6ff; border-left-color: #58a6ff; background: rgba(88,166,255,0.1); }
|
|
560
|
-
.log-entry.assistant { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
|
|
561
|
-
.log-entry.tool-call { color: #d2a8ff; border-left-color: #d2a8ff; background: rgba(210,168,255,0.1); padding: 12px; }
|
|
562
|
-
.log-entry.tool-result { color: #ffa657; border-left-color: #ffa657; background: rgba(255,166,87,0.1); }
|
|
563
|
-
.log-entry.error { color: #f85149; border-left-color: #f85149; background: rgba(248,81,73,0.1); }
|
|
564
|
-
.log-entry.success { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
|
|
565
|
-
.log-label { font-weight: 600; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; display: block; opacity: 0.8; }
|
|
566
|
-
.log-content { white-space: pre-wrap; word-break: break-word; }
|
|
567
|
-
|
|
568
|
-
/* Code blocks with line numbers */
|
|
569
|
-
.log-code-block { background: #161b22; border-radius: 6px; overflow: hidden; margin: 8px 0; border: 1px solid #30363d; }
|
|
570
|
-
.log-code-header { background: #21262d; padding: 8px 12px; font-size: 12px; color: #8b949e; border-bottom: 1px solid #30363d; display: flex; align-items: center; gap: 8px; }
|
|
571
|
-
.log-code-header::before { content: ''; display: inline-block; width: 12px; height: 12px; background: #ffa657; border-radius: 50%; }
|
|
572
|
-
.log-code-content { padding: 12px; overflow-x: auto; font-family: 'Fira Code', 'SF Mono', Monaco, monospace; font-size: 12px; line-height: 1.5; max-height: 400px; overflow-y: auto; }
|
|
573
|
-
.log-code-line { display: flex; min-height: 20px; }
|
|
574
|
-
.log-line-number { color: #484f58; text-align: right; padding-right: 16px; user-select: none; min-width: 40px; flex-shrink: 0; }
|
|
575
|
-
.log-line-content { color: #c9d1d9; white-space: pre; }
|
|
576
|
-
|
|
577
|
-
/* Message cards */
|
|
578
|
-
.log-message-card { background: #161b22; border-radius: 8px; margin: 12px 0; overflow: hidden; border: 1px solid #30363d; flex-shrink: 0; }
|
|
579
|
-
.log-message-header { padding: 10px 14px; display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 13px; }
|
|
580
|
-
.log-message-header.assistant-header { background: linear-gradient(135deg, #238636 0%, #2ea043 100%); color: white; }
|
|
581
|
-
.log-message-header.user-header { background: linear-gradient(135deg, #1f6feb 0%, #388bfd 100%); color: white; }
|
|
582
|
-
.log-message-header.tool-header { background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%); color: white; }
|
|
583
|
-
.log-message-header.result-header { background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); color: white; }
|
|
584
|
-
.log-message-body { padding: 14px; border-top: 1px solid #30363d; color: #c9d1d9; white-space: pre-wrap; word-break: break-word; }
|
|
585
|
-
|
|
586
|
-
/* Tool badges */
|
|
587
|
-
.log-tool-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(139,92,246,0.2); color: #a78bfa; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
|
|
588
|
-
.log-tool-input { background: #0d1117; border-radius: 4px; padding: 8px 12px; margin-top: 8px; font-family: 'Fira Code', monospace; font-size: 11px; color: #8b949e; max-height: 150px; overflow: auto; white-space: pre-wrap; }
|
|
589
|
-
|
|
590
|
-
/* Collapsible raw JSON */
|
|
591
|
-
.log-raw-details { margin: 8px 0; }
|
|
592
|
-
.log-raw-summary { cursor: pointer; color: #8b949e; font-size: 12px; padding: 8px; background: rgba(139,148,158,0.1); border-radius: 4px; }
|
|
593
|
-
.log-raw-summary:hover { background: rgba(139,148,158,0.2); }
|
|
594
|
-
.log-raw-json { background: #0d1117; border-radius: 4px; padding: 12px; margin-top: 8px; font-size: 11px; color: #8b949e; max-height: 200px; overflow: auto; white-space: pre-wrap; }
|
|
595
|
-
@keyframes pulse {
|
|
596
|
-
0%, 100% { opacity: 1; }
|
|
597
|
-
50% { opacity: 0.5; }
|
|
598
|
-
}
|
|
599
179
|
</style>
|
|
600
180
|
</head>
|
|
601
181
|
<body>
|
|
@@ -611,6 +191,8 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
611
191
|
</div>
|
|
612
192
|
</header>
|
|
613
193
|
|
|
194
|
+
${generateTabsNav(issueKey, 'details', pageData)}
|
|
195
|
+
|
|
614
196
|
<main class="main-content">
|
|
615
197
|
<div class="issue-details">
|
|
616
198
|
<!-- Meta info -->
|
|
@@ -653,29 +235,6 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
653
235
|
</div>
|
|
654
236
|
` : ''}
|
|
655
237
|
|
|
656
|
-
<!-- History -->
|
|
657
|
-
${issue.history && issue.history.length > 0 ? `
|
|
658
|
-
<div class="section">
|
|
659
|
-
<div class="section-title" data-i18n="issueView.history">History</div>
|
|
660
|
-
<ul class="history-list">
|
|
661
|
-
${issue.history.map((h) => {
|
|
662
|
-
const date = new Date(h.at);
|
|
663
|
-
const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
664
|
-
const showPromptBtn = h.to && h.action !== 'created' ? `<a href="/issue/${issueKey}/${escapeHtml(h.to)}/prompt" class="btn-prompt" title="View prompt">📜</a>` : '';
|
|
665
|
-
const showLogBtn = h.to && h.action !== 'created' ? `<a href="/issue/${issueKey}/${escapeHtml(h.to)}/terminal" class="btn-prompt" title="View terminal log">🖥️</a>` : '';
|
|
666
|
-
return `<li class="history-item">
|
|
667
|
-
<span class="history-action">${h.action}</span>
|
|
668
|
-
${h.from ? `<span class="history-from">${escapeHtml(h.from)}</span> →` : ''}
|
|
669
|
-
<span class="history-to">${escapeHtml(h.to)}</span>
|
|
670
|
-
${showPromptBtn}
|
|
671
|
-
${showLogBtn}
|
|
672
|
-
<span class="history-date">${dateStr}</span>
|
|
673
|
-
</li>`;
|
|
674
|
-
}).join('')}
|
|
675
|
-
</ul>
|
|
676
|
-
</div>
|
|
677
|
-
` : ''}
|
|
678
|
-
|
|
679
238
|
<!-- Actions bar -->
|
|
680
239
|
<div class="actions-bar">
|
|
681
240
|
<button class="btn btn-primary" id="btn-save" onclick="saveIssue()" data-i18n="issueView.save">Save</button>
|
|
@@ -683,227 +242,12 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
683
242
|
<button class="btn btn-danger" onclick="archiveIssue()" data-i18n="issueView.archive">Archive</button>
|
|
684
243
|
</div>
|
|
685
244
|
</div>
|
|
686
|
-
|
|
687
|
-
<!-- Bottom section: Comments & Claude Terminal -->
|
|
688
|
-
<div class="issue-bottom">
|
|
689
|
-
<!-- Comments -->
|
|
690
|
-
<div class="section comments-section">
|
|
691
|
-
<div class="section-title"><span data-i18n="issueView.comments">Comments</span> (<span id="comments-count">${issue.comments?.length || 0}</span>)</div>
|
|
692
|
-
<div class="comments-list" id="comments-list"></div>
|
|
693
|
-
<div class="add-comment">
|
|
694
|
-
<textarea id="new-comment" placeholder="Add a comment..." data-i18n-placeholder="issueView.addComment"></textarea>
|
|
695
|
-
<button class="btn-comment" onclick="addComment()" data-i18n="btn.add">Add</button>
|
|
696
|
-
</div>
|
|
697
|
-
</div>
|
|
698
|
-
|
|
699
|
-
<!-- Claude Terminal -->
|
|
700
|
-
<div class="section claude-section" id="claude-section">
|
|
701
|
-
<div class="section-title">
|
|
702
|
-
<span data-i18n="issueView.claudeTerminal">Claude Terminal</span>
|
|
703
|
-
<span class="claude-status" id="claude-status" data-i18n="status.waiting">Waiting</span>
|
|
704
|
-
</div>
|
|
705
|
-
<pre class="claude-log" id="claude-log"></pre>
|
|
706
|
-
</div>
|
|
707
|
-
</div>
|
|
708
245
|
</main>
|
|
709
246
|
|
|
710
247
|
<div class="notification" id="notification"></div>
|
|
711
248
|
|
|
712
|
-
<!-- Prompt Modal -->
|
|
713
|
-
<div class="prompt-modal" id="prompt-modal" onclick="closePromptModal(event)">
|
|
714
|
-
<div class="prompt-modal-content" onclick="event.stopPropagation()">
|
|
715
|
-
<div class="prompt-modal-header">
|
|
716
|
-
<h3 id="prompt-modal-title">Prompt</h3>
|
|
717
|
-
<button class="prompt-modal-close" onclick="closePromptModal()">×</button>
|
|
718
|
-
</div>
|
|
719
|
-
<div class="prompt-modal-body">
|
|
720
|
-
<div id="prompt-modal-content" class="log-container"></div>
|
|
721
|
-
</div>
|
|
722
|
-
</div>
|
|
723
|
-
</div>
|
|
724
|
-
|
|
725
249
|
<script>
|
|
726
|
-
|
|
727
|
-
const ISSUE = ${issueData};
|
|
728
|
-
const COLUMNS = ${columnsData};
|
|
729
|
-
const STORAGE_KEY = 'autocode-lang';
|
|
730
|
-
|
|
731
|
-
let currentLang = localStorage.getItem(STORAGE_KEY) || 'fr';
|
|
732
|
-
let currentComments = ISSUE.comments || [];
|
|
733
|
-
|
|
734
|
-
const translations = {
|
|
735
|
-
en: {
|
|
736
|
-
'issueView.meta': 'Meta',
|
|
737
|
-
'issueView.labels': 'Labels',
|
|
738
|
-
'issueView.description': 'Description',
|
|
739
|
-
'issueView.criteria': 'Acceptance Criteria',
|
|
740
|
-
'issueView.history': 'History',
|
|
741
|
-
'issueView.actions': 'Actions',
|
|
742
|
-
'issueView.save': 'Save',
|
|
743
|
-
'issueView.moveNext': 'Move to next column',
|
|
744
|
-
'issueView.archive': 'Archive',
|
|
745
|
-
'issueView.confirmMove': 'Move this issue to the next column?',
|
|
746
|
-
'issueView.confirmArchive': 'Archive this issue?',
|
|
747
|
-
'issueView.comments': 'Comments',
|
|
748
|
-
'issueView.addComment': 'Add a comment...',
|
|
749
|
-
'issueView.noComments': 'No comments yet',
|
|
750
|
-
'issueView.claudeTerminal': 'Claude Terminal',
|
|
751
|
-
'issueView.noDescription': 'No description',
|
|
752
|
-
'issueView.noLog': 'No log yet. Waiting for Claude processing...',
|
|
753
|
-
'issueView.loadingPrompt': 'Loading prompt...',
|
|
754
|
-
'issueView.promptError': 'Error',
|
|
755
|
-
'btn.add': 'Add',
|
|
756
|
-
'btn.sending': 'Sending...',
|
|
757
|
-
'btn.saving': 'Saving...',
|
|
758
|
-
'status.waiting': 'Waiting',
|
|
759
|
-
'status.processing': 'Processing...',
|
|
760
|
-
'status.completed': 'Completed',
|
|
761
|
-
'status.failed': 'Failed',
|
|
762
|
-
'notify.commentAdded': 'Comment added',
|
|
763
|
-
'notify.issueAdvanced': 'Issue advanced',
|
|
764
|
-
'notify.issueArchived': 'Issue archived',
|
|
765
|
-
'notify.issueSaved': 'Issue saved',
|
|
766
|
-
'notify.error': 'Error'
|
|
767
|
-
},
|
|
768
|
-
fr: {
|
|
769
|
-
'issueView.meta': 'Méta',
|
|
770
|
-
'issueView.labels': 'Labels',
|
|
771
|
-
'issueView.description': 'Description',
|
|
772
|
-
'issueView.criteria': 'Critères d\\'acceptation',
|
|
773
|
-
'issueView.history': 'Historique',
|
|
774
|
-
'issueView.actions': 'Actions',
|
|
775
|
-
'issueView.save': 'Sauvegarder',
|
|
776
|
-
'issueView.moveNext': 'Déplacer vers la colonne suivante',
|
|
777
|
-
'issueView.archive': 'Archiver',
|
|
778
|
-
'issueView.confirmMove': 'Déplacer ce ticket vers la colonne suivante ?',
|
|
779
|
-
'issueView.confirmArchive': 'Archiver ce ticket ?',
|
|
780
|
-
'issueView.comments': 'Commentaires',
|
|
781
|
-
'issueView.addComment': 'Ajouter un commentaire...',
|
|
782
|
-
'issueView.noComments': 'Aucun commentaire',
|
|
783
|
-
'issueView.claudeTerminal': 'Terminal Claude',
|
|
784
|
-
'issueView.noDescription': 'Aucune description',
|
|
785
|
-
'issueView.noLog': 'Aucun log. En attente du traitement Claude...',
|
|
786
|
-
'issueView.loadingPrompt': 'Chargement du prompt...',
|
|
787
|
-
'issueView.promptError': 'Erreur',
|
|
788
|
-
'btn.add': 'Ajouter',
|
|
789
|
-
'btn.sending': 'Envoi...',
|
|
790
|
-
'btn.saving': 'Sauvegarde...',
|
|
791
|
-
'status.waiting': 'En attente',
|
|
792
|
-
'status.processing': 'En cours...',
|
|
793
|
-
'status.completed': 'Terminé',
|
|
794
|
-
'status.failed': 'Échec',
|
|
795
|
-
'notify.commentAdded': 'Commentaire ajouté',
|
|
796
|
-
'notify.issueAdvanced': 'Ticket avancé',
|
|
797
|
-
'notify.issueArchived': 'Ticket archivé',
|
|
798
|
-
'notify.issueSaved': 'Ticket sauvegardé',
|
|
799
|
-
'notify.error': 'Erreur'
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
function t(key) {
|
|
804
|
-
return translations[currentLang]?.[key] || translations['en'][key] || key;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function escapeHtml(text) {
|
|
808
|
-
if (!text) return '';
|
|
809
|
-
const div = document.createElement('div');
|
|
810
|
-
div.textContent = text;
|
|
811
|
-
return div.innerHTML;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
function renderMarkdown(text) {
|
|
815
|
-
if (!text) return '';
|
|
816
|
-
let html = escapeHtml(text);
|
|
817
|
-
html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
|
|
818
|
-
html = html.replace(/^## (.+)$/gm, '<h3>$1</h3>');
|
|
819
|
-
html = html.replace(/^# (.+)$/gm, '<h2>$1</h2>');
|
|
820
|
-
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
|
|
821
|
-
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
|
|
822
|
-
html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
823
|
-
html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
824
|
-
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
825
|
-
html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');
|
|
826
|
-
html = html.replace(/\\n/g, '<br>');
|
|
827
|
-
return html;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function updateLangUI() {
|
|
831
|
-
document.querySelectorAll('.lang-btn').forEach(btn => {
|
|
832
|
-
btn.classList.toggle('active', btn.dataset.lang === currentLang);
|
|
833
|
-
});
|
|
834
|
-
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
835
|
-
el.textContent = t(el.dataset.i18n);
|
|
836
|
-
});
|
|
837
|
-
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
|
838
|
-
el.placeholder = t(el.dataset.i18nPlaceholder);
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function renderComments() {
|
|
843
|
-
const list = document.getElementById('comments-list');
|
|
844
|
-
const count = document.getElementById('comments-count');
|
|
845
|
-
count.textContent = currentComments.length;
|
|
846
|
-
|
|
847
|
-
if (currentComments.length === 0) {
|
|
848
|
-
list.innerHTML = '<div class="no-comments">' + t('issueView.noComments') + '</div>';
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const sorted = [...currentComments].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
853
|
-
list.innerHTML = sorted.map((comment, index) => {
|
|
854
|
-
const date = new Date(comment.created_at);
|
|
855
|
-
const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
856
|
-
const source = comment.source || 'user';
|
|
857
|
-
const sourceBadge = source === 'claude'
|
|
858
|
-
? '<span class="comment-source claude">Claude</span>'
|
|
859
|
-
: '<span class="comment-source user">User</span>';
|
|
860
|
-
return '<div class="comment" id="comment-' + index + '">' +
|
|
861
|
-
'<div class="comment-meta" onclick="toggleComment(' + index + ')">' + sourceBadge + '<span class="comment-column">' + (comment.column || 'N/A') + '</span>' +
|
|
862
|
-
'<span class="comment-date">' + dateStr + '</span></div>' +
|
|
863
|
-
'<div class="comment-text">' + renderMarkdown(comment.text) + '</div></div>';
|
|
864
|
-
}).join('');
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function toggleComment(index) {
|
|
868
|
-
const comments = document.querySelectorAll('.comment');
|
|
869
|
-
comments.forEach((comment, i) => {
|
|
870
|
-
if (i === index) {
|
|
871
|
-
comment.classList.toggle('expanded');
|
|
872
|
-
} else {
|
|
873
|
-
comment.classList.remove('expanded');
|
|
874
|
-
}
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
async function addComment() {
|
|
879
|
-
const textarea = document.getElementById('new-comment');
|
|
880
|
-
const text = textarea.value.trim();
|
|
881
|
-
if (!text) return;
|
|
882
|
-
|
|
883
|
-
const btn = document.querySelector('.btn-comment');
|
|
884
|
-
btn.disabled = true;
|
|
885
|
-
btn.textContent = t('btn.sending');
|
|
886
|
-
|
|
887
|
-
try {
|
|
888
|
-
const res = await fetch('/api/issues/' + ISSUE_KEY + '/comments', {
|
|
889
|
-
method: 'POST',
|
|
890
|
-
headers: { 'Content-Type': 'application/json' },
|
|
891
|
-
body: JSON.stringify({ text, source: 'user' })
|
|
892
|
-
});
|
|
893
|
-
const result = await res.json();
|
|
894
|
-
textarea.value = '';
|
|
895
|
-
if (result.success && result.data && result.data.comments) {
|
|
896
|
-
currentComments = result.data.comments;
|
|
897
|
-
renderComments();
|
|
898
|
-
}
|
|
899
|
-
showNotification(t('notify.commentAdded'));
|
|
900
|
-
} catch (e) {
|
|
901
|
-
showNotification(t('notify.error') + ': ' + e.message, true);
|
|
902
|
-
} finally {
|
|
903
|
-
btn.disabled = false;
|
|
904
|
-
btn.textContent = t('btn.add');
|
|
905
|
-
}
|
|
906
|
-
}
|
|
250
|
+
${generateIssueBaseScript(issueKey)}
|
|
907
251
|
|
|
908
252
|
let isEditingDescription = false;
|
|
909
253
|
|
|
@@ -1010,32 +354,8 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
1010
354
|
}
|
|
1011
355
|
}
|
|
1012
356
|
|
|
1013
|
-
|
|
1014
|
-
const notification = document.getElementById('notification');
|
|
1015
|
-
notification.textContent = msg;
|
|
1016
|
-
notification.className = 'notification show' + (isError ? ' error' : '');
|
|
1017
|
-
setTimeout(() => notification.className = 'notification', 3000);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Language switcher
|
|
1021
|
-
document.querySelectorAll('.lang-btn').forEach(btn => {
|
|
1022
|
-
btn.addEventListener('click', () => {
|
|
1023
|
-
const newLang = btn.dataset.lang;
|
|
1024
|
-
if (newLang !== currentLang) {
|
|
1025
|
-
currentLang = newLang;
|
|
1026
|
-
localStorage.setItem(STORAGE_KEY, newLang);
|
|
1027
|
-
updateLangUI();
|
|
1028
|
-
renderComments();
|
|
1029
|
-
}
|
|
1030
|
-
});
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
// ========================================
|
|
1034
|
-
// WEBSOCKET & CLAUDE LOG
|
|
1035
|
-
// ========================================
|
|
357
|
+
// WebSocket for real-time updates
|
|
1036
358
|
let ws;
|
|
1037
|
-
let logPollingInterval = null;
|
|
1038
|
-
|
|
1039
359
|
function connectWebSocket() {
|
|
1040
360
|
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1041
361
|
ws = new WebSocket(protocol + '//' + location.host + '/ws');
|
|
@@ -1043,27 +363,8 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
1043
363
|
ws.onmessage = (event) => {
|
|
1044
364
|
try {
|
|
1045
365
|
const data = JSON.parse(event.data);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if (data.key === ISSUE_KEY) {
|
|
1049
|
-
location.reload();
|
|
1050
|
-
}
|
|
1051
|
-
break;
|
|
1052
|
-
case 'claude_start':
|
|
1053
|
-
if (data.issue === ISSUE_KEY) {
|
|
1054
|
-
onClaudeStart();
|
|
1055
|
-
}
|
|
1056
|
-
break;
|
|
1057
|
-
case 'claude_stream':
|
|
1058
|
-
if (data.issue === ISSUE_KEY) {
|
|
1059
|
-
fetchLog();
|
|
1060
|
-
}
|
|
1061
|
-
break;
|
|
1062
|
-
case 'claude_end':
|
|
1063
|
-
if (data.issue === ISSUE_KEY) {
|
|
1064
|
-
onClaudeEnd(data.success, data.duration);
|
|
1065
|
-
}
|
|
1066
|
-
break;
|
|
366
|
+
if (data.type === 'issue_updated' && data.key === ISSUE_KEY) {
|
|
367
|
+
location.reload();
|
|
1067
368
|
}
|
|
1068
369
|
} catch (e) {
|
|
1069
370
|
console.error('WebSocket message error:', e);
|
|
@@ -1075,288 +376,9 @@ export function generateIssueViewPage(issueKey, lang) {
|
|
|
1075
376
|
};
|
|
1076
377
|
}
|
|
1077
378
|
|
|
1078
|
-
function escapeHtml(str) {
|
|
1079
|
-
if (typeof str !== 'string') return '';
|
|
1080
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
function formatCodeBlock(content, filename) {
|
|
1084
|
-
const lines = content.split(/\\\\n|\\n/);
|
|
1085
|
-
// Détecter le langage depuis le nom de fichier
|
|
1086
|
-
let lang = 'plaintext';
|
|
1087
|
-
if (filename) {
|
|
1088
|
-
const ext = filename.split('.').pop().toLowerCase();
|
|
1089
|
-
const langMap = {
|
|
1090
|
-
'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
|
|
1091
|
-
'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
|
|
1092
|
-
'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
|
|
1093
|
-
'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
|
|
1094
|
-
'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
|
|
1095
|
-
};
|
|
1096
|
-
lang = langMap[ext] || 'plaintext';
|
|
1097
|
-
}
|
|
1098
|
-
// Extraire le code sans les numéros de ligne pour highlight.js
|
|
1099
|
-
let codeLines = [];
|
|
1100
|
-
for (const line of lines) {
|
|
1101
|
-
const match = line.match(/^\\s*\\d+[→|](.*)$/);
|
|
1102
|
-
if (match) {
|
|
1103
|
-
codeLines.push(match[1]);
|
|
1104
|
-
} else if (line.trim()) {
|
|
1105
|
-
codeLines.push(line);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
const codeContent = codeLines.join('\\n');
|
|
1109
|
-
const lineCount = codeLines.length;
|
|
1110
|
-
const preview = filename || (lineCount + ' lignes');
|
|
1111
|
-
|
|
1112
|
-
let html = '<details class="log-code-block">';
|
|
1113
|
-
html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
|
|
1114
|
-
html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
|
|
1115
|
-
html += '</details>';
|
|
1116
|
-
return html;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
function formatLogContent(rawContent) {
|
|
1120
|
-
if (!rawContent) return '';
|
|
1121
|
-
// Supprimer les balises <system-reminder> et leur contenu
|
|
1122
|
-
let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
|
|
1123
|
-
const lines = cleanedContent.split('\\n');
|
|
1124
|
-
const entries = [];
|
|
1125
|
-
let textBuffer = [];
|
|
1126
|
-
|
|
1127
|
-
function flushTextBuffer() {
|
|
1128
|
-
if (textBuffer.length > 0) {
|
|
1129
|
-
const text = textBuffer.join('\\n');
|
|
1130
|
-
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(text) + '</div></div>');
|
|
1131
|
-
textBuffer = [];
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
for (const line of lines) {
|
|
1136
|
-
// Garder les lignes vides dans le buffer pour les paragraphes markdown
|
|
1137
|
-
if (!line.trim()) {
|
|
1138
|
-
if (textBuffer.length > 0) textBuffer.push('');
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
|
|
1143
|
-
if (timestampMatch) {
|
|
1144
|
-
flushTextBuffer();
|
|
1145
|
-
const date = new Date(timestampMatch[1]);
|
|
1146
|
-
const timeStr = date.toLocaleTimeString();
|
|
1147
|
-
entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// [RAW] - Parse avec regex (pas JSON.parse car les lignes sont coupées)
|
|
1152
|
-
if (line.startsWith('[RAW] ')) {
|
|
1153
|
-
const raw = line.slice(6);
|
|
1154
|
-
|
|
1155
|
-
// Ignorer les lignes de continuation (ne commencent pas par {)
|
|
1156
|
-
if (!raw.startsWith('{')) {
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// Extraire tool_result content
|
|
1161
|
-
if (raw.includes('"type":"tool_result"')) {
|
|
1162
|
-
const contentStart = raw.indexOf('"content":"');
|
|
1163
|
-
if (contentStart !== -1) {
|
|
1164
|
-
// Extraire le contenu après "content":"
|
|
1165
|
-
let content = raw.slice(contentStart + 11);
|
|
1166
|
-
// Enlever le reste du JSON (approximatif car tronqué)
|
|
1167
|
-
const endQuote = content.lastIndexOf('"');
|
|
1168
|
-
if (endQuote > 0) content = content.slice(0, endQuote);
|
|
1169
|
-
// Décoder les échappements
|
|
1170
|
-
const decoded = content.replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"').replace(/\\\\t/g, '\\t');
|
|
1171
|
-
// Vérifier si c'est du code avec numéros de ligne
|
|
1172
|
-
if (/^\\s*\\d+[→|]/.test(decoded)) {
|
|
1173
|
-
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
|
|
1174
|
-
} else {
|
|
1175
|
-
// Résultat texte simple (liste de fichiers, etc.)
|
|
1176
|
-
const lines = decoded.split('\\n').slice(0, 20); // Limiter à 20 lignes
|
|
1177
|
-
const truncated = decoded.split('\\n').length > 20 ? '<div style="color:#8b949e;font-style:italic">... (truncated)</div>' : '';
|
|
1178
|
-
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body" style="font-family:monospace;font-size:12px;white-space:pre-wrap;max-height:200px;overflow-y:auto">' + escapeHtml(lines.join('\\n')) + truncated + '</div></div>');
|
|
1179
|
-
}
|
|
1180
|
-
continue;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// Extraire message assistant texte
|
|
1185
|
-
const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
|
|
1186
|
-
if (textMatch) {
|
|
1187
|
-
const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
|
|
1188
|
-
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(decoded) + '</div></div>');
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// Extraire tool_use
|
|
1193
|
-
const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
|
|
1194
|
-
if (toolMatch) {
|
|
1195
|
-
entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
|
|
1196
|
-
continue;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Ignorer les autres RAW
|
|
1200
|
-
continue;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
if (line.startsWith('[SYSTEM]')) {
|
|
1204
|
-
flushTextBuffer();
|
|
1205
|
-
entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
|
|
1206
|
-
continue;
|
|
1207
|
-
}
|
|
1208
|
-
if (line.startsWith('[ASSISTANT]')) {
|
|
1209
|
-
flushTextBuffer();
|
|
1210
|
-
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(line.slice(12)) + '</div></div>');
|
|
1211
|
-
continue;
|
|
1212
|
-
}
|
|
1213
|
-
if (line.startsWith('[TOOL]')) {
|
|
1214
|
-
flushTextBuffer();
|
|
1215
|
-
entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
|
|
1216
|
-
continue;
|
|
1217
|
-
}
|
|
1218
|
-
if (line.startsWith('[RESULT]')) {
|
|
1219
|
-
flushTextBuffer();
|
|
1220
|
-
const content = line.slice(9);
|
|
1221
|
-
if (/^\\s*\\d+[→|]/.test(content)) {
|
|
1222
|
-
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
|
|
1223
|
-
} else {
|
|
1224
|
-
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
|
|
1225
|
-
}
|
|
1226
|
-
continue;
|
|
1227
|
-
}
|
|
1228
|
-
if (line.startsWith('[ERROR]')) {
|
|
1229
|
-
flushTextBuffer();
|
|
1230
|
-
entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
|
|
1231
|
-
continue;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
// Ligne de texte brut - ajouter au buffer pour regrouper
|
|
1235
|
-
textBuffer.push(line);
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// Flush le buffer restant
|
|
1239
|
-
flushTextBuffer();
|
|
1240
|
-
return entries.join('');
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
function startLogPolling() {
|
|
1244
|
-
stopLogPolling();
|
|
1245
|
-
logPollingInterval = setInterval(fetchLog, 1000);
|
|
1246
|
-
fetchLog();
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
function stopLogPolling() {
|
|
1250
|
-
if (logPollingInterval) {
|
|
1251
|
-
clearInterval(logPollingInterval);
|
|
1252
|
-
logPollingInterval = null;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
async function fetchLog() {
|
|
1257
|
-
try {
|
|
1258
|
-
const res = await fetch('/api/issues/' + ISSUE_KEY + '/log');
|
|
1259
|
-
const json = await res.json();
|
|
1260
|
-
const log = document.getElementById('claude-log');
|
|
1261
|
-
|
|
1262
|
-
if (json.success && json.data && (json.data.exists || json.data.content)) {
|
|
1263
|
-
log.innerHTML = formatLogContent(json.data.content || '');
|
|
1264
|
-
log.scrollTop = log.scrollHeight;
|
|
1265
|
-
} else {
|
|
1266
|
-
log.innerHTML = '<div class="log-entry system">' + t('issueView.noLog') + '</div>';
|
|
1267
|
-
}
|
|
1268
|
-
} catch (e) {
|
|
1269
|
-
console.error('Log fetch error:', e);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
function onClaudeStart() {
|
|
1274
|
-
const status = document.getElementById('claude-status');
|
|
1275
|
-
status.className = 'claude-status processing';
|
|
1276
|
-
status.textContent = t('status.processing');
|
|
1277
|
-
startLogPolling();
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
function onClaudeEnd(success, duration) {
|
|
1281
|
-
stopLogPolling();
|
|
1282
|
-
fetchLog();
|
|
1283
|
-
const status = document.getElementById('claude-status');
|
|
1284
|
-
if (success) {
|
|
1285
|
-
status.className = 'claude-status success';
|
|
1286
|
-
status.textContent = t('status.completed') + ' (' + (duration / 1000).toFixed(1) + 's)';
|
|
1287
|
-
} else {
|
|
1288
|
-
status.className = 'claude-status error';
|
|
1289
|
-
status.textContent = t('status.failed');
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// Prompt Modal
|
|
1294
|
-
async function showPrompt(columnSlug) {
|
|
1295
|
-
const modal = document.getElementById('prompt-modal');
|
|
1296
|
-
const title = document.getElementById('prompt-modal-title');
|
|
1297
|
-
const content = document.getElementById('prompt-modal-content');
|
|
1298
|
-
|
|
1299
|
-
title.textContent = t('issueView.loadingPrompt');
|
|
1300
|
-
content.textContent = '';
|
|
1301
|
-
modal.classList.add('visible');
|
|
1302
|
-
|
|
1303
|
-
try {
|
|
1304
|
-
const res = await fetch('/api/issues/' + ISSUE_KEY + '/prompt/' + columnSlug);
|
|
1305
|
-
const json = await res.json();
|
|
1306
|
-
if (json.success) {
|
|
1307
|
-
title.textContent = 'Prompt → ' + json.data.column;
|
|
1308
|
-
content.textContent = json.data.prompt;
|
|
1309
|
-
} else {
|
|
1310
|
-
title.textContent = t('issueView.promptError');
|
|
1311
|
-
content.textContent = json.error || 'Error loading prompt';
|
|
1312
|
-
}
|
|
1313
|
-
} catch (e) {
|
|
1314
|
-
title.textContent = t('issueView.promptError');
|
|
1315
|
-
content.textContent = e.message;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
function closePromptModal(event) {
|
|
1320
|
-
if (event && event.target !== event.currentTarget) return;
|
|
1321
|
-
document.getElementById('prompt-modal').classList.remove('visible');
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
// Log Modal (reuses prompt modal)
|
|
1325
|
-
async function showLog(columnSlug) {
|
|
1326
|
-
const modal = document.getElementById('prompt-modal');
|
|
1327
|
-
const title = document.getElementById('prompt-modal-title');
|
|
1328
|
-
const content = document.getElementById('prompt-modal-content');
|
|
1329
|
-
|
|
1330
|
-
title.textContent = t('issueView.loadingLog') || 'Loading log...';
|
|
1331
|
-
content.textContent = '';
|
|
1332
|
-
modal.classList.add('visible');
|
|
1333
|
-
|
|
1334
|
-
try {
|
|
1335
|
-
const res = await fetch('/api/issues/' + ISSUE_KEY + '/log/' + columnSlug);
|
|
1336
|
-
const json = await res.json();
|
|
1337
|
-
if (json.success) {
|
|
1338
|
-
title.textContent = 'Terminal → ' + columnSlug;
|
|
1339
|
-
if (json.data.content) {
|
|
1340
|
-
content.innerHTML = formatLogContent(json.data.content);
|
|
1341
|
-
} else {
|
|
1342
|
-
content.textContent = t('issueView.noLog') || 'No log available';
|
|
1343
|
-
}
|
|
1344
|
-
} else {
|
|
1345
|
-
title.textContent = t('issueView.logError') || 'Log Error';
|
|
1346
|
-
content.textContent = json.error || 'Error loading log';
|
|
1347
|
-
}
|
|
1348
|
-
} catch (e) {
|
|
1349
|
-
title.textContent = t('issueView.logError') || 'Log Error';
|
|
1350
|
-
content.textContent = e.message;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
379
|
// Init
|
|
1355
|
-
updateLangUI();
|
|
1356
380
|
renderDescription();
|
|
1357
|
-
renderComments();
|
|
1358
381
|
connectWebSocket();
|
|
1359
|
-
fetchLog();
|
|
1360
382
|
</script>
|
|
1361
383
|
</body>
|
|
1362
384
|
</html>`;
|