hale-commenting-system 2.2.97 → 3.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.
Files changed (37) hide show
  1. package/GITHUB_OAUTH_ENV_TEMPLATE.md +2 -2
  2. package/README.md +10 -1
  3. package/package.json +16 -9
  4. package/scripts/integrate.js +64 -430
  5. package/src/app/commenting-system/components/CommentOverlay.tsx +4 -3
  6. package/src/app/commenting-system/components/CommentPanel.tsx +11 -64
  7. package/src/app/commenting-system/components/FloatingWidget.tsx +105 -39
  8. package/src/app/commenting-system/contexts/CommentContext.tsx +11 -4
  9. package/src/app/commenting-system/types/index.ts +1 -0
  10. package/.claude/settings.local.json +0 -7
  11. package/.editorconfig +0 -17
  12. package/.eslintrc.js +0 -75
  13. package/.prettierignore +0 -1
  14. package/.prettierrc +0 -4
  15. package/src/app/AppLayout/AppLayout.tsx +0 -248
  16. package/src/app/Comments/Comments.tsx +0 -273
  17. package/src/app/Dashboard/Dashboard.tsx +0 -10
  18. package/src/app/NotFound/NotFound.tsx +0 -35
  19. package/src/app/Settings/General/GeneralSettings.tsx +0 -16
  20. package/src/app/Settings/Profile/ProfileSettings.tsx +0 -18
  21. package/src/app/Support/Support.tsx +0 -50
  22. package/src/app/app.css +0 -11
  23. package/src/app/bgimages/Patternfly-Logo.svg +0 -28
  24. package/src/app/index.tsx +0 -22
  25. package/src/app/routes.tsx +0 -81
  26. package/src/app/utils/useDocumentTitle.ts +0 -13
  27. package/src/favicon.png +0 -0
  28. package/src/index.html +0 -18
  29. package/src/index.tsx +0 -25
  30. package/src/test/setup.ts +0 -33
  31. package/src/typings.d.ts +0 -12
  32. package/stylePaths.js +0 -14
  33. package/tsconfig.json +0 -34
  34. package/vitest.config.ts +0 -19
  35. package/webpack.common.js +0 -139
  36. package/webpack.dev.js +0 -318
  37. package/webpack.prod.js +0 -38
