hale-commenting-system 3.5.1 → 3.6.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 +187 -8
- package/package.json +3 -1
- package/src/app/commenting-system/components/CommentOverlay.tsx +282 -14
- package/src/app/commenting-system/components/CommentPanel.tsx +67 -12
- package/src/app/commenting-system/components/CommentPin.tsx +87 -5
- package/src/app/commenting-system/components/FloatingWidget.tsx +79 -9
- package/src/app/commenting-system/components/JiraTab.tsx +295 -166
- package/src/app/commenting-system/contexts/CommentContext.tsx +77 -33
- package/src/app/commenting-system/index.ts +4 -1
- package/src/app/commenting-system/services/githubAdapter.ts +9 -2
- package/src/app/commenting-system/types/index.ts +14 -2
- package/src/app/commenting-system/utils/componentUtils.ts +242 -0
- package/src/app/commenting-system/utils/selectorUtils.ts +155 -0
package/README.md
CHANGED
|
@@ -16,8 +16,23 @@ npm uninstall hale-commenting-system
|
|
|
16
16
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
|
-
- **
|
|
20
|
-
-
|
|
19
|
+
- **React Component-Based Commenting** - Detects and attaches comments to React components, not just DOM elements
|
|
20
|
+
- Automatically identifies React component names, types, and props
|
|
21
|
+
- Shows component tree path (e.g., "App > Dashboard > Button")
|
|
22
|
+
- Displays component metadata including props in the comment panel
|
|
23
|
+
- **Hover Preview** - See what will be selected before clicking with a visual preview
|
|
24
|
+
- Dashed blue border highlights the element you're about to select
|
|
25
|
+
- Component name label appears above the element
|
|
26
|
+
- Works on all elements including buttons, links, and interactive components
|
|
27
|
+
- **Smart Pin Positioning** - Pins anchor precisely to elements using CSS selectors
|
|
28
|
+
- Pins appear at the top-left corner of selected components
|
|
29
|
+
- Automatically follows elements on scroll and resize
|
|
30
|
+
- Falls back to stored coordinates if element is deleted
|
|
31
|
+
- **Component Highlighting** - Selected components are highlighted with a blue border (similar to Chrome DevTools)
|
|
32
|
+
- **Show Pins Toggle** - View comment pins even when commenting is disabled
|
|
33
|
+
- **Resizable Widget** - Adjust the commenting panel size to fit your workflow
|
|
34
|
+
- **Missing Element Detection** - Pins fade and show [deleted] when target component is removed
|
|
35
|
+
- **Thread Discussions** - Organize comments into threads with replies
|
|
21
36
|
- **GitHub Integration** - Sync comments with GitHub Issues automatically
|
|
22
37
|
- **Jira Integration** - Link Jira tickets to specific pages or sections
|
|
23
38
|
- **PatternFly Design** - Built with PatternFly React components
|
|
@@ -140,17 +155,94 @@ After running the integration script, the commenting system will be available in
|
|
|
140
155
|
|
|
141
156
|
### Adding Comments
|
|
142
157
|
|
|
143
|
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
158
|
+
1. **Hover to preview** - Move your mouse over any component to see a preview of what will be selected
|
|
159
|
+
- A dashed blue border highlights the element
|
|
160
|
+
- A label shows the component or element name
|
|
161
|
+
- This helps you target the exact component you want to comment on
|
|
162
|
+
|
|
163
|
+
2. **Click on any component** to attach a comment pin
|
|
164
|
+
- The system detects React components automatically
|
|
165
|
+
- Falls back to DOM element detection for non-React elements
|
|
166
|
+
- Pins anchor to specific elements using CSS selectors
|
|
167
|
+
- Pins appear at the top-left corner of the selected component
|
|
168
|
+
|
|
169
|
+
3. **View component information** in the comment panel
|
|
170
|
+
- **React Components:** Shows component name, type (function/class/memo), component tree path, and props
|
|
171
|
+
- **DOM Elements:** Shows element description (e.g., "button.pf-c-button")
|
|
172
|
+
- Components marked as [deleted] if the element is removed from the page
|
|
173
|
+
- Expandable props section shows all component props in formatted JSON
|
|
174
|
+
|
|
175
|
+
4. **Pin behavior:**
|
|
176
|
+
- **Element exists:** Pin positions at top-left corner and follows it on scroll/resize
|
|
177
|
+
- **Element deleted:** Pin fades to 40% opacity, uses fallback position, and shows [deleted] label
|
|
178
|
+
- **Selected pin:** Highlighted with blue border around the target component
|
|
179
|
+
|
|
180
|
+
5. **Control visibility:**
|
|
181
|
+
- **Enable Comments:** Toggle to enable/disable creating new comments
|
|
182
|
+
- **Show pins:** Toggle to show/hide pins even when commenting is disabled
|
|
183
|
+
- Use both toggles to view existing comments without creating new ones
|
|
184
|
+
|
|
185
|
+
6. **Resize the widget:**
|
|
186
|
+
- Drag the resize handle in the bottom-right corner of the commenting panel
|
|
187
|
+
- Adjust width (300-800px) and height (200px to viewport height)
|
|
188
|
+
- Size is maintained during your session
|
|
189
|
+
|
|
190
|
+
7. **Navigate and manage comments:**
|
|
191
|
+
- **View all comments** in the "Comments" menu item in the sidebar
|
|
192
|
+
- **Reply to comments** to create discussion threads
|
|
193
|
+
- **Select pins** by clicking on them to view/edit comments
|
|
194
|
+
- **Close/reopen threads** just like GitHub Issues
|
|
195
|
+
- **Remove pins** to delete comment threads
|
|
196
|
+
|
|
197
|
+
### How It Works
|
|
198
|
+
|
|
199
|
+
The commenting system uses a **hybrid approach** combining React component detection and CSS selectors:
|
|
200
|
+
|
|
201
|
+
#### React Component Detection (Primary)
|
|
202
|
+
|
|
203
|
+
1. **Component identification:**
|
|
204
|
+
- Detects React components using React fiber nodes
|
|
205
|
+
- Extracts component name, type (function/class/memo/forwardRef), and props
|
|
206
|
+
- Builds component tree path showing hierarchy (e.g., "App > Dashboard > Button")
|
|
207
|
+
- Works with all React component types including HOCs and wrapped components
|
|
208
|
+
|
|
209
|
+
2. **Component metadata:**
|
|
210
|
+
- Component name (e.g., "Button", "Card", "CustomComponent")
|
|
211
|
+
- Component type (function, class, memo, forwardRef, etc.)
|
|
212
|
+
- Component props (sanitized for display, functions shown as [Function])
|
|
213
|
+
- Component tree path showing parent hierarchy
|
|
214
|
+
|
|
215
|
+
#### CSS Selector Fallback
|
|
216
|
+
|
|
217
|
+
For non-React elements or when component detection isn't available:
|
|
218
|
+
|
|
219
|
+
1. **Priority selection strategy:**
|
|
220
|
+
- `data-testid` or `data-id` attributes (most stable)
|
|
221
|
+
- `id` attribute
|
|
222
|
+
- Combination of tag + class + aria attributes
|
|
223
|
+
- Fallback to nth-child path
|
|
224
|
+
|
|
225
|
+
2. **Stored metadata for each pin:**
|
|
226
|
+
- CSS selector for the target element
|
|
227
|
+
- React component metadata (if available)
|
|
228
|
+
- Simplified element/component name for display
|
|
229
|
+
- Fallback coordinates (if element is deleted)
|
|
230
|
+
|
|
231
|
+
3. **Example:** Clicking a React Button component might show:
|
|
232
|
+
- **Component Name:** `Button`
|
|
233
|
+
- **Component Type:** `function`
|
|
234
|
+
- **Component Path:** `App > Dashboard > Button`
|
|
235
|
+
- **Props:** `{ variant: "primary", children: "Submit" }`
|
|
236
|
+
- **CSS Selector:** `button.pf-c-button[aria-label="Submit"]` (fallback)
|
|
147
237
|
|
|
148
238
|
### GitHub Integration (Optional)
|
|
149
239
|
|
|
150
240
|
When configured, comments automatically sync with GitHub Issues:
|
|
151
|
-
- Each comment thread becomes a GitHub Issue
|
|
152
|
-
-
|
|
241
|
+
- Each comment thread becomes a GitHub Issue with component metadata
|
|
242
|
+
- Issue body includes: React component name (if available), CSS selector, and fallback position
|
|
243
|
+
- Replies sync as Issue comments with proper threading
|
|
153
244
|
- Status changes (open/closed) sync between the app and GitHub
|
|
245
|
+
- Component metadata is preserved in issue descriptions
|
|
154
246
|
|
|
155
247
|
### Jira Integration (Optional)
|
|
156
248
|
|
|
@@ -196,6 +288,93 @@ The integration script automatically modifies your project:
|
|
|
196
288
|
5. **`src/app/Comments/Comments.tsx`** - Creates the Comments view component
|
|
197
289
|
6. **`.env` and `.env.server`** - Creates configuration files
|
|
198
290
|
|
|
291
|
+
## Testing Before Publishing
|
|
292
|
+
|
|
293
|
+
To test this package locally before publishing to npm:
|
|
294
|
+
|
|
295
|
+
### Method 1: Using npm link (Recommended)
|
|
296
|
+
|
|
297
|
+
1. **In the package directory** (hale-npm-package):
|
|
298
|
+
```bash
|
|
299
|
+
npm run build
|
|
300
|
+
npm link
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
2. **In your test application** (e.g., PatternFly React Seed):
|
|
304
|
+
```bash
|
|
305
|
+
npm link hale-commenting-system
|
|
306
|
+
npx hale-commenting-system init
|
|
307
|
+
npm run start:dev
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
3. **Test the features:**
|
|
311
|
+
- Hover over components to see preview highlighting
|
|
312
|
+
- Click on various components (buttons, cards, inputs)
|
|
313
|
+
- Verify pins attach to components and appear at top-left corner
|
|
314
|
+
- Check that React component metadata is displayed correctly
|
|
315
|
+
- Scroll/resize and verify pins follow elements
|
|
316
|
+
- Add comments and replies
|
|
317
|
+
- Test "Show pins" toggle to view pins without enabling comments
|
|
318
|
+
- Resize the commenting widget
|
|
319
|
+
- Remove a component from the DOM and verify pin fades and shows [deleted]
|
|
320
|
+
- Reload page and verify pins reattach correctly
|
|
321
|
+
|
|
322
|
+
4. **When done testing:**
|
|
323
|
+
```bash
|
|
324
|
+
# In your test app
|
|
325
|
+
npx hale-commenting-system remove
|
|
326
|
+
npm unlink hale-commenting-system
|
|
327
|
+
npm install
|
|
328
|
+
|
|
329
|
+
# In the package directory
|
|
330
|
+
npm unlink
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Method 2: Using npm pack
|
|
334
|
+
|
|
335
|
+
1. **In the package directory:**
|
|
336
|
+
```bash
|
|
337
|
+
npm run build
|
|
338
|
+
npm pack
|
|
339
|
+
```
|
|
340
|
+
This creates a `.tgz` file (e.g., `hale-commenting-system-3.6.0.tgz`)
|
|
341
|
+
|
|
342
|
+
2. **In your test application:**
|
|
343
|
+
```bash
|
|
344
|
+
npm install /path/to/hale-commenting-system-3.5.2.tgz
|
|
345
|
+
npx hale-commenting-system init
|
|
346
|
+
npm run start:dev
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
3. **Test and clean up as above**
|
|
350
|
+
|
|
351
|
+
### Method 3: Using Local Path
|
|
352
|
+
|
|
353
|
+
1. **In your test application:**
|
|
354
|
+
```bash
|
|
355
|
+
npm install /path/to/hale-npm-package
|
|
356
|
+
npx hale-commenting-system init
|
|
357
|
+
npm run start:dev
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### What to Test
|
|
361
|
+
|
|
362
|
+
- ✅ **Hover preview** - Verify preview shows correct element and component name
|
|
363
|
+
- ✅ **Pin creation** - Click on different component types (React and DOM elements)
|
|
364
|
+
- ✅ **Pin positioning** - Verify pins appear at top-left corner of elements
|
|
365
|
+
- ✅ **Component detection** - Verify React components show name, type, path, and props
|
|
366
|
+
- ✅ **Component highlighting** - Verify selected components are highlighted with blue border
|
|
367
|
+
- ✅ **Show pins toggle** - Verify pins are visible when toggle is on, even with comments disabled
|
|
368
|
+
- ✅ **Widget resizing** - Verify widget can be resized and maintains size
|
|
369
|
+
- ✅ **Dynamic tracking** - Scroll, resize, and verify pins follow elements
|
|
370
|
+
- ✅ **Element deletion** - Remove components and verify fade + [deleted] label
|
|
371
|
+
- ✅ **Page reload** - Verify pins reattach to correct elements
|
|
372
|
+
- ✅ **Component display** - Check panel shows React component info or DOM element names
|
|
373
|
+
- ✅ **GitHub sync** (if configured) - Verify issues include component metadata
|
|
374
|
+
- ✅ **Comments & replies** - Test threaded discussions
|
|
375
|
+
- ✅ **Close/reopen** - Test thread status changes
|
|
376
|
+
- ✅ **Remove pin** - Verify removing a pin doesn't create a new one
|
|
377
|
+
|
|
199
378
|
## Development
|
|
200
379
|
|
|
201
380
|
### Running Locally
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hale-commenting-system",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "A commenting system for PatternFly React applications that allows designers and developers to add comments directly on design pages, sync with GitHub Issues, and link Jira tickets.",
|
|
5
5
|
"homepage": "https://www.npmjs.com/package/hale-commenting-system",
|
|
6
6
|
"license": "MIT",
|
|
@@ -91,6 +91,8 @@
|
|
|
91
91
|
"@babel/parser": "^7.23.0",
|
|
92
92
|
"@babel/traverse": "^7.23.0",
|
|
93
93
|
"@babel/types": "^7.23.0",
|
|
94
|
+
"dotenv": "^16.4.5",
|
|
95
|
+
"express": "^4.21.1",
|
|
94
96
|
"inquirer": "^8.2.6",
|
|
95
97
|
"node-fetch": "^2.7.0"
|
|
96
98
|
},
|
|
@@ -3,44 +3,310 @@ import { useLocation } from 'react-router-dom';
|
|
|
3
3
|
import { useComments } from '../contexts/CommentContext';
|
|
4
4
|
import { CommentPin } from './CommentPin';
|
|
5
5
|
import { getVersionFromPathOrQuery } from '../utils/version';
|
|
6
|
+
import { generateSelectorForElement, getElementDescription, getElementComponentMetadata, findElementBySelector } from '../utils/selectorUtils';
|
|
7
|
+
import { getFiberFromElement, getComponentName } from '../utils/componentUtils';
|
|
6
8
|
|
|
7
9
|
export const CommentOverlay: React.FunctionComponent = () => {
|
|
8
10
|
const location = useLocation();
|
|
9
|
-
const { commentsEnabled, addThread, selectedThreadId, setSelectedThreadId, syncFromGitHub, getThreadsForRoute } = useComments();
|
|
11
|
+
const { commentsEnabled, showPinsEnabled, addThread, selectedThreadId, setSelectedThreadId, syncFromGitHub, getThreadsForRoute } = useComments();
|
|
10
12
|
const detectedVersion = getVersionFromPathOrQuery(location.pathname, location.search);
|
|
11
13
|
const overlayRef = React.useRef<HTMLDivElement>(null);
|
|
12
14
|
|
|
13
15
|
// Show both open and closed threads as pins (GitHub-style: closed issues still exist)
|
|
14
16
|
const currentThreads = getThreadsForRoute(location.pathname, detectedVersion);
|
|
17
|
+
const selectedThread = currentThreads.find((t) => t.id === selectedThreadId);
|
|
18
|
+
const highlightRef = React.useRef<HTMLDivElement | null>(null);
|
|
19
|
+
const previewRef = React.useRef<HTMLDivElement | null>(null);
|
|
20
|
+
const previewLabelRef = React.useRef<HTMLDivElement | null>(null);
|
|
21
|
+
const hoveredElementRef = React.useRef<Element | null>(null);
|
|
22
|
+
|
|
23
|
+
// Component highlighting effect (similar to Chrome DevTools)
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
// Hide highlight when comments are disabled
|
|
26
|
+
if (!commentsEnabled || !selectedThread || !selectedThread.cssSelector) {
|
|
27
|
+
// Remove highlight
|
|
28
|
+
if (highlightRef.current) {
|
|
29
|
+
highlightRef.current.remove();
|
|
30
|
+
highlightRef.current = null;
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const element = findElementBySelector(selectedThread.cssSelector);
|
|
36
|
+
if (!element) {
|
|
37
|
+
// Element not found, remove highlight
|
|
38
|
+
if (highlightRef.current) {
|
|
39
|
+
highlightRef.current.remove();
|
|
40
|
+
highlightRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create or update highlight overlay
|
|
46
|
+
let highlight = highlightRef.current;
|
|
47
|
+
if (!highlight) {
|
|
48
|
+
highlight = document.createElement('div');
|
|
49
|
+
highlight.style.cssText = `
|
|
50
|
+
position: fixed;
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
z-index: 998;
|
|
53
|
+
border: 2px solid #0066CC;
|
|
54
|
+
background-color: rgba(0, 102, 204, 0.1);
|
|
55
|
+
box-shadow: 0 0 0 1px rgba(0, 102, 204, 0.3);
|
|
56
|
+
transition: all 0.15s ease;
|
|
57
|
+
`;
|
|
58
|
+
document.body.appendChild(highlight);
|
|
59
|
+
highlightRef.current = highlight;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Update highlight position and size
|
|
63
|
+
const updateHighlight = () => {
|
|
64
|
+
if (!highlight || !element) return;
|
|
65
|
+
const rect = element.getBoundingClientRect();
|
|
66
|
+
highlight.style.left = `${rect.left + window.scrollX}px`;
|
|
67
|
+
highlight.style.top = `${rect.top + window.scrollY}px`;
|
|
68
|
+
highlight.style.width = `${rect.width}px`;
|
|
69
|
+
highlight.style.height = `${rect.height}px`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
updateHighlight();
|
|
73
|
+
|
|
74
|
+
// Update on scroll/resize
|
|
75
|
+
const handleUpdate = () => updateHighlight();
|
|
76
|
+
window.addEventListener('scroll', handleUpdate, true);
|
|
77
|
+
window.addEventListener('resize', handleUpdate);
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
window.removeEventListener('scroll', handleUpdate, true);
|
|
81
|
+
window.removeEventListener('resize', handleUpdate);
|
|
82
|
+
};
|
|
83
|
+
}, [commentsEnabled, selectedThreadId, selectedThread]);
|
|
84
|
+
|
|
85
|
+
// Cleanup highlight on unmount
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
return () => {
|
|
88
|
+
if (highlightRef.current) {
|
|
89
|
+
highlightRef.current.remove();
|
|
90
|
+
highlightRef.current = null;
|
|
91
|
+
}
|
|
92
|
+
if (previewRef.current) {
|
|
93
|
+
previewRef.current.remove();
|
|
94
|
+
previewRef.current = null;
|
|
95
|
+
}
|
|
96
|
+
if (previewLabelRef.current) {
|
|
97
|
+
previewLabelRef.current.remove();
|
|
98
|
+
previewLabelRef.current = null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
// Hover preview effect - shows what will be selected before clicking
|
|
104
|
+
const handleMouseMove = React.useCallback((e: MouseEvent) => {
|
|
105
|
+
if (!commentsEnabled) {
|
|
106
|
+
// Remove preview if comments are disabled
|
|
107
|
+
if (previewRef.current) {
|
|
108
|
+
previewRef.current.remove();
|
|
109
|
+
previewRef.current = null;
|
|
110
|
+
}
|
|
111
|
+
if (previewLabelRef.current) {
|
|
112
|
+
previewLabelRef.current.remove();
|
|
113
|
+
previewLabelRef.current = null;
|
|
114
|
+
}
|
|
115
|
+
hoveredElementRef.current = null;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const target = e.target as HTMLElement;
|
|
120
|
+
|
|
121
|
+
// Don't show preview on comment system UI elements (but allow buttons/links to be selected)
|
|
122
|
+
if (
|
|
123
|
+
target.closest('[data-comment-controls]') ||
|
|
124
|
+
target.closest('[data-comment-pin]') ||
|
|
125
|
+
target.closest('[data-floating-widget]') ||
|
|
126
|
+
target.closest('[data-comment-preview]')
|
|
127
|
+
) {
|
|
128
|
+
if (previewRef.current) {
|
|
129
|
+
previewRef.current.style.display = 'none';
|
|
130
|
+
}
|
|
131
|
+
if (previewLabelRef.current) {
|
|
132
|
+
previewLabelRef.current.style.display = 'none';
|
|
133
|
+
}
|
|
134
|
+
hoveredElementRef.current = null;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Use the actual element being hovered (don't traverse up for preview)
|
|
139
|
+
const element = target as Element;
|
|
140
|
+
|
|
141
|
+
// Only update if hovering over a different element
|
|
142
|
+
if (hoveredElementRef.current === element) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
hoveredElementRef.current = element;
|
|
147
|
+
|
|
148
|
+
// Get element info for preview - use the actual element, not parent component
|
|
149
|
+
const elementDescription = getElementDescription(element);
|
|
150
|
+
|
|
151
|
+
// Only get component metadata if this element itself is a React component
|
|
152
|
+
// Don't traverse up to parent components for preview
|
|
153
|
+
const fiber = getFiberFromElement(element);
|
|
154
|
+
let previewName = elementDescription;
|
|
155
|
+
|
|
156
|
+
if (fiber) {
|
|
157
|
+
const type = fiber.type;
|
|
158
|
+
// Only use component name if this element IS a React component (not native)
|
|
159
|
+
if (type && typeof type !== 'string') {
|
|
160
|
+
const componentName = getComponentName(fiber);
|
|
161
|
+
const displayName = (typeof type === 'function' && (type.displayName || type.name)) ||
|
|
162
|
+
(type?.$$typeof === Symbol.for('react.forward_ref') && (type.render?.displayName || type.render?.name)) ||
|
|
163
|
+
undefined;
|
|
164
|
+
previewName = componentName || displayName || elementDescription;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create or update preview highlight
|
|
169
|
+
let preview = previewRef.current;
|
|
170
|
+
if (!preview) {
|
|
171
|
+
preview = document.createElement('div');
|
|
172
|
+
preview.setAttribute('data-comment-preview', 'true');
|
|
173
|
+
preview.style.cssText = `
|
|
174
|
+
position: fixed;
|
|
175
|
+
pointer-events: none;
|
|
176
|
+
z-index: 997;
|
|
177
|
+
border: 2px dashed #0066CC;
|
|
178
|
+
background-color: rgba(0, 102, 204, 0.05);
|
|
179
|
+
box-shadow: 0 0 0 1px rgba(0, 102, 204, 0.2);
|
|
180
|
+
transition: all 0.1s ease;
|
|
181
|
+
`;
|
|
182
|
+
document.body.appendChild(preview);
|
|
183
|
+
previewRef.current = preview;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Create or update preview label
|
|
187
|
+
let previewLabel = previewLabelRef.current;
|
|
188
|
+
if (!previewLabel) {
|
|
189
|
+
previewLabel = document.createElement('div');
|
|
190
|
+
previewLabel.setAttribute('data-comment-preview', 'true');
|
|
191
|
+
previewLabel.style.cssText = `
|
|
192
|
+
position: fixed;
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
z-index: 998;
|
|
195
|
+
background-color: #0066CC;
|
|
196
|
+
color: white;
|
|
197
|
+
padding: 4px 8px;
|
|
198
|
+
border-radius: 4px;
|
|
199
|
+
font-size: 12px;
|
|
200
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
203
|
+
transition: all 0.1s ease;
|
|
204
|
+
`;
|
|
205
|
+
document.body.appendChild(previewLabel);
|
|
206
|
+
previewLabelRef.current = previewLabel;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Update preview position and size
|
|
210
|
+
const rect = element.getBoundingClientRect();
|
|
211
|
+
preview.style.display = 'block';
|
|
212
|
+
preview.style.left = `${rect.left + window.scrollX}px`;
|
|
213
|
+
preview.style.top = `${rect.top + window.scrollY}px`;
|
|
214
|
+
preview.style.width = `${rect.width}px`;
|
|
215
|
+
preview.style.height = `${rect.height}px`;
|
|
216
|
+
|
|
217
|
+
// Update label position (above the element, top-left, but ensure it stays on screen)
|
|
218
|
+
previewLabel.style.display = 'block';
|
|
219
|
+
previewLabel.textContent = previewName;
|
|
220
|
+
|
|
221
|
+
const labelLeft = rect.left + window.scrollX;
|
|
222
|
+
const labelTop = rect.top + window.scrollY - 28;
|
|
223
|
+
|
|
224
|
+
// Ensure label doesn't go off the left edge
|
|
225
|
+
const adjustedLeft = Math.max(8, labelLeft);
|
|
226
|
+
|
|
227
|
+
// If element is near top of viewport, show label below instead
|
|
228
|
+
const adjustedTop = labelTop < window.scrollY + 40
|
|
229
|
+
? rect.bottom + window.scrollY + 4
|
|
230
|
+
: labelTop;
|
|
231
|
+
|
|
232
|
+
previewLabel.style.left = `${adjustedLeft}px`;
|
|
233
|
+
previewLabel.style.top = `${adjustedTop}px`;
|
|
234
|
+
}, [commentsEnabled]);
|
|
235
|
+
|
|
236
|
+
// Mouse leave handler to hide preview
|
|
237
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
238
|
+
if (previewRef.current) {
|
|
239
|
+
previewRef.current.style.display = 'none';
|
|
240
|
+
}
|
|
241
|
+
if (previewLabelRef.current) {
|
|
242
|
+
previewLabelRef.current.style.display = 'none';
|
|
243
|
+
}
|
|
244
|
+
hoveredElementRef.current = null;
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
// Set up hover preview listeners
|
|
248
|
+
React.useEffect(() => {
|
|
249
|
+
if (commentsEnabled) {
|
|
250
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
251
|
+
document.addEventListener('mouseleave', handleMouseLeave, true);
|
|
252
|
+
|
|
253
|
+
return () => {
|
|
254
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
255
|
+
document.removeEventListener('mouseleave', handleMouseLeave, true);
|
|
256
|
+
};
|
|
257
|
+
} else {
|
|
258
|
+
// Clean up preview when disabled
|
|
259
|
+
if (previewRef.current) {
|
|
260
|
+
previewRef.current.remove();
|
|
261
|
+
previewRef.current = null;
|
|
262
|
+
}
|
|
263
|
+
if (previewLabelRef.current) {
|
|
264
|
+
previewLabelRef.current.remove();
|
|
265
|
+
previewLabelRef.current = null;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
}, [commentsEnabled, handleMouseMove, handleMouseLeave]);
|
|
15
270
|
|
|
16
271
|
const handlePageClick = (e: MouseEvent) => {
|
|
17
272
|
if (!commentsEnabled) return;
|
|
18
273
|
|
|
19
|
-
// Check if clicking on
|
|
274
|
+
// Check if clicking on comment system UI elements (but allow buttons/links to be selected)
|
|
20
275
|
const target = e.target as HTMLElement;
|
|
21
276
|
if (
|
|
22
|
-
target.closest('button') ||
|
|
23
|
-
target.closest('a') ||
|
|
24
|
-
target.closest('input') ||
|
|
25
|
-
target.closest('select') ||
|
|
26
|
-
target.closest('textarea') ||
|
|
27
|
-
target.closest('[role="button"]') ||
|
|
28
277
|
target.closest('[data-comment-controls]') ||
|
|
29
278
|
target.closest('[data-comment-pin]') ||
|
|
30
|
-
target.closest('[data-floating-widget]')
|
|
279
|
+
target.closest('[data-floating-widget]') ||
|
|
280
|
+
target.closest('[data-comment-preview]') ||
|
|
281
|
+
target.closest('button[aria-label*="Remove"]') ||
|
|
282
|
+
target.closest('button[aria-label*="Delete"]')
|
|
31
283
|
) {
|
|
32
|
-
return; // Don't create pin if clicking
|
|
284
|
+
return; // Don't create pin if clicking comment system UI or remove/delete buttons
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Also check if the click originated from within the floating widget
|
|
288
|
+
// This prevents clicks on "Remove pin" button from creating new pins
|
|
289
|
+
if (e.target && (e.target as Element).closest('[data-floating-widget]')) {
|
|
290
|
+
return;
|
|
33
291
|
}
|
|
34
292
|
|
|
35
293
|
// Get the overlay container dimensions (accounts for drawer being open)
|
|
36
294
|
if (!overlayRef.current) return;
|
|
37
295
|
const rect = overlayRef.current.getBoundingClientRect();
|
|
38
296
|
|
|
39
|
-
// Calculate percentage based on the content area, not the full window
|
|
297
|
+
// Calculate percentage based on the content area, not the full window (used as fallback)
|
|
40
298
|
const xPercent = ((e.clientX - rect.left) / rect.width) * 100;
|
|
41
299
|
const yPercent = ((e.clientY - rect.top) / rect.height) * 100;
|
|
42
300
|
|
|
43
|
-
|
|
301
|
+
// Generate CSS selector for the clicked element
|
|
302
|
+
const clickedElement = target as Element;
|
|
303
|
+
const cssSelector = generateSelectorForElement(clickedElement);
|
|
304
|
+
const elementDescription = getElementDescription(clickedElement);
|
|
305
|
+
|
|
306
|
+
// Extract React component metadata (component-based commenting)
|
|
307
|
+
const componentMetadata = getElementComponentMetadata(clickedElement);
|
|
308
|
+
|
|
309
|
+
const threadId = addThread(cssSelector, elementDescription, componentMetadata, xPercent, yPercent, location.pathname, detectedVersion);
|
|
44
310
|
setSelectedThreadId(threadId);
|
|
45
311
|
};
|
|
46
312
|
|
|
@@ -60,14 +326,15 @@ export const CommentOverlay: React.FunctionComponent = () => {
|
|
|
60
326
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
61
327
|
}, [commentsEnabled, location.pathname, detectedVersion]);
|
|
62
328
|
|
|
63
|
-
//
|
|
64
|
-
if (!commentsEnabled) {
|
|
329
|
+
// Show pins when comments are enabled OR when showPinsEnabled is true
|
|
330
|
+
if (!commentsEnabled && !showPinsEnabled) {
|
|
65
331
|
return null;
|
|
66
332
|
}
|
|
67
333
|
|
|
68
334
|
return (
|
|
69
335
|
<div
|
|
70
336
|
ref={overlayRef}
|
|
337
|
+
data-comment-overlay
|
|
71
338
|
style={{
|
|
72
339
|
position: 'absolute',
|
|
73
340
|
top: 0,
|
|
@@ -84,6 +351,7 @@ export const CommentOverlay: React.FunctionComponent = () => {
|
|
|
84
351
|
{currentThreads.map((thread) => (
|
|
85
352
|
<CommentPin
|
|
86
353
|
key={thread.id}
|
|
354
|
+
cssSelector={thread.cssSelector}
|
|
87
355
|
xPercent={thread.xPercent}
|
|
88
356
|
yPercent={thread.yPercent}
|
|
89
357
|
commentCount={thread.comments.length}
|