hale-commenting-system 2.1.1 → 2.2.1

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 (99) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.editorconfig +17 -0
  3. package/.eslintrc.js +75 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
  5. package/.github/workflows/ci.yaml +51 -0
  6. package/.prettierignore +1 -0
  7. package/.prettierrc +4 -0
  8. package/GITHUB_OAUTH_ENV_TEMPLATE.md +53 -0
  9. package/LICENSE +21 -0
  10. package/README.md +92 -21
  11. package/package.json +74 -50
  12. package/scripts/README.md +42 -0
  13. package/scripts/integrate.js +440 -0
  14. package/src/app/AppLayout/AppLayout.tsx +248 -0
  15. package/src/app/Comments/Comments.tsx +273 -0
  16. package/src/app/Dashboard/Dashboard.tsx +10 -0
  17. package/src/app/NotFound/NotFound.tsx +35 -0
  18. package/src/app/Settings/General/GeneralSettings.tsx +16 -0
  19. package/src/app/Settings/Profile/ProfileSettings.tsx +18 -0
  20. package/src/app/Support/Support.tsx +50 -0
  21. package/src/app/__snapshots__/app.test.tsx.snap +524 -0
  22. package/src/app/app.css +11 -0
  23. package/src/app/app.test.tsx +55 -0
  24. package/src/app/bgimages/Patternfly-Logo.svg +28 -0
  25. package/src/app/commenting-system/components/CommentOverlay.tsx +93 -0
  26. package/src/app/commenting-system/components/CommentPanel.tsx +534 -0
  27. package/src/app/commenting-system/components/CommentPin.tsx +60 -0
  28. package/src/app/commenting-system/components/DetailsTab.tsx +516 -0
  29. package/src/app/commenting-system/components/FloatingWidget.tsx +130 -0
  30. package/src/app/commenting-system/components/JiraTab.tsx +696 -0
  31. package/src/app/commenting-system/contexts/CommentContext.tsx +1033 -0
  32. package/src/app/commenting-system/contexts/GitHubAuthContext.tsx +84 -0
  33. package/{dist/index.d.ts → src/app/commenting-system/index.ts} +5 -4
  34. package/src/app/commenting-system/services/githubAdapter.ts +359 -0
  35. package/src/app/commenting-system/types/index.ts +27 -0
  36. package/src/app/commenting-system/utils/version.ts +19 -0
  37. package/src/app/index.tsx +22 -0
  38. package/src/app/routes.tsx +81 -0
  39. package/src/app/utils/useDocumentTitle.ts +13 -0
  40. package/src/favicon.png +0 -0
  41. package/src/index.html +18 -0
  42. package/src/index.tsx +25 -0
  43. package/src/test/setup.ts +33 -0
  44. package/src/typings.d.ts +12 -0
  45. package/stylePaths.js +14 -0
  46. package/tsconfig.json +34 -0
  47. package/vitest.config.ts +19 -0
  48. package/webpack.common.js +139 -0
  49. package/webpack.dev.js +318 -0
  50. package/webpack.prod.js +38 -0
  51. package/bin/detect.d.ts +0 -10
  52. package/bin/detect.js +0 -134
  53. package/bin/generators.d.ts +0 -18
  54. package/bin/generators.js +0 -193
  55. package/bin/hale-commenting.js +0 -4
  56. package/bin/index.d.ts +0 -2
  57. package/bin/index.js +0 -61
  58. package/bin/onboarding.d.ts +0 -1
  59. package/bin/onboarding.js +0 -349
  60. package/bin/postinstall.d.ts +0 -2
  61. package/bin/postinstall.js +0 -65
  62. package/bin/validators.d.ts +0 -2
  63. package/bin/validators.js +0 -66
  64. package/dist/cli/detect.d.ts +0 -10
  65. package/dist/cli/detect.js +0 -134
  66. package/dist/cli/generators.d.ts +0 -18
  67. package/dist/cli/generators.js +0 -193
  68. package/dist/cli/index.d.ts +0 -2
  69. package/dist/cli/index.js +0 -61
  70. package/dist/cli/onboarding.d.ts +0 -1
  71. package/dist/cli/onboarding.js +0 -349
  72. package/dist/cli/postinstall.d.ts +0 -2
  73. package/dist/cli/postinstall.js +0 -65
  74. package/dist/cli/validators.d.ts +0 -2
  75. package/dist/cli/validators.js +0 -66
  76. package/dist/components/CommentOverlay.d.ts +0 -2
  77. package/dist/components/CommentOverlay.js +0 -101
  78. package/dist/components/CommentPanel.d.ts +0 -6
  79. package/dist/components/CommentPanel.js +0 -334
  80. package/dist/components/CommentPin.d.ts +0 -11
  81. package/dist/components/CommentPin.js +0 -64
  82. package/dist/components/DetailsTab.d.ts +0 -2
  83. package/dist/components/DetailsTab.js +0 -380
  84. package/dist/components/FloatingWidget.d.ts +0 -8
  85. package/dist/components/FloatingWidget.js +0 -128
  86. package/dist/components/JiraTab.d.ts +0 -2
  87. package/dist/components/JiraTab.js +0 -507
  88. package/dist/contexts/CommentContext.d.ts +0 -30
  89. package/dist/contexts/CommentContext.js +0 -891
  90. package/dist/contexts/GitHubAuthContext.d.ts +0 -13
  91. package/dist/contexts/GitHubAuthContext.js +0 -96
  92. package/dist/index.js +0 -27
  93. package/dist/services/githubAdapter.d.ts +0 -56
  94. package/dist/services/githubAdapter.js +0 -321
  95. package/dist/types/index.d.ts +0 -25
  96. package/dist/types/index.js +0 -2
  97. package/dist/utils/version.d.ts +0 -1
  98. package/dist/utils/version.js +0 -23
  99. package/templates/webpack-middleware.js +0 -226