@@ -6,14 +6,6 @@ import {
6
6
  Button,
7
7
  Card,
8
8
  CardBody,
9
- Drawer,
10
- DrawerActions,
11
- DrawerCloseButton,
12
- DrawerContent,
13
- DrawerContentBody,
14
- DrawerHead,
15
- DrawerPanelBody,
16
- DrawerPanelContent,
17
9
  EmptyState,
18
10
  EmptyStateBody,
19
11
  Label,
@@ -40,10 +32,6 @@ export const CommentPanel: React.FunctionComponent<CommentPanelProps> = ({ child
40
32
  getThreadsForRoute,
41
33
  selectedThreadId,
42
34
  setSelectedThreadId,
43
- drawerPinnedOpen,
44
- setDrawerPinnedOpen,
45
- floatingWidgetMode,
46
- setFloatingWidgetMode,
47
35
  addReply,
48
36
  updateComment,
49
37
  deleteComment,
@@ -59,28 +47,18 @@ export const CommentPanel: React.FunctionComponent<CommentPanelProps> = ({ child
59
47
  const [replyTextByCommentId, setReplyTextByCommentId] = React.useState<Record<string, string>>({});
60
48
  const [editingCommentId, setEditingCommentId] = React.useState<string | null>(null);
61
49
  const [editText, setEditText] = React.useState('');
62
- const drawerRef = React.useRef<HTMLSpanElement>(null);
63
50
  const [activeTabKey, setActiveTabKey] = React.useState<string | number>('comments');
64
51
 
65
52
  const currentThreads = getThreadsForRoute(location.pathname, detectedVersion);
66
53
  const selectedThread = currentThreads.find((t) => t.id === selectedThreadId);
67
- const isExpanded = !!selectedThreadId || drawerPinnedOpen || floatingWidgetMode;
68
-
69
- const onExpand = () => {
70
- drawerRef.current && drawerRef.current.focus();
71
- };
72
54
 
73
55
  React.useEffect(() => {
74
56
  if (selectedThreadId) {
75
57
  setActiveTabKey('comments');
76
- }
77
- }, [selectedThreadId]);
78
-
79
- React.useEffect(() => {
80
- if (drawerPinnedOpen && !selectedThreadId) {
58
+ } else {
81
59
  setActiveTabKey('details');
82
60
  }
83
- }, [drawerPinnedOpen, selectedThreadId]);
61
+ }, [selectedThreadId]);
84
62
 
85
63
  const handleAddComment = () => {
86
64
  if (newCommentText.trim() && selectedThread) {
@@ -150,11 +128,6 @@ export const CommentPanel: React.FunctionComponent<CommentPanelProps> = ({ child
150
128
 
151
129
  const handleClose = () => {
152
130
  setSelectedThreadId(null);
153
- if (floatingWidgetMode) {
154
- setFloatingWidgetMode(false);
155
- } else {
156
- setDrawerPinnedOpen(false);
157
- }
158
131
  setEditingCommentId(null);
159
132
  setEditText('');
160
133
  setNewCommentText('');
@@ -221,17 +194,17 @@ export const CommentPanel: React.FunctionComponent<CommentPanelProps> = ({ child
221
194
  aria-label="Hale Commenting System drawer tabs"
222
195
  >
223
196
  <Tab eventKey="details" title={<TabTitleText>Details</TabTitleText>}>
224
- <div style={{ paddingTop: '1rem' }}>
197
+ <div style={{ padding: '1rem' }}>
225
198
  <DetailsTab />
226
199
  </div>
227
200
  </Tab>
228
201
  <Tab eventKey="jira" title={<TabTitleText>Jira</TabTitleText>}>
229
- <div style={{ paddingTop: '1rem' }}>
202
+ <div style={{ padding: '1rem' }}>
230
203
  <JiraTab />
231
204
  </div>
232
205
  </Tab>
233
206
  <Tab eventKey="comments" title={<TabTitleText>Comments</TabTitleText>}>
234
- <div style={{ paddingTop: '1rem' }}>
207
+ <div style={{ padding: '1rem' }}>
235
208
  {!selectedThread ? (
236
209
  <EmptyState icon={InfoCircleIcon} titleText="No pin selected" headingLevel="h3">
237
210
  <EmptyStateBody>Select or create a comment pin to start a thread.</EmptyStateBody>
@@ -497,38 +470,12 @@ export const CommentPanel: React.FunctionComponent<CommentPanelProps> = ({ child
497
470
  </>
498
471
  );
499
472
 
500
- if (floatingWidgetMode && isExpanded) {
501
- return (
502
- <>
503
- <FloatingWidget onClose={handleClose} title="Hale Commenting System">
504
- <div style={{ padding: '1rem' }}>{panelContent}</div>
505
- </FloatingWidget>
506
- <div style={{ position: 'relative' }}>{children}</div>
507
- </>
508
- );
509
- }
510
-
511
- const drawerPanelContent = isExpanded ? (
512
- <DrawerPanelContent isResizable defaultSize={'500px'} minSize={'300px'}>
513
- <DrawerHead>
514
- <span tabIndex={isExpanded ? 0 : -1} ref={drawerRef}>
515
- <Title headingLevel="h2" size="lg">
516
- Hale Commenting System
517
- </Title>
518
- </span>
519
- <DrawerActions>
520
- <DrawerCloseButton onClick={handleClose} />
521
- </DrawerActions>
522
- </DrawerHead>
523
- <DrawerPanelBody>{panelContent}</DrawerPanelBody>
524
- </DrawerPanelContent>
525
- ) : null;
526
-
527
473
  return (
528
- <Drawer isExpanded={isExpanded} isInline onExpand={onExpand}>
529
- <DrawerContent panelContent={drawerPanelContent}>
530
- <DrawerContentBody style={{ position: 'relative' }}>{children}</DrawerContentBody>
531
- </DrawerContent>
532
- </Drawer>
474
+ <>
475
+ <FloatingWidget title="Hale Commenting System">
476
+ {panelContent}
477
+ </FloatingWidget>
478
+ <div style={{ position: 'relative' }}>{children}</div>
479
+ </>
533
480
  );
534
481
  };
@@ -1,20 +1,26 @@
1
1
  import * as React from 'react';
2
- import { Button, Card, CardBody, Title } from '@patternfly/react-core';
3
- import { GripVerticalIcon, TimesIcon, WindowMinimizeIcon } from '@patternfly/react-icons';
2
+ import { createPortal } from 'react-dom';
3
+ import { Button, Switch, Title } from '@patternfly/react-core';
4
+ import { GripVerticalIcon, WindowMinimizeIcon, GithubIcon } from '@patternfly/react-icons';
5
+ import { useComments } from '../contexts/CommentContext';
6
+ import { useGitHubAuth } from '../contexts/GitHubAuthContext';
4
7
 
5
8
  interface FloatingWidgetProps {
6
9
  children: React.ReactNode;
7
- onClose: () => void;
8
10
  title?: string;
9
11
  }
10
12
 
11
- export const FloatingWidget: React.FunctionComponent<FloatingWidgetProps> = ({ children, onClose, title = 'Hale Commenting System' }) => {
12
- const [position, setPosition] = React.useState({ x: window.innerWidth - 520, y: 100 });
13
+ export const FloatingWidget: React.FunctionComponent<FloatingWidgetProps> = ({ children, title = 'Hale Commenting System' }) => {
14
+ const [position, setPosition] = React.useState({ x: window.innerWidth - 520, y: 20 });
13
15
  const [isDragging, setIsDragging] = React.useState(false);
14
16
  const [dragOffset, setDragOffset] = React.useState({ x: 0, y: 0 });
15
17
  const [isMinimized, setIsMinimized] = React.useState(false);
18
+ const [viewportHeight, setViewportHeight] = React.useState(window.innerHeight);
16
19
  const widgetRef = React.useRef<HTMLDivElement>(null);
17
20
 
21
+ const { commentsEnabled, setCommentsEnabled } = useComments();
22
+ const { isAuthenticated, user, login, logout } = useGitHubAuth();
23
+
18
24
  const handleMouseDown = (e: React.MouseEvent) => {
19
25
  if (!widgetRef.current) return;
20
26
  const rect = widgetRef.current.getBoundingClientRect();
@@ -48,29 +54,43 @@ export const FloatingWidget: React.FunctionComponent<FloatingWidgetProps> = ({ c
48
54
  };
49
55
  }, [isDragging, dragOffset]);
50
56
 
51
- // Constrain to viewport but allow dragging header even when partially off-screen
57
+ // Update viewport height on resize to recalculate constraints
58
+ React.useEffect(() => {
59
+ const handleResize = () => {
60
+ setViewportHeight(window.innerHeight);
61
+ };
62
+
63
+ window.addEventListener('resize', handleResize);
64
+ return () => window.removeEventListener('resize', handleResize);
65
+ }, []);
66
+
67
+ // Constrain to viewport but allow moving into topbar area (just keep drag handle accessible)
52
68
  const constrainedPosition = React.useMemo(() => {
53
69
  const widgetWidth = 500;
54
- const widgetHeight = isMinimized ? 60 : 400;
70
+ // Calculate actual widget height: 80vh when expanded, or estimate ~120px when minimized
71
+ const widgetHeight = isMinimized ? 120 : viewportHeight * 0.8;
55
72
  const maxX = window.innerWidth - 50; // Allow 50px of widget to be visible for dragging
56
- const maxY = window.innerHeight - 50;
73
+ const maxY = viewportHeight - 50;
74
+ // Allow widget to move into topbar, but keep at least 60px of drag handle visible
75
+ const minY = -widgetHeight + 60;
57
76
  return {
58
77
  x: Math.max(-widgetWidth + 50, Math.min(position.x, maxX)),
59
- y: Math.max(-widgetHeight + 50, Math.min(position.y, maxY)),
78
+ y: Math.max(minY, Math.min(position.y, maxY)),
60
79
  };
61
- }, [position, isMinimized]);
80
+ }, [position, isMinimized, viewportHeight]);
62
81
 
63
- return (
82
+ const widgetContent = (
64
83
  <div
65
84
  ref={widgetRef}
85
+ data-floating-widget
66
86
  style={{
67
87
  position: 'fixed',
68
88
  left: `${constrainedPosition.x}px`,
69
89
  top: `${constrainedPosition.y}px`,
70
90
  width: '500px',
71
- height: isMinimized ? '60px' : '80vh',
91
+ height: isMinimized ? 'fit-content' : '80vh',
72
92
  maxHeight: '80vh',
73
- zIndex: 10000,
93
+ zIndex: 99999,
74
94
  boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)',
75
95
  borderRadius: 'var(--pf-t--global--border--radius--medium)',
76
96
  backgroundColor: '#ffffff',
@@ -80,43 +100,87 @@ export const FloatingWidget: React.FunctionComponent<FloatingWidgetProps> = ({ c
80
100
  }}
81
101
  >
82
102
  <div
83
- onMouseDown={handleMouseDown}
84
103
  style={{
85
- padding: '1rem',
86
104
  borderBottom: isMinimized ? 'none' : '1px solid var(--pf-t--global--border--color--default)',
87
- display: 'flex',
88
- alignItems: 'center',
89
- justifyContent: 'space-between',
90
- cursor: 'grab',
91
- userSelect: 'none',
92
105
  backgroundColor: '#ffffff',
93
106
  borderRadius: isMinimized ? 'var(--pf-t--global--border--radius--medium)' : 'var(--pf-t--global--border--radius--medium) var(--pf-t--global--border--radius--medium) 0 0',
94
107
  }}
95
108
  >
96
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flex: 1 }}>
97
- <GripVerticalIcon style={{ color: 'var(--pf-t--global--text--color--subtle)' }} />
98
- <Title headingLevel="h2" size="lg">
99
- {title}
100
- </Title>
109
+ {/* Title bar with drag handle */}
110
+ <div
111
+ onMouseDown={handleMouseDown}
112
+ style={{
113
+ padding: '1rem',
114
+ display: 'flex',
115
+ alignItems: 'center',
116
+ justifyContent: 'space-between',
117
+ cursor: 'grab',
118
+ userSelect: 'none',
119
+ }}
120
+ >
121
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flex: 1 }}>
122
+ <GripVerticalIcon style={{ color: 'var(--pf-t--global--text--color--subtle)' }} />
123
+ <Title headingLevel="h2" size="lg">
124
+ {title}
125
+ </Title>
126
+ </div>
127
+ <div style={{ display: 'flex', gap: '0.25rem' }}>
128
+ <Button
129
+ variant="plain"
130
+ icon={<WindowMinimizeIcon />}
131
+ onClick={(e) => {
132
+ e.stopPropagation();
133
+ setIsMinimized(!isMinimized);
134
+ }}
135
+ aria-label={isMinimized ? 'Maximize widget' : 'Minimize widget'}
136
+ />
137
+ </div>
101
138
  </div>
102
- <div style={{ display: 'flex', gap: '0.25rem' }}>
103
- <Button
104
- variant="plain"
105
- icon={<WindowMinimizeIcon />}
106
- onClick={(e) => {
107
- e.stopPropagation();
108
- setIsMinimized(!isMinimized);
139
+
140
+ {/* Controls row */}
141
+ {!isMinimized && (
142
+ <div
143
+ style={{
144
+ padding: '0 1rem 0.75rem 1rem',
145
+ display: 'flex',
146
+ alignItems: 'center',
147
+ gap: '1rem',
148
+ borderBottom: '1px solid var(--pf-t--global--border--color--default)',
109
149
  }}
110
- aria-label={isMinimized ? 'Maximize widget' : 'Minimize widget'}
111
- />
112
- <Button variant="plain" icon={<TimesIcon />} onClick={onClose} aria-label="Close widget" />
113
- </div>
150
+ >
151
+ <Switch
152
+ id="floating-comments-enabled-switch"
153
+ label="Enable Comments"
154
+ isChecked={commentsEnabled}
155
+ onChange={(_event, checked) => setCommentsEnabled(checked)}
156
+ aria-label="Enable or disable comments"
157
+ />
158
+ <div style={{ flex: 1 }} />
159
+ {isAuthenticated ? (
160
+ <>
161
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.25rem', fontSize: 'var(--pf-t--global--font--size--sm)' }}>
162
+ <GithubIcon />
163
+ {user?.login ? `@${user.login}` : 'Signed in'}
164
+ </span>
165
+ <Button variant="link" isInline onClick={logout} style={{ fontSize: 'var(--pf-t--global--font--size--sm)' }}>
166
+ Sign out
167
+ </Button>
168
+ </>
169
+ ) : (
170
+ <Button variant="link" isInline icon={<GithubIcon />} onClick={login} style={{ fontSize: 'var(--pf-t--global--font--size--sm)' }}>
171
+ Sign in with GitHub
172
+ </Button>
173
+ )}
174
+ </div>
175
+ )}
114
176
  </div>
115
177
  {!isMinimized && (
116
178
  <div
117
179
  style={{
118
- overflow: 'auto',
119
- flex: 1,
180
+ overflowY: 'auto',
181
+ overflowX: 'hidden',
182
+ flex: '1 1 0',
183
+ minHeight: 0,
120
184
  backgroundColor: '#ffffff',
121
185
  borderRadius: '0 0 var(--pf-t--global--border--radius--medium) var(--pf-t--global--border--radius--medium)',
122
186
  }}
@@ -126,5 +190,7 @@ export const FloatingWidget: React.FunctionComponent<FloatingWidgetProps> = ({ c
126
190
  )}
127
191
  </div>
128
192
  );
129
- };
130
193
 
194
+ // Render widget in a portal to document.body so it floats above ALL page elements
195
+ return typeof document !== 'undefined' ? createPortal(widgetContent, document.body) : null;
196
+ };
@@ -204,7 +204,9 @@ export const CommentProvider: React.FunctionComponent<{ children: React.ReactNod
204
204
  React.useEffect(() => {
205
205
  if (typeof window === 'undefined') return;
206
206
  try {
207
- window.localStorage.setItem(STORAGE_KEY, JSON.stringify(threads));
207
+ // Filter out temporary threads before saving to localStorage
208
+ const persistedThreads = threads.filter(t => !t.isTemporary);
209
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(persistedThreads));
208
210
  } catch {
209
211
  // ignore quota/serialization errors
210
212
  }
@@ -255,14 +257,17 @@ export const CommentProvider: React.FunctionComponent<{ children: React.ReactNod
255
257
  version,
256
258
  comments: [],
257
259
  provider: 'github',
258
- syncStatus: isConfigured ? 'syncing' : 'local',
260
+ syncStatus: 'local',
259
261
  status: 'open',
262
+ isTemporary: true, // Mark as temporary until first comment is added
260
263
  };
261
264
  setThreads((prev) => [...prev, newThread]);
262
265
 
263
- console.log(`📌 Thread created locally with syncStatus: ${newThread.syncStatus}`);
266
+ console.log(`📌 Thread created as temporary (will persist when first comment is added)`);
264
267
 
265
- // Background sync to GitHub (optimistic UI)
268
+ // Don't sync to GitHub yet - wait for first comment
269
+ // This prevents creating empty GitHub issues
270
+ /* Commented out - only sync when comment is added
266
271
  if (isConfigured) {
267
272
  console.log(`🔵 Creating GitHub issue for thread ${threadId}...`);
268
273
 
@@ -319,6 +324,7 @@ export const CommentProvider: React.FunctionComponent<{ children: React.ReactNod
319
324
  console.log(`📌 Thread ${threadId} syncStatus updated to: error (exception caught)`);
320
325
  });
321
326
  }
327
+ */
322
328
 
323
329
  return threadId;
324
330
  };
@@ -521,6 +527,7 @@ export const CommentProvider: React.FunctionComponent<{ children: React.ReactNod
521
527
  return {
522
528
  ...thread,
523
529
  comments: [...thread.comments, newComment],
530
+ isTemporary: undefined, // Remove temporary flag - thread now persists
524
531
  };
525
532
  }),
526
533
  );
@@ -24,4 +24,5 @@ export interface Thread {
24
24
  syncStatus?: SyncStatus;
25
25
  syncError?: string;
26
26
  status?: ThreadStatus; // open or closed (mirrors GitHub issue state)
27
+ isTemporary?: boolean; // If true, thread is not persisted until first comment is added
27
28
  }
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm run type-check:*)"
5
- ]
6
- }
7
- }
package/.editorconfig DELETED
@@ -1,17 +0,0 @@
1
- # Editor configuration, see http://editorconfig.org
2
- root = true
3
-
4
- [*]
5
- charset = utf-8
6
- indent_style = space
7
- indent_size = 2
8
- insert_final_newline = true
9
- trim_trailing_whitespace = true
10
-
11
- [*.snap]
12
- max_line_length = off
13
- trim_trailing_whitespace = false
14
-
15
- [*.md]
16
- max_line_length = off
17
- trim_trailing_whitespace = false
package/.eslintrc.js DELETED
@@ -1,75 +0,0 @@
1
- module.exports = {
2
- // tells eslint to use the TypeScript parser
3
- "parser": "@typescript-eslint/parser",
4
- // tell the TypeScript parser that we want to use JSX syntax
5
- "parserOptions": {
6
- "tsx": true,
7
- "jsx": true,
8
- "js": true,
9
- "useJSXTextNode": true,
10
- "project": "./tsconfig.json",
11
- "tsconfigRootDir": "."
12
- },
13
- // we want to use the recommended rules provided from the typescript plugin
14
- "extends": [
15
- "eslint:recommended",
16
- "plugin:react/recommended",
17
- "plugin:@typescript-eslint/recommended"
18
- ],
19
- "globals": {
20
- "window": "readonly",
21
- "describe": "readonly",
22
- "test": "readonly",
23
- "expect": "readonly",
24
- "it": "readonly",
25
- "process": "readonly",
26
- "document": "readonly",
27
- "insights": "readonly",
28
- "shallow": "readonly",
29
- "render": "readonly",
30
- "mount": "readonly"
31
- },
32
- "overrides": [
33
- {
34
- "files": ["src/**/*.ts", "src/**/*.tsx"],
35
- "parser": "@typescript-eslint/parser",
36
- "plugins": ["@typescript-eslint"],
37
- "extends": ["plugin:@typescript-eslint/recommended"],
38
- "rules": {
39
- "react/prop-types": "off",
40
- "@typescript-eslint/no-unused-vars": "error"
41
- },
42
- },
43
- ],
44
- "settings": {
45
- "react": {
46
- "version": "^16.11.0"
47
- }
48
- },
49
- // includes the typescript specific rules found here: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules
50
- "plugins": [
51
- "@typescript-eslint",
52
- "react-hooks",
53
- "eslint-plugin-react-hooks"
54
- ],
55
- "rules": {
56
- "sort-imports": [
57
- "error",
58
- {
59
- "ignoreDeclarationSort": true
60
- }
61
- ],
62
- "@typescript-eslint/explicit-function-return-type": "off",
63
- "react-hooks/rules-of-hooks": "error",
64
- "react-hooks/exhaustive-deps": "warn",
65
- "@typescript-eslint/interface-name-prefix": "off",
66
- "prettier/prettier": "off",
67
- "import/no-unresolved": "off",
68
- "import/extensions": "off",
69
- "react/prop-types": "off"
70
- },
71
- "env": {
72
- "browser": true,
73
- "node": true
74
- }
75
- }
package/.prettierignore DELETED
@@ -1 +0,0 @@
1
- package.json
package/.prettierrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "printWidth": 120
4
- }