hale-commenting-system 2.0.3 → 2.1.0
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 +16 -207
- package/bin/detect.d.ts +10 -0
- package/bin/detect.js +134 -0
- package/bin/generators.d.ts +18 -0
- package/bin/generators.js +193 -0
- package/bin/hale-commenting.js +4 -0
- package/bin/index.d.ts +2 -0
- package/bin/index.js +61 -0
- package/bin/onboarding.d.ts +1 -0
- package/bin/onboarding.js +344 -0
- package/bin/postinstall.d.ts +2 -0
- package/bin/postinstall.js +65 -0
- package/bin/validators.d.ts +2 -0
- package/bin/validators.js +66 -0
- package/dist/cli/detect.d.ts +10 -0
- package/dist/cli/detect.js +134 -0
- package/dist/cli/generators.d.ts +18 -0
- package/dist/cli/generators.js +193 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +61 -0
- package/dist/cli/onboarding.d.ts +1 -0
- package/dist/cli/onboarding.js +344 -0
- package/dist/cli/postinstall.d.ts +2 -0
- package/dist/cli/postinstall.js +65 -0
- package/dist/cli/validators.d.ts +2 -0
- package/dist/cli/validators.js +66 -0
- package/dist/components/CommentOverlay.d.ts +2 -0
- package/dist/components/CommentOverlay.js +101 -0
- package/dist/components/CommentPanel.d.ts +6 -0
- package/dist/components/CommentPanel.js +334 -0
- package/dist/components/CommentPin.d.ts +11 -0
- package/dist/components/CommentPin.js +64 -0
- package/dist/components/DetailsTab.d.ts +2 -0
- package/dist/components/DetailsTab.js +380 -0
- package/dist/components/FloatingWidget.d.ts +8 -0
- package/dist/components/FloatingWidget.js +128 -0
- package/dist/components/JiraTab.d.ts +2 -0
- package/dist/components/JiraTab.js +507 -0
- package/dist/contexts/CommentContext.d.ts +30 -0
- package/dist/contexts/CommentContext.js +891 -0
- package/dist/contexts/GitHubAuthContext.d.ts +13 -0
- package/dist/contexts/GitHubAuthContext.js +96 -0
- package/dist/index.d.ts +10 -97
- package/dist/index.js +26 -786
- package/dist/services/githubAdapter.d.ts +56 -0
- package/dist/services/githubAdapter.js +321 -0
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.js +23 -0
- package/package.json +39 -38
- package/templates/webpack-middleware.js +226 -0
- package/cli/dist/index.js +0 -370
- package/cli/dist/index.js.map +0 -1
- package/dist/index.d.mts +0 -97
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -759
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateGitHubCredentials = validateGitHubCredentials;
|
|
7
|
+
exports.validateJiraCredentials = validateJiraCredentials;
|
|
8
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
|
+
async function validateGitHubCredentials(clientId, clientSecret, owner, repo) {
|
|
10
|
+
try {
|
|
11
|
+
// Test OAuth app credentials by attempting token exchange (we'll use a dummy code)
|
|
12
|
+
// Actually, we can't test OAuth without a real callback, so we'll just check repo access
|
|
13
|
+
// For a real validation, we'd need the user to complete OAuth flow, but for onboarding
|
|
14
|
+
// we'll just verify the repo exists and is accessible
|
|
15
|
+
const repoUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
|
16
|
+
const response = await (0, node_fetch_1.default)(repoUrl, {
|
|
17
|
+
headers: {
|
|
18
|
+
'Accept': 'application/vnd.github+json',
|
|
19
|
+
'User-Agent': 'hale-commenting-system'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
if (response.ok) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (response.status === 404) {
|
|
26
|
+
console.error(` Repository ${owner}/${repo} not found or not accessible`);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
console.error(` GitHub API error: ${response.status}`);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error(` Error validating GitHub: ${error.message}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function validateJiraCredentials(baseUrl, apiToken, email) {
|
|
38
|
+
try {
|
|
39
|
+
const url = `${baseUrl.replace(/\/+$/, '')}/rest/api/2/myself`;
|
|
40
|
+
const authHeader = email
|
|
41
|
+
? `Basic ${Buffer.from(`${email}:${apiToken}`).toString('base64')}`
|
|
42
|
+
: `Bearer ${apiToken}`;
|
|
43
|
+
const response = await (0, node_fetch_1.default)(url, {
|
|
44
|
+
headers: {
|
|
45
|
+
'Accept': 'application/json',
|
|
46
|
+
'Authorization': authHeader,
|
|
47
|
+
'User-Agent': 'hale-commenting-system'
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (response.ok) {
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
console.log(` ✅ Authenticated as: ${data.displayName || data.name || 'User'}`);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (response.status === 401 || response.status === 403) {
|
|
56
|
+
console.error(` Authentication failed. Check your token and email (if required).`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
console.error(` Jira API error: ${response.status}`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error(` Error validating Jira: ${error.message}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
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.CommentOverlay = void 0;
|
|
37
|
+
const React = __importStar(require("react"));
|
|
38
|
+
const react_router_dom_1 = require("react-router-dom");
|
|
39
|
+
const CommentContext_1 = require("../contexts/CommentContext");
|
|
40
|
+
const CommentPin_1 = require("./CommentPin");
|
|
41
|
+
const version_1 = require("../utils/version");
|
|
42
|
+
const CommentOverlay = () => {
|
|
43
|
+
const location = (0, react_router_dom_1.useLocation)();
|
|
44
|
+
const { commentsEnabled, addThread, selectedThreadId, setSelectedThreadId, syncFromGitHub, getThreadsForRoute } = (0, CommentContext_1.useComments)();
|
|
45
|
+
const detectedVersion = (0, version_1.getVersionFromPathOrQuery)(location.pathname, location.search);
|
|
46
|
+
const overlayRef = React.useRef(null);
|
|
47
|
+
// Show both open and closed threads as pins (GitHub-style: closed issues still exist)
|
|
48
|
+
const currentThreads = getThreadsForRoute(location.pathname, detectedVersion);
|
|
49
|
+
const handlePageClick = (e) => {
|
|
50
|
+
if (!commentsEnabled)
|
|
51
|
+
return;
|
|
52
|
+
// Check if clicking on a pin or any interactive element
|
|
53
|
+
const target = e.target;
|
|
54
|
+
if (target.closest('button') ||
|
|
55
|
+
target.closest('a') ||
|
|
56
|
+
target.closest('input') ||
|
|
57
|
+
target.closest('select') ||
|
|
58
|
+
target.closest('textarea') ||
|
|
59
|
+
target.closest('[role="button"]') ||
|
|
60
|
+
target.closest('[data-comment-controls]') ||
|
|
61
|
+
target.closest('[data-comment-pin]')) {
|
|
62
|
+
return; // Don't create pin if clicking interactive elements
|
|
63
|
+
}
|
|
64
|
+
// Get the overlay container dimensions (accounts for drawer being open)
|
|
65
|
+
if (!overlayRef.current)
|
|
66
|
+
return;
|
|
67
|
+
const rect = overlayRef.current.getBoundingClientRect();
|
|
68
|
+
// Calculate percentage based on the content area, not the full window
|
|
69
|
+
const xPercent = ((e.clientX - rect.left) / rect.width) * 100;
|
|
70
|
+
const yPercent = ((e.clientY - rect.top) / rect.height) * 100;
|
|
71
|
+
const threadId = addThread(xPercent, yPercent, location.pathname, detectedVersion);
|
|
72
|
+
setSelectedThreadId(threadId);
|
|
73
|
+
};
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
console.log('🔄 CommentOverlay useEffect triggered', { commentsEnabled, pathname: location.pathname, detectedVersion });
|
|
76
|
+
if (commentsEnabled) {
|
|
77
|
+
document.addEventListener('click', handlePageClick);
|
|
78
|
+
// Pull latest changes from GitHub when entering comment mode or switching routes
|
|
79
|
+
console.log('🔄 CommentOverlay calling syncFromGitHub...');
|
|
80
|
+
syncFromGitHub(location.pathname, detectedVersion).catch(() => undefined);
|
|
81
|
+
}
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener('click', handlePageClick);
|
|
84
|
+
};
|
|
85
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
+
}, [commentsEnabled, location.pathname, detectedVersion]);
|
|
87
|
+
// Only show pins when commenting is enabled
|
|
88
|
+
if (!commentsEnabled) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return (React.createElement("div", { ref: overlayRef, style: {
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
top: 0,
|
|
94
|
+
left: 0,
|
|
95
|
+
width: '100%',
|
|
96
|
+
height: '100%',
|
|
97
|
+
pointerEvents: 'none',
|
|
98
|
+
zIndex: 999,
|
|
99
|
+
} }, currentThreads.map((thread) => (React.createElement(CommentPin_1.CommentPin, { key: thread.id, xPercent: thread.xPercent, yPercent: thread.yPercent, commentCount: thread.comments.length, isClosed: thread.status === 'closed', isSelected: selectedThreadId === thread.id, onClick: () => setSelectedThreadId(thread.id) })))));
|
|
100
|
+
};
|
|
101
|
+
exports.CommentOverlay = CommentOverlay;
|
|
@@ -0,0 +1,334 @@
|
|
|
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.CommentPanel = 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 CommentContext_1 = require("../contexts/CommentContext");
|
|
42
|
+
const DetailsTab_1 = require("./DetailsTab");
|
|
43
|
+
const JiraTab_1 = require("./JiraTab");
|
|
44
|
+
const FloatingWidget_1 = require("./FloatingWidget");
|
|
45
|
+
const version_1 = require("../utils/version");
|
|
46
|
+
const CommentPanel = ({ children }) => {
|
|
47
|
+
const { getThreadsForRoute, selectedThreadId, setSelectedThreadId, drawerPinnedOpen, setDrawerPinnedOpen, floatingWidgetMode, setFloatingWidgetMode, addReply, updateComment, deleteComment, closeThread, reopenThread, removePin, retrySync, } = (0, CommentContext_1.useComments)();
|
|
48
|
+
const location = (0, react_router_dom_1.useLocation)();
|
|
49
|
+
const detectedVersion = (0, version_1.getVersionFromPathOrQuery)(location.pathname, location.search);
|
|
50
|
+
const [newCommentText, setNewCommentText] = React.useState('');
|
|
51
|
+
const [replyingToCommentId, setReplyingToCommentId] = React.useState(null);
|
|
52
|
+
const [replyTextByCommentId, setReplyTextByCommentId] = React.useState({});
|
|
53
|
+
const [editingCommentId, setEditingCommentId] = React.useState(null);
|
|
54
|
+
const [editText, setEditText] = React.useState('');
|
|
55
|
+
const drawerRef = React.useRef(null);
|
|
56
|
+
const [activeTabKey, setActiveTabKey] = React.useState('comments');
|
|
57
|
+
const currentThreads = getThreadsForRoute(location.pathname, detectedVersion);
|
|
58
|
+
const selectedThread = currentThreads.find((t) => t.id === selectedThreadId);
|
|
59
|
+
const isExpanded = !!selectedThreadId || drawerPinnedOpen || floatingWidgetMode;
|
|
60
|
+
const onExpand = () => {
|
|
61
|
+
drawerRef.current && drawerRef.current.focus();
|
|
62
|
+
};
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
if (selectedThreadId) {
|
|
65
|
+
setActiveTabKey('comments');
|
|
66
|
+
}
|
|
67
|
+
}, [selectedThreadId]);
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
if (drawerPinnedOpen && !selectedThreadId) {
|
|
70
|
+
setActiveTabKey('details');
|
|
71
|
+
}
|
|
72
|
+
}, [drawerPinnedOpen, selectedThreadId]);
|
|
73
|
+
const handleAddComment = () => {
|
|
74
|
+
if (newCommentText.trim() && selectedThread) {
|
|
75
|
+
addReply(selectedThread.id, newCommentText.trim());
|
|
76
|
+
setNewCommentText('');
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const handleStartReply = (commentId) => {
|
|
80
|
+
setReplyingToCommentId(commentId);
|
|
81
|
+
setReplyTextByCommentId((prev) => ({ ...prev, [commentId]: prev[commentId] ?? '' }));
|
|
82
|
+
};
|
|
83
|
+
const handleCancelReply = () => {
|
|
84
|
+
setReplyingToCommentId(null);
|
|
85
|
+
};
|
|
86
|
+
const handleSubmitReply = (parentCommentId) => {
|
|
87
|
+
if (!selectedThread)
|
|
88
|
+
return;
|
|
89
|
+
const text = (replyTextByCommentId[parentCommentId] || '').trim();
|
|
90
|
+
if (!text)
|
|
91
|
+
return;
|
|
92
|
+
addReply(selectedThread.id, text, parentCommentId);
|
|
93
|
+
setReplyTextByCommentId((prev) => ({ ...prev, [parentCommentId]: '' }));
|
|
94
|
+
setReplyingToCommentId(null);
|
|
95
|
+
};
|
|
96
|
+
const handleStartEdit = (commentId, currentText) => {
|
|
97
|
+
setEditingCommentId(commentId);
|
|
98
|
+
setEditText(currentText);
|
|
99
|
+
};
|
|
100
|
+
const handleSaveEdit = (commentId) => {
|
|
101
|
+
if (editText.trim() && selectedThread) {
|
|
102
|
+
updateComment(selectedThread.id, commentId, editText.trim());
|
|
103
|
+
setEditingCommentId(null);
|
|
104
|
+
setEditText('');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const handleCancelEdit = () => {
|
|
108
|
+
setEditingCommentId(null);
|
|
109
|
+
setEditText('');
|
|
110
|
+
};
|
|
111
|
+
const handleDeleteComment = (commentId) => {
|
|
112
|
+
if (selectedThread) {
|
|
113
|
+
deleteComment(selectedThread.id, commentId);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const handleCloseThread = () => {
|
|
117
|
+
if (selectedThread) {
|
|
118
|
+
closeThread(selectedThread.id);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const handleReopenThread = () => {
|
|
122
|
+
if (selectedThread) {
|
|
123
|
+
reopenThread(selectedThread.id);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const handleRemovePin = () => {
|
|
127
|
+
if (!selectedThread)
|
|
128
|
+
return;
|
|
129
|
+
removePin(selectedThread.id);
|
|
130
|
+
};
|
|
131
|
+
const handleClose = () => {
|
|
132
|
+
setSelectedThreadId(null);
|
|
133
|
+
if (floatingWidgetMode) {
|
|
134
|
+
setFloatingWidgetMode(false);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
setDrawerPinnedOpen(false);
|
|
138
|
+
}
|
|
139
|
+
setEditingCommentId(null);
|
|
140
|
+
setEditText('');
|
|
141
|
+
setNewCommentText('');
|
|
142
|
+
setReplyingToCommentId(null);
|
|
143
|
+
setReplyTextByCommentId({});
|
|
144
|
+
};
|
|
145
|
+
const formatCommentDate = (isoDate) => {
|
|
146
|
+
const date = new Date(isoDate);
|
|
147
|
+
return date.toLocaleString(undefined, {
|
|
148
|
+
month: 'short',
|
|
149
|
+
day: 'numeric',
|
|
150
|
+
hour: '2-digit',
|
|
151
|
+
minute: '2-digit',
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
const stripMarkersForDisplay = (text) => {
|
|
155
|
+
return text
|
|
156
|
+
.replace(/<!--\s*hale-reply-to:\d+\s*-->\s*\n?/g, '')
|
|
157
|
+
.replace(/<!--\s*hale-reply-to-local\s*-->\s*\n?/g, '')
|
|
158
|
+
.trimEnd();
|
|
159
|
+
};
|
|
160
|
+
const deriveStatus = () => {
|
|
161
|
+
if (!selectedThread)
|
|
162
|
+
return 'local';
|
|
163
|
+
if (selectedThread.syncStatus === 'error')
|
|
164
|
+
return 'error';
|
|
165
|
+
// If we have an issue and any comment hasn't synced yet, treat as pending.
|
|
166
|
+
if (selectedThread.issueNumber && selectedThread.comments.some((c) => !c.githubCommentId))
|
|
167
|
+
return 'pending';
|
|
168
|
+
if (selectedThread.issueNumber)
|
|
169
|
+
return 'synced';
|
|
170
|
+
return selectedThread.syncStatus || 'local';
|
|
171
|
+
};
|
|
172
|
+
const renderSyncLabel = (status) => {
|
|
173
|
+
switch (status) {
|
|
174
|
+
case 'synced':
|
|
175
|
+
return (React.createElement(react_core_1.Label, { color: "green", icon: React.createElement(react_icons_1.GithubIcon, null) }, "Synced"));
|
|
176
|
+
case 'local':
|
|
177
|
+
return React.createElement(react_core_1.Label, { color: "grey" }, "Local");
|
|
178
|
+
case 'pending':
|
|
179
|
+
return React.createElement(react_core_1.Label, { color: "blue" }, "Pending\u2026");
|
|
180
|
+
case 'syncing':
|
|
181
|
+
return (React.createElement(react_core_1.Label, { color: "blue", icon: React.createElement(react_core_1.Spinner, { size: "sm" }) }, "Syncing\u2026"));
|
|
182
|
+
case 'error':
|
|
183
|
+
return React.createElement(react_core_1.Label, { color: "red" }, "Sync error");
|
|
184
|
+
default:
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const panelContent = (React.createElement(React.Fragment, null,
|
|
189
|
+
React.createElement(react_core_1.Tabs, { activeKey: activeTabKey, onSelect: (_event, tabKey) => setActiveTabKey(tabKey), "aria-label": "Hale Commenting System drawer tabs" },
|
|
190
|
+
React.createElement(react_core_1.Tab, { eventKey: "details", title: React.createElement(react_core_1.TabTitleText, null, "Details") },
|
|
191
|
+
React.createElement("div", { style: { paddingTop: '1rem' } },
|
|
192
|
+
React.createElement(DetailsTab_1.DetailsTab, null))),
|
|
193
|
+
React.createElement(react_core_1.Tab, { eventKey: "jira", title: React.createElement(react_core_1.TabTitleText, null, "Jira") },
|
|
194
|
+
React.createElement("div", { style: { paddingTop: '1rem' } },
|
|
195
|
+
React.createElement(JiraTab_1.JiraTab, null))),
|
|
196
|
+
React.createElement(react_core_1.Tab, { eventKey: "comments", title: React.createElement(react_core_1.TabTitleText, null, "Comments") },
|
|
197
|
+
React.createElement("div", { style: { paddingTop: '1rem' } }, !selectedThread ? (React.createElement(react_core_1.EmptyState, { icon: react_icons_1.InfoCircleIcon, titleText: "No pin selected", headingLevel: "h3" },
|
|
198
|
+
React.createElement(react_core_1.EmptyStateBody, null, "Select or create a comment pin to start a thread."))) : (React.createElement(React.Fragment, null,
|
|
199
|
+
React.createElement(react_core_1.Card, { style: { marginBottom: '1rem' } },
|
|
200
|
+
React.createElement(react_core_1.CardBody, null,
|
|
201
|
+
React.createElement("div", { style: { display: 'grid', gap: '0.5rem' } },
|
|
202
|
+
React.createElement("div", { style: { fontSize: '0.875rem' } },
|
|
203
|
+
React.createElement("strong", null, "Location:"),
|
|
204
|
+
" (",
|
|
205
|
+
selectedThread.xPercent.toFixed(1),
|
|
206
|
+
"%, ",
|
|
207
|
+
selectedThread.yPercent.toFixed(1),
|
|
208
|
+
"%)"),
|
|
209
|
+
React.createElement("div", { style: { fontSize: '0.875rem' } },
|
|
210
|
+
React.createElement("strong", null, "Comments:"),
|
|
211
|
+
" ",
|
|
212
|
+
selectedThread.comments.length),
|
|
213
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.875rem' } },
|
|
214
|
+
React.createElement("strong", null, "Status:"),
|
|
215
|
+
renderSyncLabel(deriveStatus()) ?? React.createElement(react_core_1.Label, { color: "grey" }, "Local")),
|
|
216
|
+
React.createElement("div", { style: { fontSize: '0.875rem' } }, selectedThread.issueNumber && selectedThread.issueUrl ? (React.createElement("a", { href: selectedThread.issueUrl, target: "_blank", rel: "noopener noreferrer", style: { display: 'inline-flex', alignItems: 'center', gap: '0.25rem' } },
|
|
217
|
+
React.createElement(react_icons_1.GithubIcon, null),
|
|
218
|
+
"Issue #",
|
|
219
|
+
selectedThread.issueNumber,
|
|
220
|
+
React.createElement(react_icons_1.ExternalLinkAltIcon, { style: { fontSize: '0.75rem' } }))) : (React.createElement("span", { style: { display: 'inline-flex', alignItems: 'center', gap: '0.25rem', color: 'var(--pf-t--global--text--color--subtle)' } },
|
|
221
|
+
React.createElement(react_icons_1.GithubIcon, null),
|
|
222
|
+
"Issue pending\u2026"))),
|
|
223
|
+
React.createElement("div", null)))),
|
|
224
|
+
selectedThread.comments.length > 0 && (React.createElement("div", { style: { marginBottom: '1.5rem' } }, (() => {
|
|
225
|
+
const comments = selectedThread.comments;
|
|
226
|
+
const byId = new Map(comments.map((c) => [c.id, c]));
|
|
227
|
+
const byGitHubId = new Map();
|
|
228
|
+
for (const c of comments) {
|
|
229
|
+
if (c.githubCommentId)
|
|
230
|
+
byGitHubId.set(c.githubCommentId, c.id);
|
|
231
|
+
}
|
|
232
|
+
const childrenByParent = new Map();
|
|
233
|
+
const topLevel = [];
|
|
234
|
+
for (const c of comments) {
|
|
235
|
+
const parentLocal = c.parentCommentId ||
|
|
236
|
+
(c.parentGitHubCommentId ? byGitHubId.get(c.parentGitHubCommentId) : undefined);
|
|
237
|
+
if (parentLocal && byId.has(parentLocal)) {
|
|
238
|
+
const list = childrenByParent.get(parentLocal) || [];
|
|
239
|
+
list.push(c.id);
|
|
240
|
+
childrenByParent.set(parentLocal, list);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
topLevel.push(c.id);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const sortByCreatedAt = (aId, bId) => {
|
|
247
|
+
const a = byId.get(aId);
|
|
248
|
+
const b = byId.get(bId);
|
|
249
|
+
const at = a ? Date.parse(a.createdAt) : 0;
|
|
250
|
+
const bt = b ? Date.parse(b.createdAt) : 0;
|
|
251
|
+
return at - bt;
|
|
252
|
+
};
|
|
253
|
+
topLevel.sort(sortByCreatedAt);
|
|
254
|
+
childrenByParent.forEach((list, parentId) => {
|
|
255
|
+
list.sort(sortByCreatedAt);
|
|
256
|
+
childrenByParent.set(parentId, list);
|
|
257
|
+
});
|
|
258
|
+
const renderNode = (id, depth, topIndex) => {
|
|
259
|
+
const comment = byId.get(id);
|
|
260
|
+
if (!comment)
|
|
261
|
+
return null;
|
|
262
|
+
const isReply = depth > 0;
|
|
263
|
+
const title = isReply ? 'Reply' : `Comment #${(topIndex ?? 0) + 1}`;
|
|
264
|
+
const children = childrenByParent.get(id) || [];
|
|
265
|
+
return (React.createElement("div", { key: id, style: {
|
|
266
|
+
marginLeft: depth * 16,
|
|
267
|
+
marginTop: depth > 0 ? '8px' : undefined,
|
|
268
|
+
marginBottom: depth > 0 ? '8px' : '1rem',
|
|
269
|
+
} },
|
|
270
|
+
React.createElement(react_core_1.Card, null,
|
|
271
|
+
React.createElement(react_core_1.CardBody, { style: { position: 'relative' } },
|
|
272
|
+
React.createElement(react_core_1.Button, { variant: "plain", icon: React.createElement(react_icons_1.TrashIcon, null), isDanger: true, "aria-label": "Delete comment", title: "Delete comment", onClick: () => handleDeleteComment(comment.id), style: { position: 'absolute', top: '12px', right: '12px' } }),
|
|
273
|
+
React.createElement(react_core_1.Title, { headingLevel: "h3", size: isReply ? 'lg' : 'xl', style: { paddingRight: '2.5rem' } }, title),
|
|
274
|
+
React.createElement("div", { style: {
|
|
275
|
+
marginTop: '0.25rem',
|
|
276
|
+
fontSize: '0.875rem',
|
|
277
|
+
color: 'var(--pf-t--global--text--color--subtle)',
|
|
278
|
+
paddingRight: '2.5rem',
|
|
279
|
+
} },
|
|
280
|
+
"@",
|
|
281
|
+
comment.author ?? '—',
|
|
282
|
+
" \u00A0 ",
|
|
283
|
+
formatCommentDate(comment.createdAt)),
|
|
284
|
+
editingCommentId === comment.id ? (React.createElement("div", { style: { marginTop: '0.5rem' } },
|
|
285
|
+
React.createElement(react_core_1.TextArea, { value: editText, onChange: (_event, value) => setEditText(value), "aria-label": "Edit comment", rows: 3 }),
|
|
286
|
+
React.createElement(react_core_1.ActionList, { style: { marginTop: '0.5rem' } },
|
|
287
|
+
React.createElement(react_core_1.ActionListItem, null,
|
|
288
|
+
React.createElement(react_core_1.Button, { variant: "primary", onClick: () => handleSaveEdit(comment.id) }, "Save")),
|
|
289
|
+
React.createElement(react_core_1.ActionListItem, null,
|
|
290
|
+
React.createElement(react_core_1.Button, { variant: "link", onClick: handleCancelEdit }, "Cancel"))))) : (React.createElement("div", null,
|
|
291
|
+
React.createElement("div", { style: { marginTop: '0.75rem', whiteSpace: 'pre-wrap' } }, stripMarkersForDisplay(comment.text)),
|
|
292
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '0.5rem' } },
|
|
293
|
+
React.createElement(react_core_1.Button, { variant: "primary", onClick: () => handleStartReply(comment.id) }, "Reply"),
|
|
294
|
+
React.createElement(react_core_1.Button, { variant: "link", onClick: () => handleStartEdit(comment.id, stripMarkersForDisplay(comment.text)) }, "Edit")))),
|
|
295
|
+
replyingToCommentId === comment.id && (React.createElement("div", { style: { marginTop: '0.75rem' } },
|
|
296
|
+
React.createElement(react_core_1.Title, { headingLevel: "h4", size: "md", style: { marginBottom: '0.5rem' } }, "Reply to this comment"),
|
|
297
|
+
React.createElement(react_core_1.TextArea, { value: replyTextByCommentId[comment.id] || '', onChange: (_event, value) => setReplyTextByCommentId((prev) => ({ ...prev, [comment.id]: value })), placeholder: "Type your reply...", "aria-label": "Reply to comment", rows: 3 }),
|
|
298
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '0.5rem' } },
|
|
299
|
+
React.createElement(react_core_1.Button, { variant: "primary", onClick: () => handleSubmitReply(comment.id), isDisabled: !(replyTextByCommentId[comment.id] || '').trim() }, "Post reply"),
|
|
300
|
+
React.createElement(react_core_1.Button, { variant: "link", onClick: handleCancelReply }, "Cancel")))))),
|
|
301
|
+
children.map((childId) => renderNode(childId, depth + 1))));
|
|
302
|
+
};
|
|
303
|
+
return React.createElement(React.Fragment, null, topLevel.map((id, idx) => renderNode(id, 0, idx)));
|
|
304
|
+
})())),
|
|
305
|
+
React.createElement("div", null, selectedThread.status === 'closed' ? (React.createElement("div", { style: { marginBottom: '1rem', padding: '1rem', backgroundColor: 'var(--pf-t--global--background--color--secondary--default)', borderRadius: 'var(--pf-t--global--border--radius--medium)' } },
|
|
306
|
+
React.createElement(react_core_1.Title, { headingLevel: "h3", size: "md", style: { marginBottom: '0.5rem' } }, "\uD83D\uDD12 Thread Closed"),
|
|
307
|
+
React.createElement("p", { style: { color: 'var(--pf-t--global--text--color--subtle)', marginBottom: '1rem' } }, "This thread has been closed and locked. Reopen it to add new comments."),
|
|
308
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
|
|
309
|
+
React.createElement(react_core_1.Button, { variant: "primary", onClick: handleReopenThread }, "Reopen Thread"),
|
|
310
|
+
React.createElement(react_core_1.Button, { variant: "link", isDanger: true, onClick: handleRemovePin }, "Remove pin")))) : (React.createElement(React.Fragment, null,
|
|
311
|
+
React.createElement(react_core_1.Title, { headingLevel: "h3", size: "md", style: { marginBottom: '0.5rem' } }, "Add comment"),
|
|
312
|
+
React.createElement(react_core_1.TextArea, { value: newCommentText, onChange: (_event, value) => setNewCommentText(value), placeholder: "Type your comment...", "aria-label": "New comment", rows: 4 }),
|
|
313
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '1rem' } },
|
|
314
|
+
React.createElement(react_core_1.Button, { variant: "primary", onClick: handleAddComment, isDisabled: !newCommentText.trim() }, "Add Comment"),
|
|
315
|
+
React.createElement(react_core_1.Button, { variant: "secondary", onClick: handleCloseThread }, "Close Thread"),
|
|
316
|
+
React.createElement(react_core_1.Button, { variant: "link", isDanger: true, onClick: handleRemovePin }, "Remove pin"))))))))))));
|
|
317
|
+
if (floatingWidgetMode && isExpanded) {
|
|
318
|
+
return (React.createElement(React.Fragment, null,
|
|
319
|
+
React.createElement(FloatingWidget_1.FloatingWidget, { onClose: handleClose, title: "Hale Commenting System" },
|
|
320
|
+
React.createElement("div", { style: { padding: '1rem' } }, panelContent)),
|
|
321
|
+
React.createElement("div", { style: { position: 'relative' } }, children)));
|
|
322
|
+
}
|
|
323
|
+
const drawerPanelContent = isExpanded ? (React.createElement(react_core_1.DrawerPanelContent, { isResizable: true, defaultSize: '500px', minSize: '300px' },
|
|
324
|
+
React.createElement(react_core_1.DrawerHead, null,
|
|
325
|
+
React.createElement("span", { tabIndex: isExpanded ? 0 : -1, ref: drawerRef },
|
|
326
|
+
React.createElement(react_core_1.Title, { headingLevel: "h2", size: "lg" }, "Hale Commenting System")),
|
|
327
|
+
React.createElement(react_core_1.DrawerActions, null,
|
|
328
|
+
React.createElement(react_core_1.DrawerCloseButton, { onClick: handleClose }))),
|
|
329
|
+
React.createElement(react_core_1.DrawerPanelBody, null, panelContent))) : null;
|
|
330
|
+
return (React.createElement(react_core_1.Drawer, { isExpanded: isExpanded, isInline: true, onExpand: onExpand },
|
|
331
|
+
React.createElement(react_core_1.DrawerContent, { panelContent: drawerPanelContent },
|
|
332
|
+
React.createElement(react_core_1.DrawerContentBody, { style: { position: 'relative' } }, children))));
|
|
333
|
+
};
|
|
334
|
+
exports.CommentPanel = CommentPanel;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface CommentPinProps {
|
|
3
|
+
xPercent: number;
|
|
4
|
+
yPercent: number;
|
|
5
|
+
commentCount: number;
|
|
6
|
+
isClosed?: boolean;
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const CommentPin: React.FunctionComponent<CommentPinProps>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
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.CommentPin = void 0;
|
|
37
|
+
const React = __importStar(require("react"));
|
|
38
|
+
const react_core_1 = require("@patternfly/react-core");
|
|
39
|
+
const react_icons_1 = require("@patternfly/react-icons");
|
|
40
|
+
const CommentPin = ({ xPercent, yPercent, commentCount, isClosed = false, isSelected, onClick, }) => {
|
|
41
|
+
return (React.createElement(react_core_1.Button, { variant: "plain", "data-comment-pin": true, style: {
|
|
42
|
+
position: 'absolute',
|
|
43
|
+
left: `${xPercent}%`,
|
|
44
|
+
top: `${yPercent}%`,
|
|
45
|
+
transform: 'translate(-50%, -50%)',
|
|
46
|
+
width: '32px',
|
|
47
|
+
height: '32px',
|
|
48
|
+
borderRadius: '50%',
|
|
49
|
+
backgroundColor: isClosed ? 'var(--pf-t--global--icon--color--subtle)' : '#C9190B',
|
|
50
|
+
color: 'white',
|
|
51
|
+
border: isSelected ? '3px solid #0066CC' : '2px solid white',
|
|
52
|
+
boxShadow: isSelected
|
|
53
|
+
? '0 0 0 3px rgba(0, 102, 204, 0.3), 0 2px 8px rgba(0,0,0,0.3)'
|
|
54
|
+
: '0 2px 8px rgba(0,0,0,0.3)',
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
padding: 0,
|
|
57
|
+
display: 'flex',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center',
|
|
60
|
+
transition: 'all 0.2s ease',
|
|
61
|
+
pointerEvents: 'auto',
|
|
62
|
+
}, onClick: onClick, "aria-label": `${isClosed ? 'Closed ' : ''}comment thread with ${commentCount} comment${commentCount !== 1 ? 's' : ''}` }, commentCount === 0 ? (React.createElement(react_icons_1.CommentIcon, { style: { fontSize: '16px' } })) : commentCount === 1 ? (React.createElement(react_icons_1.CommentIcon, { style: { fontSize: '16px' } })) : (React.createElement("span", { style: { fontSize: '14px', fontWeight: 'bold' } }, commentCount))));
|
|
63
|
+
};
|
|
64
|
+
exports.CommentPin = CommentPin;
|