hale-commenting-system 2.2.0 → 2.2.2
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/.claude/settings.local.json +7 -0
- package/.editorconfig +17 -0
- package/.eslintrc.js +75 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- package/.github/workflows/ci.yaml +51 -0
- package/.prettierignore +1 -0
- package/.prettierrc +4 -0
- package/GITHUB_OAUTH_ENV_TEMPLATE.md +53 -0
- package/LICENSE +21 -0
- package/README.md +92 -21
- package/package.json +74 -50
- package/scripts/README.md +42 -0
- package/scripts/integrate.js +472 -0
- package/src/app/AppLayout/AppLayout.tsx +248 -0
- package/src/app/Comments/Comments.tsx +273 -0
- package/src/app/Dashboard/Dashboard.tsx +10 -0
- package/src/app/NotFound/NotFound.tsx +35 -0
- package/src/app/Settings/General/GeneralSettings.tsx +16 -0
- package/src/app/Settings/Profile/ProfileSettings.tsx +18 -0
- package/src/app/Support/Support.tsx +50 -0
- package/src/app/__snapshots__/app.test.tsx.snap +524 -0
- package/src/app/app.css +11 -0
- package/src/app/app.test.tsx +55 -0
- package/src/app/bgimages/Patternfly-Logo.svg +28 -0
- package/src/app/commenting-system/components/CommentOverlay.tsx +93 -0
- package/src/app/commenting-system/components/CommentPanel.tsx +534 -0
- package/src/app/commenting-system/components/CommentPin.tsx +60 -0
- package/src/app/commenting-system/components/DetailsTab.tsx +516 -0
- package/src/app/commenting-system/components/FloatingWidget.tsx +130 -0
- package/src/app/commenting-system/components/JiraTab.tsx +696 -0
- package/src/app/commenting-system/contexts/CommentContext.tsx +1033 -0
- package/src/app/commenting-system/contexts/GitHubAuthContext.tsx +84 -0
- package/{dist/index.d.ts → src/app/commenting-system/index.ts} +5 -4
- package/src/app/commenting-system/services/githubAdapter.ts +359 -0
- package/src/app/commenting-system/types/index.ts +27 -0
- package/src/app/commenting-system/utils/version.ts +19 -0
- package/src/app/index.tsx +22 -0
- package/src/app/routes.tsx +81 -0
- package/src/app/utils/useDocumentTitle.ts +13 -0
- package/src/favicon.png +0 -0
- package/src/index.html +18 -0
- package/src/index.tsx +25 -0
- package/src/test/setup.ts +33 -0
- package/src/typings.d.ts +12 -0
- package/stylePaths.js +14 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +19 -0
- package/webpack.common.js +139 -0
- package/webpack.dev.js +318 -0
- package/webpack.prod.js +38 -0
- package/bin/detect.d.ts +0 -10
- package/bin/detect.js +0 -134
- package/bin/generators.d.ts +0 -20
- package/bin/generators.js +0 -272
- package/bin/hale-commenting.js +0 -4
- package/bin/index.d.ts +0 -2
- package/bin/index.js +0 -61
- package/bin/onboarding.d.ts +0 -1
- package/bin/onboarding.js +0 -395
- package/bin/postinstall.d.ts +0 -2
- package/bin/postinstall.js +0 -65
- package/bin/validators.d.ts +0 -2
- package/bin/validators.js +0 -66
- package/dist/cli/detect.d.ts +0 -10
- package/dist/cli/detect.js +0 -134
- package/dist/cli/generators.d.ts +0 -20
- package/dist/cli/generators.js +0 -272
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -61
- package/dist/cli/onboarding.d.ts +0 -1
- package/dist/cli/onboarding.js +0 -395
- package/dist/cli/postinstall.d.ts +0 -2
- package/dist/cli/postinstall.js +0 -65
- package/dist/cli/validators.d.ts +0 -2
- package/dist/cli/validators.js +0 -66
- package/dist/components/CommentOverlay.d.ts +0 -2
- package/dist/components/CommentOverlay.js +0 -101
- package/dist/components/CommentPanel.d.ts +0 -6
- package/dist/components/CommentPanel.js +0 -334
- package/dist/components/CommentPin.d.ts +0 -11
- package/dist/components/CommentPin.js +0 -64
- package/dist/components/DetailsTab.d.ts +0 -2
- package/dist/components/DetailsTab.js +0 -380
- package/dist/components/FloatingWidget.d.ts +0 -8
- package/dist/components/FloatingWidget.js +0 -128
- package/dist/components/JiraTab.d.ts +0 -2
- package/dist/components/JiraTab.js +0 -507
- package/dist/contexts/CommentContext.d.ts +0 -30
- package/dist/contexts/CommentContext.js +0 -891
- package/dist/contexts/GitHubAuthContext.d.ts +0 -13
- package/dist/contexts/GitHubAuthContext.js +0 -96
- package/dist/index.js +0 -27
- package/dist/services/githubAdapter.d.ts +0 -56
- package/dist/services/githubAdapter.js +0 -321
- package/dist/types/index.d.ts +0 -25
- package/dist/types/index.js +0 -2
- package/dist/utils/version.d.ts +0 -1
- package/dist/utils/version.js +0 -23
- package/templates/webpack-middleware.js +0 -226
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.JiraTab = void 0;
|
|
37
|
-
const React = __importStar(require("react"));
|
|
38
|
-
const react_router_dom_1 = require("react-router-dom");
|
|
39
|
-
const react_core_1 = require("@patternfly/react-core");
|
|
40
|
-
const react_icons_1 = require("@patternfly/react-icons");
|
|
41
|
-
const githubAdapter_1 = require("../services/githubAdapter");
|
|
42
|
-
const STORAGE_KEY = 'hale_commenting_jira_v1';
|
|
43
|
-
const GH_JIRA_PATH = '.hale/jira.json';
|
|
44
|
-
function safeParseStore(raw) {
|
|
45
|
-
if (!raw)
|
|
46
|
-
return {};
|
|
47
|
-
try {
|
|
48
|
-
const parsed = JSON.parse(raw);
|
|
49
|
-
if (!parsed || typeof parsed !== 'object')
|
|
50
|
-
return {};
|
|
51
|
-
return parsed;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return {};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function getStore() {
|
|
58
|
-
if (typeof window === 'undefined')
|
|
59
|
-
return {};
|
|
60
|
-
return safeParseStore(window.localStorage.getItem(STORAGE_KEY));
|
|
61
|
-
}
|
|
62
|
-
function setStore(next) {
|
|
63
|
-
if (typeof window === 'undefined')
|
|
64
|
-
return;
|
|
65
|
-
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
66
|
-
}
|
|
67
|
-
function normalizePathname(pathname) {
|
|
68
|
-
if (!pathname)
|
|
69
|
-
return '/';
|
|
70
|
-
const cleaned = pathname.split('?')[0].split('#')[0];
|
|
71
|
-
return cleaned === '' ? '/' : cleaned;
|
|
72
|
-
}
|
|
73
|
-
function getSectionRoute(pathname) {
|
|
74
|
-
const normalized = normalizePathname(pathname);
|
|
75
|
-
const parts = normalized.split('/').filter(Boolean);
|
|
76
|
-
if (parts.length === 0)
|
|
77
|
-
return '/';
|
|
78
|
-
return `/${parts[0]}`;
|
|
79
|
-
}
|
|
80
|
-
function getPageKey(pathname) {
|
|
81
|
-
return `page:${normalizePathname(pathname)}`;
|
|
82
|
-
}
|
|
83
|
-
function getSectionKey(sectionRoute) {
|
|
84
|
-
return `section:${normalizePathname(sectionRoute)}/*`;
|
|
85
|
-
}
|
|
86
|
-
function loadForRoute(pathname) {
|
|
87
|
-
const store = getStore();
|
|
88
|
-
const pageKey = getPageKey(pathname);
|
|
89
|
-
if (store[pageKey])
|
|
90
|
-
return { record: store[pageKey], source: 'page' };
|
|
91
|
-
const sectionRoute = getSectionRoute(pathname);
|
|
92
|
-
const sectionKey = getSectionKey(sectionRoute);
|
|
93
|
-
if (store[sectionKey])
|
|
94
|
-
return { record: store[sectionKey], source: 'section' };
|
|
95
|
-
return { record: null, source: null };
|
|
96
|
-
}
|
|
97
|
-
const normalizeJiraKey = (input) => {
|
|
98
|
-
const raw = input.trim();
|
|
99
|
-
if (!raw)
|
|
100
|
-
return '';
|
|
101
|
-
// Allow users to paste full URLs; extract trailing key-ish segment.
|
|
102
|
-
const m = raw.match(/([A-Z][A-Z0-9]+-\d+)/i);
|
|
103
|
-
if (m?.[1])
|
|
104
|
-
return m[1].toUpperCase();
|
|
105
|
-
return raw.toUpperCase();
|
|
106
|
-
};
|
|
107
|
-
const stripHtmlTags = (input) => {
|
|
108
|
-
// Jira sometimes returns HTML-ish strings (or users paste HTML). For our UI,
|
|
109
|
-
// show readable plain text.
|
|
110
|
-
return input.replace(/<[^>]*>/g, '').replace(/\r\n/g, '\n').trim();
|
|
111
|
-
};
|
|
112
|
-
const canonicalizeSectionTitle = (raw) => {
|
|
113
|
-
const t = raw.trim().replace(/\s+/g, ' ');
|
|
114
|
-
const lower = t.toLowerCase();
|
|
115
|
-
if (lower === 'problem statement')
|
|
116
|
-
return 'Problem statement';
|
|
117
|
-
if (lower === 'objective')
|
|
118
|
-
return 'Objective';
|
|
119
|
-
if (lower === 'definition of done')
|
|
120
|
-
return 'Definition of Done';
|
|
121
|
-
if (lower === 'job stories')
|
|
122
|
-
return 'Job Stories';
|
|
123
|
-
if (lower === 'stakeholders')
|
|
124
|
-
return 'Stakeholders';
|
|
125
|
-
return t;
|
|
126
|
-
};
|
|
127
|
-
const parseJiraTemplateSections = (rawText) => {
|
|
128
|
-
const text = stripHtmlTags(rawText || '');
|
|
129
|
-
if (!text)
|
|
130
|
-
return [];
|
|
131
|
-
const lines = text.split('\n');
|
|
132
|
-
const sections = [];
|
|
133
|
-
let currentTitle = null;
|
|
134
|
-
let currentBody = [];
|
|
135
|
-
const flush = () => {
|
|
136
|
-
if (!currentTitle)
|
|
137
|
-
return;
|
|
138
|
-
const body = currentBody.join('\n').trim();
|
|
139
|
-
sections.push({ title: canonicalizeSectionTitle(currentTitle), body });
|
|
140
|
-
currentTitle = null;
|
|
141
|
-
currentBody = [];
|
|
142
|
-
};
|
|
143
|
-
const isKnownHeading = (t) => {
|
|
144
|
-
const lower = t.trim().toLowerCase();
|
|
145
|
-
return (lower === 'problem statement' ||
|
|
146
|
-
lower === 'objective' ||
|
|
147
|
-
lower === 'job stories' ||
|
|
148
|
-
lower === 'stakeholders' ||
|
|
149
|
-
lower === 'definition of done');
|
|
150
|
-
};
|
|
151
|
-
for (const rawLine of lines) {
|
|
152
|
-
const line = rawLine.trimRight();
|
|
153
|
-
const trimmed = line.trim();
|
|
154
|
-
// Jira wiki-style headings often come through as: "h3. Problem statement"
|
|
155
|
-
const m = trimmed.match(/^h3\.\s*(.+)$/i);
|
|
156
|
-
if (m?.[1]) {
|
|
157
|
-
flush();
|
|
158
|
-
currentTitle = m[1].trim();
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
// After HTML stripping, rendered headings may be left as plain lines like "Problem statement"
|
|
162
|
-
// Treat them as headings when they match a known template title.
|
|
163
|
-
if (isKnownHeading(trimmed)) {
|
|
164
|
-
flush();
|
|
165
|
-
currentTitle = trimmed;
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (!currentTitle) {
|
|
169
|
-
// ignore leading preamble until we hit the first known heading
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
currentBody.push(line);
|
|
173
|
-
}
|
|
174
|
-
flush();
|
|
175
|
-
return sections;
|
|
176
|
-
};
|
|
177
|
-
const renderBulletsOrText = (text) => {
|
|
178
|
-
const cleaned = stripHtmlTags(text || '');
|
|
179
|
-
if (!cleaned)
|
|
180
|
-
return (React.createElement("div", { style: { fontSize: '0.875rem', color: 'var(--pf-t--global--text--color--subtle)' } }, "\u2014"));
|
|
181
|
-
const lines = cleaned
|
|
182
|
-
.split('\n')
|
|
183
|
-
.map((l) => l.trim())
|
|
184
|
-
.filter(Boolean);
|
|
185
|
-
if (lines.length === 0) {
|
|
186
|
-
return React.createElement("div", { style: { fontSize: '0.875rem', whiteSpace: 'pre-wrap' } }, cleaned);
|
|
187
|
-
}
|
|
188
|
-
const bulletLines = lines.filter((l) => /^(\*{1,2}\s+|[-•]\s+)/.test(l));
|
|
189
|
-
const hasBullets = bulletLines.length > 0;
|
|
190
|
-
if (!hasBullets) {
|
|
191
|
-
return React.createElement("div", { style: { fontSize: '0.875rem', whiteSpace: 'pre-wrap' } }, cleaned);
|
|
192
|
-
}
|
|
193
|
-
const items = lines
|
|
194
|
-
.map((l) => l.replace(/^(\*{1,2}\s+|[-•]\s+)/, '').trim())
|
|
195
|
-
.filter(Boolean);
|
|
196
|
-
return (React.createElement("ul", { style: { margin: 0, paddingLeft: '1.25rem', display: 'grid', gap: '0.5rem' } }, items.map((item, idx) => (
|
|
197
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
198
|
-
React.createElement("li", { key: idx, style: { fontSize: '0.875rem' } }, item)))));
|
|
199
|
-
};
|
|
200
|
-
const JiraTab = () => {
|
|
201
|
-
const location = (0, react_router_dom_1.useLocation)();
|
|
202
|
-
const route = normalizePathname(location.pathname);
|
|
203
|
-
const sectionRoute = getSectionRoute(route);
|
|
204
|
-
const [{ record, source }, setResolved] = React.useState(() => loadForRoute(route));
|
|
205
|
-
const [isEditing, setIsEditing] = React.useState(false);
|
|
206
|
-
const [draftScope, setDraftScope] = React.useState('section');
|
|
207
|
-
const [draftKey, setDraftKey] = React.useState('');
|
|
208
|
-
const [isLoadingRemote, setIsLoadingRemote] = React.useState(false);
|
|
209
|
-
const [remoteError, setRemoteError] = React.useState(null);
|
|
210
|
-
const remoteShaRef = React.useRef(undefined);
|
|
211
|
-
const [isFetchingIssue, setIsFetchingIssue] = React.useState(false);
|
|
212
|
-
const [issueError, setIssueError] = React.useState(null);
|
|
213
|
-
const [issue, setIssue] = React.useState(null);
|
|
214
|
-
React.useEffect(() => {
|
|
215
|
-
setResolved(loadForRoute(route));
|
|
216
|
-
setIsEditing(false);
|
|
217
|
-
}, [route]);
|
|
218
|
-
// Load Jira store from GitHub if configured.
|
|
219
|
-
React.useEffect(() => {
|
|
220
|
-
const load = async () => {
|
|
221
|
-
if (!(0, githubAdapter_1.isGitHubConfigured)())
|
|
222
|
-
return;
|
|
223
|
-
setIsLoadingRemote(true);
|
|
224
|
-
setRemoteError(null);
|
|
225
|
-
try {
|
|
226
|
-
const local = getStore();
|
|
227
|
-
const res = await githubAdapter_1.githubAdapter.getRepoFile(GH_JIRA_PATH);
|
|
228
|
-
if (!res.success) {
|
|
229
|
-
setRemoteError(res.error || 'Failed to load Jira store from GitHub');
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (!res.data) {
|
|
233
|
-
// No remote file yet: initialize from local if we have anything.
|
|
234
|
-
if (Object.keys(local).length > 0) {
|
|
235
|
-
const created = await githubAdapter_1.githubAdapter.putRepoFile({
|
|
236
|
-
path: GH_JIRA_PATH,
|
|
237
|
-
text: JSON.stringify(local, null, 2) + '\n',
|
|
238
|
-
message: 'chore(jira): initialize jira store',
|
|
239
|
-
});
|
|
240
|
-
if (created.success)
|
|
241
|
-
remoteShaRef.current = created.data?.sha;
|
|
242
|
-
}
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
remoteShaRef.current = res.data.sha;
|
|
246
|
-
const parsed = safeParseStore(res.data.text);
|
|
247
|
-
setStore(parsed);
|
|
248
|
-
setResolved(loadForRoute(route));
|
|
249
|
-
}
|
|
250
|
-
finally {
|
|
251
|
-
setIsLoadingRemote(false);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
void load();
|
|
255
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
256
|
-
}, []);
|
|
257
|
-
// Fetch Jira issue details for the effective key.
|
|
258
|
-
React.useEffect(() => {
|
|
259
|
-
const key = record?.jiraKey?.trim();
|
|
260
|
-
if (!key) {
|
|
261
|
-
setIssue(null);
|
|
262
|
-
setIssueError(null);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const run = async () => {
|
|
266
|
-
setIsFetchingIssue(true);
|
|
267
|
-
setIssueError(null);
|
|
268
|
-
try {
|
|
269
|
-
const resp = await fetch(`/api/jira-issue?key=${encodeURIComponent(key)}`);
|
|
270
|
-
const payload = await resp.json().catch(() => ({}));
|
|
271
|
-
if (!resp.ok) {
|
|
272
|
-
setIssue(null);
|
|
273
|
-
const raw = String(payload?.message || `Failed to fetch Jira issue (${resp.status})`);
|
|
274
|
-
const sanitized = raw.trim().startsWith('<') ? 'Unauthorized or non-JSON response from Jira.' : raw;
|
|
275
|
-
const hint = payload?.hint ? ` ${String(payload.hint)}` : '';
|
|
276
|
-
setIssueError(`${sanitized}${hint}`);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
setIssue(payload);
|
|
280
|
-
}
|
|
281
|
-
catch (e) {
|
|
282
|
-
setIssue(null);
|
|
283
|
-
setIssueError(e?.message || 'Failed to fetch Jira issue');
|
|
284
|
-
}
|
|
285
|
-
finally {
|
|
286
|
-
setIsFetchingIssue(false);
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
void run();
|
|
290
|
-
}, [record?.jiraKey]);
|
|
291
|
-
const startNew = () => {
|
|
292
|
-
setDraftScope('section');
|
|
293
|
-
setDraftKey('');
|
|
294
|
-
setIsEditing(true);
|
|
295
|
-
};
|
|
296
|
-
const startEdit = (mode) => {
|
|
297
|
-
if (mode === 'override-page') {
|
|
298
|
-
setDraftScope('page');
|
|
299
|
-
setDraftKey(record?.jiraKey ?? '');
|
|
300
|
-
setIsEditing(true);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const existingScope = record?.scope ?? 'section';
|
|
304
|
-
setDraftScope(existingScope);
|
|
305
|
-
setDraftKey(record?.jiraKey ?? '');
|
|
306
|
-
setIsEditing(true);
|
|
307
|
-
};
|
|
308
|
-
const save = () => {
|
|
309
|
-
const next = {
|
|
310
|
-
jiraKey: normalizeJiraKey(draftKey),
|
|
311
|
-
scope: draftScope,
|
|
312
|
-
anchorRoute: draftScope === 'section' ? sectionRoute : route,
|
|
313
|
-
updatedAt: new Date().toISOString(),
|
|
314
|
-
};
|
|
315
|
-
const store = getStore();
|
|
316
|
-
const key = draftScope === 'section' ? getSectionKey(sectionRoute) : getPageKey(route);
|
|
317
|
-
const nextStore = { ...store, [key]: next };
|
|
318
|
-
setStore(nextStore);
|
|
319
|
-
setResolved(loadForRoute(route));
|
|
320
|
-
setIsEditing(false);
|
|
321
|
-
if ((0, githubAdapter_1.isGitHubConfigured)()) {
|
|
322
|
-
(async () => {
|
|
323
|
-
const text = JSON.stringify(nextStore, null, 2) + '\n';
|
|
324
|
-
const message = `chore(jira): update ${key}`;
|
|
325
|
-
const sha = remoteShaRef.current;
|
|
326
|
-
const write = await githubAdapter_1.githubAdapter.putRepoFile({ path: GH_JIRA_PATH, text, message, sha });
|
|
327
|
-
if (write.success && write.data?.sha) {
|
|
328
|
-
remoteShaRef.current = write.data.sha;
|
|
329
|
-
setRemoteError(null);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const refreshed = await githubAdapter_1.githubAdapter.getRepoFile(GH_JIRA_PATH);
|
|
333
|
-
if (refreshed.success && refreshed.data?.sha) {
|
|
334
|
-
remoteShaRef.current = refreshed.data.sha;
|
|
335
|
-
const retry = await githubAdapter_1.githubAdapter.putRepoFile({
|
|
336
|
-
path: GH_JIRA_PATH,
|
|
337
|
-
text,
|
|
338
|
-
message,
|
|
339
|
-
sha: refreshed.data.sha,
|
|
340
|
-
});
|
|
341
|
-
if (retry.success && retry.data?.sha) {
|
|
342
|
-
remoteShaRef.current = retry.data.sha;
|
|
343
|
-
setRemoteError(null);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
setRemoteError(write.error || 'Failed to save Jira store to GitHub');
|
|
348
|
-
})();
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
const remove = () => {
|
|
352
|
-
const store = getStore();
|
|
353
|
-
const keyToRemove = source === 'page' ? getPageKey(route) : source === 'section' ? getSectionKey(sectionRoute) : null;
|
|
354
|
-
if (!keyToRemove)
|
|
355
|
-
return;
|
|
356
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
357
|
-
const { [keyToRemove]: _removed, ...rest } = store;
|
|
358
|
-
setStore(rest);
|
|
359
|
-
setResolved(loadForRoute(route));
|
|
360
|
-
setIsEditing(false);
|
|
361
|
-
if ((0, githubAdapter_1.isGitHubConfigured)()) {
|
|
362
|
-
(async () => {
|
|
363
|
-
const text = JSON.stringify(rest, null, 2) + '\n';
|
|
364
|
-
const message = `chore(jira): remove ${keyToRemove}`;
|
|
365
|
-
const sha = remoteShaRef.current;
|
|
366
|
-
const write = await githubAdapter_1.githubAdapter.putRepoFile({ path: GH_JIRA_PATH, text, message, sha });
|
|
367
|
-
if (write.success && write.data?.sha) {
|
|
368
|
-
remoteShaRef.current = write.data.sha;
|
|
369
|
-
setRemoteError(null);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
setRemoteError(write.error || 'Failed to update Jira store in GitHub');
|
|
373
|
-
})();
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
const remoteStatusLine = (0, githubAdapter_1.isGitHubConfigured)()
|
|
377
|
-
? isLoadingRemote
|
|
378
|
-
? 'Loading Jira store from GitHub…'
|
|
379
|
-
: remoteError
|
|
380
|
-
? `GitHub sync: ${remoteError}`
|
|
381
|
-
: 'GitHub sync enabled'
|
|
382
|
-
: null;
|
|
383
|
-
const isInherited = source === 'section' && record?.anchorRoute !== route;
|
|
384
|
-
if (!record && !isEditing) {
|
|
385
|
-
return (React.createElement("div", { style: { display: 'grid', gap: '1rem' } },
|
|
386
|
-
React.createElement("div", { style: { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '1rem' } },
|
|
387
|
-
React.createElement("div", null,
|
|
388
|
-
React.createElement(react_core_1.Title, { headingLevel: "h3", size: "lg" }, "Jira"),
|
|
389
|
-
React.createElement("div", { style: { fontSize: '0.875rem', color: 'var(--pf-t--global--text--color--subtle)' } },
|
|
390
|
-
"No Jira issue set for ",
|
|
391
|
-
React.createElement("b", null, route),
|
|
392
|
-
"."),
|
|
393
|
-
remoteStatusLine && (React.createElement("div", { style: { fontSize: '0.75rem', color: 'var(--pf-t--global--text--color--subtle)', marginTop: '0.25rem' } }, remoteStatusLine))),
|
|
394
|
-
React.createElement(react_core_1.Button, { variant: "primary", onClick: startNew }, "Add Jira issue")),
|
|
395
|
-
React.createElement(react_core_1.EmptyState, { icon: react_icons_1.InfoCircleIcon, titleText: "No Jira issue linked", headingLevel: "h3" },
|
|
396
|
-
React.createElement(react_core_1.EmptyStateBody, null,
|
|
397
|
-
"Add a Jira key like ",
|
|
398
|
-
React.createElement("b", null, "ABC-123"),
|
|
399
|
-
" (or paste a Jira URL)."))));
|
|
400
|
-
}
|
|
401
|
-
if (isEditing) {
|
|
402
|
-
const effectiveAnchor = draftScope === 'section' ? `${sectionRoute}/*` : route;
|
|
403
|
-
return (React.createElement("div", { style: { display: 'grid', gap: '1rem' } },
|
|
404
|
-
React.createElement("div", { style: { display: 'grid', gap: '0.5rem' } },
|
|
405
|
-
React.createElement("div", null,
|
|
406
|
-
React.createElement(react_core_1.Title, { headingLevel: "h3", size: "lg" }, "Edit Jira"),
|
|
407
|
-
React.createElement("div", { style: { fontSize: '0.875rem', color: 'var(--pf-t--global--text--color--subtle)' } },
|
|
408
|
-
"Applies to: ",
|
|
409
|
-
React.createElement("b", null, effectiveAnchor)),
|
|
410
|
-
remoteStatusLine && (React.createElement("div", { style: { fontSize: '0.75rem', color: 'var(--pf-t--global--text--color--subtle)', marginTop: '0.25rem' } }, remoteStatusLine))),
|
|
411
|
-
React.createElement("div", { style: { display: 'flex', gap: '0.5rem', flexWrap: 'wrap' } },
|
|
412
|
-
React.createElement(react_core_1.Button, { variant: draftScope === 'page' ? 'primary' : 'secondary', onClick: () => setDraftScope('page') }, "This page only"),
|
|
413
|
-
React.createElement(react_core_1.Button, { variant: draftScope === 'section' ? 'primary' : 'secondary', onClick: () => setDraftScope('section') }, "This section"))),
|
|
414
|
-
React.createElement(react_core_1.Card, null,
|
|
415
|
-
React.createElement(react_core_1.CardBody, null,
|
|
416
|
-
React.createElement(react_core_1.Title, { headingLevel: "h4", size: "md", style: { marginBottom: '1rem' } }, "Jira issue"),
|
|
417
|
-
React.createElement("div", { style: { display: 'grid', gap: '0.75rem' } },
|
|
418
|
-
React.createElement("div", null,
|
|
419
|
-
React.createElement("div", { style: { fontSize: '0.875rem', marginBottom: '0.25rem' } },
|
|
420
|
-
React.createElement("b", null, "Jira key or URL")),
|
|
421
|
-
React.createElement(react_core_1.TextArea, { value: draftKey, onChange: (_e, v) => setDraftKey(v), "aria-label": "Jira key or URL", rows: 1 })),
|
|
422
|
-
React.createElement("div", { style: { display: 'flex', gap: '8px', justifyContent: 'flex-start', marginTop: '0.5rem' } },
|
|
423
|
-
React.createElement(react_core_1.Button, { variant: "primary", onClick: save, isDisabled: !normalizeJiraKey(draftKey) }, "Save"),
|
|
424
|
-
React.createElement(react_core_1.Button, { variant: "link", onClick: () => setIsEditing(false) }, "Cancel")))))));
|
|
425
|
-
}
|
|
426
|
-
// View mode
|
|
427
|
-
if (!record) {
|
|
428
|
-
return null;
|
|
429
|
-
}
|
|
430
|
-
const scopeLabel = source === 'page' ? 'This page' : source === 'section' ? `Section (${sectionRoute}/*)` : null;
|
|
431
|
-
const key = record.jiraKey || '';
|
|
432
|
-
const url = issue?.url || (process.env.VITE_JIRA_BASE_URL ? `${process.env.VITE_JIRA_BASE_URL}/browse/${key}` : '');
|
|
433
|
-
const parsedSections = parseJiraTemplateSections(issue?.description || '');
|
|
434
|
-
const byTitle = new Map(parsedSections.map((s) => [s.title, s.body]));
|
|
435
|
-
const summary = issue?.summary || '';
|
|
436
|
-
const status = issue?.status || '';
|
|
437
|
-
const priority = issue?.priority || '';
|
|
438
|
-
const assignee = issue?.assignee || '';
|
|
439
|
-
const issueType = issue?.issueType || 'Issue';
|
|
440
|
-
const created = issue?.created ? new Date(issue.created).toLocaleString() : '';
|
|
441
|
-
const updated = issue?.updated ? new Date(issue.updated).toLocaleString() : '';
|
|
442
|
-
return (React.createElement("div", { style: { display: 'grid', gap: '1rem' } },
|
|
443
|
-
React.createElement("div", { style: { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '1rem' } },
|
|
444
|
-
React.createElement("div", null,
|
|
445
|
-
React.createElement(react_core_1.Title, { headingLevel: "h3", size: "lg" }, "Jira"),
|
|
446
|
-
scopeLabel && (React.createElement("div", { style: { fontSize: '0.875rem', color: 'var(--pf-t--global--text--color--subtle)' } },
|
|
447
|
-
"Scope: ",
|
|
448
|
-
React.createElement("b", null, scopeLabel),
|
|
449
|
-
source === 'section' ? ` (applies to ${record.anchorRoute}/*)` : '',
|
|
450
|
-
isInherited ? ` (inherited)` : '')),
|
|
451
|
-
remoteStatusLine && (React.createElement("div", { style: { fontSize: '0.75rem', color: 'var(--pf-t--global--text--color--subtle)', marginTop: '0.25rem' } }, remoteStatusLine))),
|
|
452
|
-
React.createElement("div", { style: { display: 'flex', gap: '8px' } },
|
|
453
|
-
isInherited && (React.createElement(react_core_1.Button, { variant: "secondary", onClick: () => startEdit('override-page') }, "Override for this page")),
|
|
454
|
-
React.createElement(react_core_1.Button, { variant: "secondary", onClick: () => startEdit('edit-existing') }, "Edit"))),
|
|
455
|
-
React.createElement(react_core_1.Card, null,
|
|
456
|
-
React.createElement(react_core_1.CardBody, null,
|
|
457
|
-
React.createElement("div", { style: { display: 'grid', gap: '1rem' } },
|
|
458
|
-
React.createElement("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' } },
|
|
459
|
-
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' } },
|
|
460
|
-
React.createElement(react_core_1.Label, { color: "blue", isCompact: true }, issueType || 'Issue'),
|
|
461
|
-
url ? (React.createElement("a", { href: url, target: "_blank", rel: "noopener noreferrer", style: { display: 'inline-flex', alignItems: 'center', gap: '0.25rem', fontWeight: 600 } },
|
|
462
|
-
key,
|
|
463
|
-
" ",
|
|
464
|
-
React.createElement(react_icons_1.ExternalLinkAltIcon, { style: { fontSize: '0.75rem' } }))) : (React.createElement("span", { style: { fontWeight: 600 } }, key)))),
|
|
465
|
-
isFetchingIssue ? (React.createElement("div", { style: { display: 'inline-flex', alignItems: 'center', gap: '0.5rem' } },
|
|
466
|
-
React.createElement(react_core_1.Spinner, { size: "sm" }),
|
|
467
|
-
" ",
|
|
468
|
-
React.createElement("span", null, "Fetching Jira details\u2026"))) : issueError ? (React.createElement("div", { style: { fontSize: '0.875rem', color: 'var(--pf-t--global--danger--color--100)' } }, issueError)) : (React.createElement(React.Fragment, null,
|
|
469
|
-
React.createElement(react_core_1.Title, { headingLevel: "h3", size: "lg", style: { marginTop: '0.25rem' } }, summary || '—'),
|
|
470
|
-
React.createElement("div", { style: { display: 'flex', gap: '0.5rem', flexWrap: 'wrap', marginTop: '0.25rem' } },
|
|
471
|
-
React.createElement(react_core_1.Label, { color: "grey", isCompact: true },
|
|
472
|
-
"Status: ",
|
|
473
|
-
status || '—'),
|
|
474
|
-
React.createElement(react_core_1.Label, { color: "orange", isCompact: true },
|
|
475
|
-
"Priority: ",
|
|
476
|
-
priority || '—'),
|
|
477
|
-
React.createElement(react_core_1.Label, { color: "grey", isCompact: true },
|
|
478
|
-
"Assignee: ",
|
|
479
|
-
assignee || '—')),
|
|
480
|
-
React.createElement("div", { style: { display: 'flex', gap: '1rem', flexWrap: 'wrap', fontSize: '0.875rem', color: 'var(--pf-t--global--text--color--subtle)' } },
|
|
481
|
-
created && (React.createElement("span", null,
|
|
482
|
-
React.createElement("b", null, "Created:"),
|
|
483
|
-
" ",
|
|
484
|
-
created)),
|
|
485
|
-
updated && (React.createElement("span", null,
|
|
486
|
-
React.createElement("b", null, "Updated:"),
|
|
487
|
-
" ",
|
|
488
|
-
updated))),
|
|
489
|
-
React.createElement("div", { style: { height: 1, background: 'var(--pf-t--global--border--color--default)', marginTop: '0.25rem' } }),
|
|
490
|
-
parsedSections.length > 0 ? (React.createElement("div", { style: { display: 'grid', gap: '1rem' } },
|
|
491
|
-
React.createElement("div", null,
|
|
492
|
-
React.createElement(react_core_1.Title, { headingLevel: "h4", size: "md", style: { marginBottom: '0.5rem' } }, "Problem statement"),
|
|
493
|
-
renderBulletsOrText(byTitle.get('Problem statement') || '')),
|
|
494
|
-
React.createElement("div", null,
|
|
495
|
-
React.createElement(react_core_1.Title, { headingLevel: "h4", size: "md", style: { marginBottom: '0.5rem' } }, "Objective"),
|
|
496
|
-
renderBulletsOrText(byTitle.get('Objective') || '')),
|
|
497
|
-
React.createElement("div", null,
|
|
498
|
-
React.createElement(react_core_1.Title, { headingLevel: "h4", size: "md", style: { marginBottom: '0.5rem' } }, "Definition of Done"),
|
|
499
|
-
renderBulletsOrText(byTitle.get('Definition of Done') || '')))) : (
|
|
500
|
-
// Fallback: show the raw description if it doesn't follow the template.
|
|
501
|
-
React.createElement("div", null,
|
|
502
|
-
React.createElement(react_core_1.Title, { headingLevel: "h4", size: "md", style: { marginBottom: '0.5rem' } }, "Description"),
|
|
503
|
-
React.createElement("div", { style: { fontSize: '0.875rem', whiteSpace: 'pre-wrap' } }, issue?.description ? stripHtmlTags(issue.description) : (React.createElement("span", { style: { color: 'var(--pf-t--global--text--color--subtle)' } }, "No description"))))))),
|
|
504
|
-
React.createElement("div", { style: { marginTop: '0.25rem' } },
|
|
505
|
-
React.createElement(react_core_1.Button, { variant: "link", isDanger: true, onClick: remove }, "Remove Jira link")))))));
|
|
506
|
-
};
|
|
507
|
-
exports.JiraTab = JiraTab;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Thread } from '../types';
|
|
3
|
-
interface CommentContextType {
|
|
4
|
-
threads: Thread[];
|
|
5
|
-
commentsEnabled: boolean;
|
|
6
|
-
setCommentsEnabled: (enabled: boolean) => void;
|
|
7
|
-
drawerPinnedOpen: boolean;
|
|
8
|
-
setDrawerPinnedOpen: (open: boolean) => void;
|
|
9
|
-
floatingWidgetMode: boolean;
|
|
10
|
-
setFloatingWidgetMode: (mode: boolean) => void;
|
|
11
|
-
addThread: (xPercent: number, yPercent: number, route: string, version?: string) => string;
|
|
12
|
-
addReply: (threadId: string, text: string, parentCommentId?: string) => void;
|
|
13
|
-
syncFromGitHub: (route: string, version?: string) => Promise<void>;
|
|
14
|
-
retrySync: () => Promise<void>;
|
|
15
|
-
isSyncing: boolean;
|
|
16
|
-
hasPendingSync: boolean;
|
|
17
|
-
updateComment: (threadId: string, commentId: string, text: string) => void;
|
|
18
|
-
deleteComment: (threadId: string, commentId: string) => void;
|
|
19
|
-
closeThread: (threadId: string) => void;
|
|
20
|
-
reopenThread: (threadId: string) => void;
|
|
21
|
-
removePin: (threadId: string) => void;
|
|
22
|
-
getThreadsForRoute: (route: string, version?: string) => Thread[];
|
|
23
|
-
selectedThreadId: string | null;
|
|
24
|
-
setSelectedThreadId: (threadId: string | null) => void;
|
|
25
|
-
}
|
|
26
|
-
export declare const CommentProvider: React.FunctionComponent<{
|
|
27
|
-
children: React.ReactNode;
|
|
28
|
-
}>;
|
|
29
|
-
export declare const useComments: () => CommentContextType;
|
|
30
|
-
export {};
|