@@ -0,0 +1,273 @@
1
+ import * as React from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import {
4
+ Button,
5
+ Card,
6
+ CardBody,
7
+ InputGroup,
8
+ InputGroupItem,
9
+ InputGroupText,
10
+ PageSection,
11
+ TextInput,
12
+ Title,
13
+ } from '@patternfly/react-core';
14
+ import { AngleDownIcon, AngleRightIcon, SearchIcon } from '@patternfly/react-icons';
15
+ import { useComments } from '@app/commenting-system';
16
+
17
+ const Comments: React.FunctionComponent = () => {
18
+ const navigate = useNavigate();
19
+ const { threads, setSelectedThreadId, setCommentsEnabled, setDrawerPinnedOpen } = useComments();
20
+ const [filter, setFilter] = React.useState('');
21
+ const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});
22
+
23
+ // Dummy data so designers can see the intended table layout before any real comments exist.
24
+ const dummyThreads = React.useMemo(() => {
25
+ const now = Date.now();
26
+ return [
27
+ {
28
+ id: 'demo-thread-support-1',
29
+ route: '/support',
30
+ xPercent: 43.0,
31
+ yPercent: 68.3,
32
+ comments: [
33
+ { id: 'demo-c-1', text: 'The empty state is clear, but I’d prefer the primary CTA to be more prominent.', createdAt: new Date(now - 1000 * 60 * 55).toISOString() },
34
+ { id: 'demo-c-2', text: 'Can we add a short “How it works” blurb above the actions?', createdAt: new Date(now - 1000 * 60 * 48).toISOString() },
35
+ { id: 'demo-c-3', text: 'Spacing feels a little tight on smaller screens.', createdAt: new Date(now - 1000 * 60 * 35).toISOString() },
36
+ { id: 'demo-c-4', text: 'Love the hierarchy. Maybe add an icon to the primary action.', createdAt: new Date(now - 1000 * 60 * 22).toISOString() },
37
+ { id: 'demo-c-5', text: 'Could the secondary actions be collapsed into a kebab menu?', createdAt: new Date(now - 1000 * 60 * 12).toISOString() },
38
+ ],
39
+ },
40
+ {
41
+ id: 'demo-thread-projects-1',
42
+ route: '/projects',
43
+ xPercent: 27.5,
44
+ yPercent: 15.3,
45
+ comments: [
46
+ { id: 'demo-c-6', text: 'Filter dropdown should default to “All projects”.', createdAt: new Date(now - 1000 * 60 * 9).toISOString() },
47
+ ],
48
+ },
49
+ {
50
+ id: 'demo-thread-settings-1',
51
+ route: '/settings/general',
52
+ xPercent: 61.2,
53
+ yPercent: 42.7,
54
+ comments: [],
55
+ },
56
+ ];
57
+ }, []);
58
+
59
+ const isUsingDummyData = threads.length === 0;
60
+ const visibleThreads = isUsingDummyData ? dummyThreads : threads;
61
+
62
+ const normalizedFilter = filter.trim().toLowerCase();
63
+ const filteredThreads = visibleThreads
64
+ .filter((t) => {
65
+ if (!normalizedFilter) return true;
66
+ const haystack = `${t.route} ${t.xPercent} ${t.yPercent} ${(t.comments || []).map((c) => c.text).join(' ')}`.toLowerCase();
67
+ return haystack.includes(normalizedFilter);
68
+ })
69
+ .sort((a, b) => {
70
+ const aLast = a.comments?.[a.comments.length - 1]?.createdAt ?? '';
71
+ const bLast = b.comments?.[b.comments.length - 1]?.createdAt ?? '';
72
+ return bLast.localeCompare(aLast);
73
+ });
74
+
75
+ const toggleExpanded = (threadId: string) => {
76
+ setExpanded((prev) => ({ ...prev, [threadId]: !prev[threadId] }));
77
+ };
78
+
79
+ const goToPin = (threadId: string, route: string) => {
80
+ // Ensure visibility + open drawer on destination route
81
+ setCommentsEnabled(true);
82
+ setDrawerPinnedOpen(true);
83
+ setSelectedThreadId(threadId);
84
+ navigate(route);
85
+ };
86
+
87
+ const formatDate = (isoDate: string): string => {
88
+ if (!isoDate) return '—';
89
+ const date = new Date(isoDate);
90
+ return date.toLocaleString(undefined, {
91
+ month: 'short',
92
+ day: 'numeric',
93
+ hour: '2-digit',
94
+ minute: '2-digit',
95
+ });
96
+ };
97
+
98
+ return (
99
+ // Mark this page as "comment controls" so clicking around doesn't create pins
100
+ <PageSection data-comment-controls>
101
+ <Title headingLevel="h1" size="lg" style={{ marginBottom: 'var(--pf-t--global--spacer--sm)' }}>
102
+ View comments
103
+ </Title>
104
+ <p style={{ marginBottom: 'var(--pf-t--global--spacer--md)', color: 'var(--pf-t--global--text--color--subtle)' }}>
105
+ A <b>thread</b> is created when someone drops a pin on a screen. Expand a row to read comments, or use "Go to
106
+ pin" to jump to the exact spot.
107
+ </p>
108
+ <Card>
109
+ <CardBody>
110
+ {isUsingDummyData && (
111
+ <p
112
+ style={{
113
+ marginBottom: 'var(--pf-t--global--spacer--md)',
114
+ fontSize: 'var(--pf-t--global--font--size--sm)',
115
+ color: 'var(--pf-t--global--text--color--subtle)',
116
+ }}
117
+ >
118
+ Showing <b>sample data</b> (no real comment threads yet). Once pins/comments exist, this table will show
119
+ live data.
120
+ </p>
121
+ )}
122
+ <div style={{ display: 'flex', gap: 'var(--pf-t--global--spacer--sm)', marginBottom: 'var(--pf-t--global--spacer--md)' }}>
123
+ <InputGroup style={{ flex: 1 }}>
124
+ <InputGroupItem>
125
+ <InputGroupText>
126
+ <SearchIcon />
127
+ </InputGroupText>
128
+ </InputGroupItem>
129
+ <InputGroupItem isFill>
130
+ <TextInput
131
+ aria-label="Filter comments"
132
+ value={filter}
133
+ onChange={(_e, v) => setFilter(v)}
134
+ placeholder="Search by page, text, or coordinates…"
135
+ />
136
+ </InputGroupItem>
137
+ </InputGroup>
138
+ <InputGroupItem>
139
+ <Button variant="secondary" onClick={() => setFilter('')} isDisabled={!filter.trim()}>
140
+ Clear
141
+ </Button>
142
+ </InputGroupItem>
143
+ </div>
144
+
145
+ <div style={{ overflowX: 'auto' }}>
146
+ <table className="pf-v6-c-table pf-m-grid-md" role="grid" aria-label="All comment threads table">
147
+ <thead className="pf-v6-c-table__thead">
148
+ <tr className="pf-v6-c-table__tr">
149
+ <th className="pf-v6-c-table__th" scope="col" style={{ width: '3rem' }} />
150
+ <th className="pf-v6-c-table__th" scope="col">
151
+ Screen
152
+ </th>
153
+ <th className="pf-v6-c-table__th" scope="col">
154
+ Pin location
155
+ </th>
156
+ <th className="pf-v6-c-table__th" scope="col">
157
+ Comments
158
+ </th>
159
+ <th className="pf-v6-c-table__th" scope="col">
160
+ Last activity
161
+ </th>
162
+ <th className="pf-v6-c-table__th" scope="col">
163
+ Actions
164
+ </th>
165
+ </tr>
166
+ </thead>
167
+ <tbody className="pf-v6-c-table__tbody">
168
+ {filteredThreads.length === 0 ? (
169
+ <tr className="pf-v6-c-table__tr">
170
+ <td className="pf-v6-c-table__td" colSpan={6}>
171
+ No threads found.
172
+ </td>
173
+ </tr>
174
+ ) : (
175
+ filteredThreads.map((thread) => {
176
+ const isOpen = !!expanded[thread.id];
177
+ const last = thread.comments?.[thread.comments.length - 1]?.createdAt ?? '';
178
+ return (
179
+ <React.Fragment key={thread.id}>
180
+ <tr className="pf-v6-c-table__tr">
181
+ <td className="pf-v6-c-table__td">
182
+ <Button variant="plain" onClick={() => toggleExpanded(thread.id)} aria-label="Toggle thread">
183
+ {isOpen ? <AngleDownIcon /> : <AngleRightIcon />}
184
+ </Button>
185
+ </td>
186
+ <td className="pf-v6-c-table__td">
187
+ <Button
188
+ variant="link"
189
+ isInline
190
+ onClick={() => {
191
+ if (!isUsingDummyData) goToPin(thread.id, thread.route);
192
+ }}
193
+ isDisabled={isUsingDummyData}
194
+ >
195
+ {thread.route}
196
+ </Button>
197
+ </td>
198
+ <td className="pf-v6-c-table__td">
199
+ ({thread.xPercent.toFixed(1)}%, {thread.yPercent.toFixed(1)}%)
200
+ </td>
201
+ <td className="pf-v6-c-table__td">{thread.comments.length}</td>
202
+ <td className="pf-v6-c-table__td">{formatDate(last)}</td>
203
+ <td className="pf-v6-c-table__td">
204
+ <Button
205
+ variant="secondary"
206
+ onClick={() => {
207
+ if (!isUsingDummyData) goToPin(thread.id, thread.route);
208
+ }}
209
+ isDisabled={isUsingDummyData}
210
+ >
211
+ Go to pin
212
+ </Button>
213
+ </td>
214
+ </tr>
215
+
216
+ {isOpen && (
217
+ <tr className="pf-v6-c-table__tr">
218
+ <td className="pf-v6-c-table__td" colSpan={6}>
219
+ <div
220
+ style={{
221
+ display: 'grid',
222
+ gap: 'var(--pf-t--global--spacer--sm)',
223
+ padding: 'var(--pf-t--global--spacer--md)',
224
+ }}
225
+ >
226
+ {thread.comments.length === 0 ? (
227
+ <p style={{ color: 'var(--pf-t--global--text--color--subtle)' }}>No comments yet.</p>
228
+ ) : (
229
+ thread.comments.map((c, idx) => (
230
+ <Card key={c.id} isCompact>
231
+ <CardBody>
232
+ <div style={{ fontWeight: 'var(--pf-t--global--font--weight--bold)' }}>
233
+ Comment #{idx + 1}
234
+ </div>
235
+ <div
236
+ style={{
237
+ fontSize: 'var(--pf-t--global--font--size--sm)',
238
+ color: 'var(--pf-t--global--text--color--subtle)',
239
+ marginTop: 'var(--pf-t--global--spacer--xs)',
240
+ }}
241
+ >
242
+ @— &nbsp; {formatDate(c.createdAt)}
243
+ </div>
244
+ <div
245
+ style={{
246
+ marginTop: 'var(--pf-t--global--spacer--sm)',
247
+ whiteSpace: 'pre-wrap',
248
+ }}
249
+ >
250
+ {c.text}
251
+ </div>
252
+ </CardBody>
253
+ </Card>
254
+ ))
255
+ )}
256
+ </div>
257
+ </td>
258
+ </tr>
259
+ )}
260
+ </React.Fragment>
261
+ );
262
+ })
263
+ )}
264
+ </tbody>
265
+ </table>
266
+ </div>
267
+ </CardBody>
268
+ </Card>
269
+ </PageSection>
270
+ );
271
+ };
272
+
273
+ export { Comments };
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import { PageSection, Title } from '@patternfly/react-core';
3
+
4
+ const Dashboard: React.FunctionComponent = () => (
5
+ <PageSection hasBodyWrapper={false}>
6
+ <Title headingLevel="h1" size="lg">Dashboard Page Title!</Title>
7
+ </PageSection>
8
+ )
9
+
10
+ export { Dashboard };
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import { ExclamationTriangleIcon } from '@patternfly/react-icons';
3
+ import {
4
+ Button,
5
+ EmptyState,
6
+ EmptyStateBody,
7
+ EmptyStateFooter,
8
+ PageSection,
9
+ } from '@patternfly/react-core';
10
+ import { useNavigate } from 'react-router-dom';
11
+
12
+ const NotFound: React.FunctionComponent = () => {
13
+ function GoHomeBtn() {
14
+ const navigate = useNavigate();
15
+ function handleClick() {
16
+ navigate('/');
17
+ }
18
+ return (
19
+ <Button onClick={handleClick}>Take me home</Button>
20
+ );
21
+ }
22
+
23
+ return (
24
+ <PageSection hasBodyWrapper={false}>
25
+ <EmptyState titleText="404 Page not found" variant="full" icon={ExclamationTriangleIcon} >
26
+ <EmptyStateBody>
27
+ We didn&apos;t find a page that matches the address you navigated to.
28
+ </EmptyStateBody><EmptyStateFooter>
29
+ <GoHomeBtn />
30
+ </EmptyStateFooter></EmptyState>
31
+ </PageSection>
32
+ )
33
+ };
34
+
35
+ export { NotFound };
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { PageSection, Title } from '@patternfly/react-core';
3
+ import { useDocumentTitle } from '@app/utils/useDocumentTitle';
4
+
5
+ const GeneralSettings: React.FunctionComponent = () => {
6
+ useDocumentTitle("General Settings");
7
+ return (
8
+ <PageSection hasBodyWrapper={false}>
9
+ <Title headingLevel="h1" size="lg">
10
+ General Settings Page Title
11
+ </Title>
12
+ </PageSection>
13
+ );
14
+ }
15
+
16
+ export { GeneralSettings };
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import { PageSection, Title } from '@patternfly/react-core';
3
+ import { useDocumentTitle } from '@app/utils/useDocumentTitle';
4
+
5
+ const ProfileSettings: React.FunctionComponent = () => {
6
+ useDocumentTitle("Profile Settings");
7
+
8
+ return (
9
+ <PageSection hasBodyWrapper={false}>
10
+ <Title headingLevel="h1" size="lg">
11
+ Profile Settings Page Title
12
+ </Title>
13
+ </PageSection>
14
+ );
15
+
16
+ }
17
+
18
+ export { ProfileSettings };
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { CubesIcon } from '@patternfly/react-icons';
3
+ import {
4
+ Button,
5
+ Content,
6
+ ContentVariants,
7
+ EmptyState,
8
+ EmptyStateActions,
9
+ EmptyStateBody,
10
+ EmptyStateFooter,
11
+ EmptyStateVariant,
12
+ PageSection,
13
+ } from '@patternfly/react-core';
14
+
15
+ export interface ISupportProps {
16
+ sampleProp?: string;
17
+ }
18
+
19
+ // eslint-disable-next-line prefer-const
20
+ let Support: React.FunctionComponent<ISupportProps> = () => (
21
+ <PageSection hasBodyWrapper={false}>
22
+ <EmptyState variant={EmptyStateVariant.full} titleText="Empty State (Stub Support Module)" icon={CubesIcon}>
23
+ <EmptyStateBody>
24
+ <Content>
25
+ <Content component="p">
26
+ This represents an the empty state pattern in Patternfly. Hopefully it&apos;s simple enough to use but
27
+ flexible enough to meet a variety of needs.
28
+ </Content>
29
+ <Content component={ContentVariants.small}>
30
+ This text has overridden a css component variable to demonstrate how to apply customizations using
31
+ PatternFly&apos;s CSS tokens.
32
+ </Content>
33
+ </Content>
34
+ </EmptyStateBody>
35
+ <EmptyStateFooter>
36
+ <Button variant="primary">Primary Action</Button>
37
+ <EmptyStateActions>
38
+ <Button variant="link">Multiple</Button>
39
+ <Button variant="link">Action Buttons</Button>
40
+ <Button variant="link">Can</Button>
41
+ <Button variant="link">Go here</Button>
42
+ <Button variant="link">In the secondary</Button>
43
+ <Button variant="link">Action area</Button>
44
+ </EmptyStateActions>
45
+ </EmptyStateFooter>
46
+ </EmptyState>
47
+ </PageSection>
48
+ );
49
+
50
+ export { Support };