@igstack/app-catalog-frontend-core 6.9.0 → 6.10.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.
- package/dist/esm/__tests__/modules/appCatalog/ui/detail/CommunitySection.feed.test.d.ts +0 -0
- package/dist/esm/__tests__/modules/appCatalog/ui/detail/CommunitySection.help.test.d.ts +0 -0
- package/dist/esm/__tests__/modules/appCatalog/ui/detail/CommunitySection.input.test.d.ts +0 -0
- package/dist/esm/__tests__/modules/appCatalog/ui/detail/composeComments.test.d.ts +1 -0
- package/dist/esm/__tests__/ui/detail/CommunityInput.test.d.ts +1 -0
- package/dist/esm/modules/appCatalog/hooks/useUpdateApp.d.ts +8 -0
- package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js +1 -0
- package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js +237 -218
- package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.d.ts +17 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.js +187 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.d.ts +9 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.js +91 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.d.ts +17 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.js +328 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.d.ts +15 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.js +26 -0
- package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.d.ts +8 -19
- package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js +49 -37
- package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/detail/communityDraft.d.ts +30 -0
- package/dist/esm/modules/appCatalog/ui/detail/communityDraft.js +50 -0
- package/dist/esm/modules/appCatalog/ui/detail/communityDraft.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/communityError.d.ts +6 -0
- package/dist/esm/modules/appCatalog/ui/detail/communityError.js +8 -0
- package/dist/esm/modules/appCatalog/ui/detail/communityError.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/composeComments.d.ts +27 -0
- package/dist/esm/modules/appCatalog/ui/detail/composeComments.js +66 -0
- package/dist/esm/modules/appCatalog/ui/detail/composeComments.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/detail/usePendingCount.d.ts +11 -0
- package/dist/esm/modules/appCatalog/ui/detail/usePendingCount.js +15 -0
- package/dist/esm/modules/appCatalog/ui/detail/usePendingCount.js.map +1 -0
- package/dist/esm/modules/extensions/features/resourceDetail.d.ts +38 -0
- package/dist/esm/modules/extensions/features/resourceDetail.js +4 -0
- package/dist/esm/modules/extensions/features/resourceDetail.js.map +1 -1
- package/dist/esm/modules/extensions/index.d.ts +1 -1
- package/dist/esm/modules/extensions/index.js.map +1 -1
- package/dist/index.css +501 -49
- package/package.json +4 -4
- package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.appSwitch.test.tsx +5 -2
- package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.enter.test.tsx +5 -2
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.background.test.tsx +9 -10
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.behindThisCard.test.tsx +402 -0
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.doorAndTabs.test.tsx +8 -4
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.escape.test.tsx +5 -2
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.knowMore.test.tsx +175 -0
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.sectionOrder.test.tsx +5 -2
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.subResourceTab.test.tsx +5 -2
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.tags.test.tsx +5 -2
- package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.feed.test.tsx +281 -0
- package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.help.test.tsx +179 -0
- package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.input.test.tsx +423 -0
- package/src/__tests__/modules/appCatalog/ui/detail/composeComments.test.ts +170 -0
- package/src/__tests__/ui/detail/CommunityInput.test.tsx +39 -0
- package/src/index.css +501 -49
- package/src/modules/appCatalog/ui/detail/AppDetails.tsx +292 -289
- package/src/modules/appCatalog/ui/detail/CommentsFeed.tsx +254 -0
- package/src/modules/appCatalog/ui/detail/CommentsHelp.tsx +102 -0
- package/src/modules/appCatalog/ui/detail/CommunityInput.tsx +419 -0
- package/src/modules/appCatalog/ui/detail/CommunitySection.tsx +41 -0
- package/src/modules/appCatalog/ui/detail/DetailTabs.tsx +77 -71
- package/src/modules/appCatalog/ui/detail/communityDraft.ts +80 -0
- package/src/modules/appCatalog/ui/detail/communityError.ts +10 -0
- package/src/modules/appCatalog/ui/detail/composeComments.ts +103 -0
- package/src/modules/appCatalog/ui/detail/usePendingCount.ts +26 -0
- package/src/modules/extensions/features/resourceDetail.ts +28 -0
- package/src/modules/extensions/index.ts +2 -0
- package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.d.ts +0 -13
- package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.js +0 -433
- package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.js.map +0 -1
- package/src/__tests__/modules/appCatalog/ui/detail/FeedbackSection.emptyState.test.tsx +0 -181
- package/src/__tests__/modules/appCatalog/ui/detail/FeedbackSection.reviewStatus.test.tsx +0 -131
- package/src/modules/appCatalog/ui/detail/FeedbackSection.tsx +0 -605
- /package/dist/esm/__tests__/modules/appCatalog/ui/detail/{FeedbackSection.emptyState.test.d.ts → AppDetails.behindThisCard.test.d.ts} +0 -0
- /package/dist/esm/__tests__/modules/appCatalog/ui/detail/{FeedbackSection.reviewStatus.test.d.ts → AppDetails.knowMore.test.d.ts} +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Claim,
|
|
3
|
+
FeedbackView as Note,
|
|
4
|
+
} from '@igstack/app-catalog-backend-core'
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
import { useTRPC } from '~/api/infra/trpc'
|
|
8
|
+
import { cn } from '~/lib/utils'
|
|
9
|
+
import { Button } from '~/ui/button'
|
|
10
|
+
import { formatRelativeTime } from '../../utils/formatRelativeTime'
|
|
11
|
+
import { attachmentUrl } from './communityDraft'
|
|
12
|
+
import { formatCommunityError } from './communityError'
|
|
13
|
+
import { composeComments } from './composeComments'
|
|
14
|
+
import type { CommentEntry } from './composeComments'
|
|
15
|
+
|
|
16
|
+
/** Same shape the input primitive uses, minus the fixed height. */
|
|
17
|
+
const FIELD_CLASSES =
|
|
18
|
+
'placeholder:text-muted-foreground dark:bg-input/30 border-input w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50'
|
|
19
|
+
|
|
20
|
+
const BADGES = {
|
|
21
|
+
pending: { text: 'Pending', className: 'is-pending' },
|
|
22
|
+
used: { text: 'Used as source', className: 'is-used' },
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Screenshots sent with a note: a thumbnail strip under the words. The words
|
|
27
|
+
* are the substance and a screenshot is the evidence, so it should be
|
|
28
|
+
* glanceable and openable, not dominate the row.
|
|
29
|
+
*/
|
|
30
|
+
function AttachmentStrip({ ids }: { ids: string[] }) {
|
|
31
|
+
if (ids.length === 0) return null
|
|
32
|
+
return (
|
|
33
|
+
<ul className="community-shots">
|
|
34
|
+
{ids.map((id) => (
|
|
35
|
+
<li key={id}>
|
|
36
|
+
<a href={attachmentUrl(id)} target="_blank" rel="noreferrer">
|
|
37
|
+
<img
|
|
38
|
+
src={attachmentUrl(id)}
|
|
39
|
+
alt="Attached screenshot"
|
|
40
|
+
loading="lazy"
|
|
41
|
+
/>
|
|
42
|
+
</a>
|
|
43
|
+
</li>
|
|
44
|
+
))}
|
|
45
|
+
</ul>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Who, when and where it stands, on one small line above the words. */
|
|
50
|
+
function Meta({ entry, edited }: { entry: CommentEntry; edited: boolean }) {
|
|
51
|
+
const badge = entry.badge && BADGES[entry.badge]
|
|
52
|
+
return (
|
|
53
|
+
<p className="comment__meta">
|
|
54
|
+
{entry.who && (
|
|
55
|
+
<>
|
|
56
|
+
<span className="comment__who">{entry.who}</span>
|
|
57
|
+
{' · '}
|
|
58
|
+
</>
|
|
59
|
+
)}
|
|
60
|
+
<time dateTime={entry.at} title={entry.at}>
|
|
61
|
+
{formatRelativeTime(entry.at)}
|
|
62
|
+
</time>
|
|
63
|
+
{edited && ' (edited)'}
|
|
64
|
+
{badge && (
|
|
65
|
+
<span
|
|
66
|
+
className={cn('comment__badge', badge.className)}
|
|
67
|
+
data-badge={entry.badge}
|
|
68
|
+
>
|
|
69
|
+
{badge.text}
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</p>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Everything already said about the entry, under the input: one feed, newest
|
|
78
|
+
* first, one entry per visitor note. See `composeComments` for how claims fold
|
|
79
|
+
* into the notes they came from.
|
|
80
|
+
*
|
|
81
|
+
* Nobody logs in, so the server attributes each note to a pseudonym from an
|
|
82
|
+
* httpOnly cookie, and that cookie is what authorises editing (for an hour) or
|
|
83
|
+
* withdrawing (until someone reviews it). `canEditUntil` arrives as an absolute
|
|
84
|
+
* instant so a skewed client clock cannot show a control the server would refuse.
|
|
85
|
+
*/
|
|
86
|
+
export function CommentsFeed({
|
|
87
|
+
appSlug,
|
|
88
|
+
claims,
|
|
89
|
+
labelledBy,
|
|
90
|
+
}: {
|
|
91
|
+
appSlug: string
|
|
92
|
+
claims: Claim[]
|
|
93
|
+
/** Id of the heading that names the list. */
|
|
94
|
+
labelledBy: string
|
|
95
|
+
}) {
|
|
96
|
+
const trpc = useTRPC()
|
|
97
|
+
const queryClient = useQueryClient()
|
|
98
|
+
const listOptions = trpc.feedback.list.queryOptions({ resourceSlug: appSlug })
|
|
99
|
+
const { data, error, isPending } = useQuery(listOptions)
|
|
100
|
+
const [editingId, setEditingId] = React.useState<string | null>(null)
|
|
101
|
+
const [editDraft, setEditDraft] = React.useState('')
|
|
102
|
+
|
|
103
|
+
const refresh = () =>
|
|
104
|
+
queryClient.invalidateQueries({ queryKey: listOptions.queryKey })
|
|
105
|
+
const edit = useMutation(
|
|
106
|
+
trpc.feedback.edit.mutationOptions({
|
|
107
|
+
onSuccess: () => {
|
|
108
|
+
setEditingId(null)
|
|
109
|
+
void refresh()
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
)
|
|
113
|
+
const dismiss = useMutation(
|
|
114
|
+
trpc.feedback.dismiss.mutationOptions({ onSuccess: () => void refresh() }),
|
|
115
|
+
)
|
|
116
|
+
const saveEdit = (id: string) => {
|
|
117
|
+
const body = editDraft.trim()
|
|
118
|
+
if (body) edit.mutate({ id, body })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const entries =
|
|
122
|
+
data || error ? composeComments(data?.items ?? [], claims) : []
|
|
123
|
+
const failure = error ?? edit.error ?? dismiss.error
|
|
124
|
+
|
|
125
|
+
const noteBody = (note: Note) => {
|
|
126
|
+
const canEdit =
|
|
127
|
+
note.isMine &&
|
|
128
|
+
!!note.canEditUntil &&
|
|
129
|
+
Date.parse(note.canEditUntil) > Date.now()
|
|
130
|
+
// Withdrawing stays available for as long as nobody has reviewed it.
|
|
131
|
+
const canDismiss = note.isMine && note.status === null
|
|
132
|
+
const editing = editingId === note.id
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
{editing ? (
|
|
136
|
+
<div className="mt-1 space-y-2">
|
|
137
|
+
<textarea
|
|
138
|
+
autoFocus
|
|
139
|
+
rows={3}
|
|
140
|
+
className={FIELD_CLASSES}
|
|
141
|
+
aria-label="Edit your note"
|
|
142
|
+
value={editDraft}
|
|
143
|
+
onChange={(e) => {
|
|
144
|
+
setEditDraft(e.target.value)
|
|
145
|
+
if (edit.isError) edit.reset()
|
|
146
|
+
}}
|
|
147
|
+
onKeyDown={(e) => {
|
|
148
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
149
|
+
e.preventDefault()
|
|
150
|
+
saveEdit(note.id)
|
|
151
|
+
}
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
<div className="flex gap-2">
|
|
155
|
+
<Button
|
|
156
|
+
type="button"
|
|
157
|
+
size="sm"
|
|
158
|
+
disabled={!editDraft.trim() || edit.isPending}
|
|
159
|
+
onClick={() => saveEdit(note.id)}
|
|
160
|
+
>
|
|
161
|
+
Save
|
|
162
|
+
</Button>
|
|
163
|
+
<Button
|
|
164
|
+
type="button"
|
|
165
|
+
size="sm"
|
|
166
|
+
variant="ghost"
|
|
167
|
+
onClick={() => setEditingId(null)}
|
|
168
|
+
>
|
|
169
|
+
Cancel
|
|
170
|
+
</Button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
) : (
|
|
174
|
+
<p
|
|
175
|
+
data-feedback-body=""
|
|
176
|
+
className={cn('comment__text', !note.body && 'is-empty')}
|
|
177
|
+
>
|
|
178
|
+
{note.body ??
|
|
179
|
+
(note.attachmentIds.length > 0
|
|
180
|
+
? 'Screenshot only — no words added.'
|
|
181
|
+
: 'Flagged as out of date — no detail given.')}
|
|
182
|
+
</p>
|
|
183
|
+
)}
|
|
184
|
+
<AttachmentStrip ids={note.attachmentIds} />
|
|
185
|
+
{note.reviewerReply && (
|
|
186
|
+
<p className="comment__reply">↳ {note.reviewerReply}</p>
|
|
187
|
+
)}
|
|
188
|
+
{!editing && (canEdit || canDismiss) && (
|
|
189
|
+
<p className="comment__actions">
|
|
190
|
+
{canEdit && (
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onClick={() => {
|
|
194
|
+
setEditingId(note.id)
|
|
195
|
+
setEditDraft(note.body ?? '')
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
Edit
|
|
199
|
+
</button>
|
|
200
|
+
)}
|
|
201
|
+
{canDismiss && (
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
className="is-destructive"
|
|
205
|
+
onClick={() => dismiss.mutate({ id: note.id })}
|
|
206
|
+
>
|
|
207
|
+
Dismiss
|
|
208
|
+
</button>
|
|
209
|
+
)}
|
|
210
|
+
</p>
|
|
211
|
+
)}
|
|
212
|
+
</>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
{isPending ? (
|
|
219
|
+
<div
|
|
220
|
+
className="comments-skeleton"
|
|
221
|
+
aria-busy="true"
|
|
222
|
+
aria-labelledby={labelledBy}
|
|
223
|
+
>
|
|
224
|
+
<span className="sr-only" role="status">
|
|
225
|
+
Loading comments…
|
|
226
|
+
</span>
|
|
227
|
+
<div className="comments-skeleton__item" />
|
|
228
|
+
<div className="comments-skeleton__item" />
|
|
229
|
+
<div className="comments-skeleton__item" />
|
|
230
|
+
</div>
|
|
231
|
+
) : entries.length > 0 ? (
|
|
232
|
+
<ul className="comments" aria-labelledby={labelledBy}>
|
|
233
|
+
{entries.map((entry) => (
|
|
234
|
+
<li key={entry.key} className="comment">
|
|
235
|
+
<Meta entry={entry} edited={!!entry.note?.editedAt} />
|
|
236
|
+
{entry.note ? (
|
|
237
|
+
noteBody(entry.note)
|
|
238
|
+
) : (
|
|
239
|
+
<p className="comment__text">{entry.text}</p>
|
|
240
|
+
)}
|
|
241
|
+
</li>
|
|
242
|
+
))}
|
|
243
|
+
</ul>
|
|
244
|
+
) : (
|
|
245
|
+
<p className="comments-empty">
|
|
246
|
+
No comments yet. Be the first to share what you know.
|
|
247
|
+
</p>
|
|
248
|
+
)}
|
|
249
|
+
{failure && (
|
|
250
|
+
<p className="know-more-error">{formatCommunityError(failure)}</p>
|
|
251
|
+
)}
|
|
252
|
+
</>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useDismissable } from '~/modules/dismiss/DismissStack'
|
|
3
|
+
import { ResourceDetailFeedbackNote, useSlotFilled } from '~/modules/extensions'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `?` beside "Comments": what the input takes, and what becomes of a note.
|
|
7
|
+
* Folded away so the input stays one line; Esc and a click outside close it.
|
|
8
|
+
* While closed it is not a dismiss layer, so Esc still reaches the card.
|
|
9
|
+
*/
|
|
10
|
+
export function CommentsHelp({
|
|
11
|
+
appSlug,
|
|
12
|
+
appName,
|
|
13
|
+
}: {
|
|
14
|
+
appSlug: string
|
|
15
|
+
appName: string
|
|
16
|
+
}) {
|
|
17
|
+
const [open, setOpen] = React.useState(false)
|
|
18
|
+
const buttonRef = React.useRef<HTMLButtonElement>(null)
|
|
19
|
+
const popoverRef = React.useRef<HTMLDivElement>(null)
|
|
20
|
+
const id = React.useId()
|
|
21
|
+
const hasPluginNote = useSlotFilled('resourceDetailFeedbackNote')
|
|
22
|
+
|
|
23
|
+
useDismissable(
|
|
24
|
+
popoverRef,
|
|
25
|
+
() => {
|
|
26
|
+
setOpen(false)
|
|
27
|
+
buttonRef.current?.focus()
|
|
28
|
+
},
|
|
29
|
+
{ enabled: open },
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (!open) return
|
|
34
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
35
|
+
const target = e.target as Node
|
|
36
|
+
if (
|
|
37
|
+
popoverRef.current?.contains(target) ||
|
|
38
|
+
buttonRef.current?.contains(target)
|
|
39
|
+
)
|
|
40
|
+
return
|
|
41
|
+
setOpen(false)
|
|
42
|
+
}
|
|
43
|
+
document.addEventListener('pointerdown', onPointerDown)
|
|
44
|
+
return () => document.removeEventListener('pointerdown', onPointerDown)
|
|
45
|
+
}, [open])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<button
|
|
50
|
+
ref={buttonRef}
|
|
51
|
+
type="button"
|
|
52
|
+
className="comments-help-button"
|
|
53
|
+
aria-label="How comments work"
|
|
54
|
+
aria-expanded={open}
|
|
55
|
+
aria-controls={id}
|
|
56
|
+
onClick={() => setOpen((v) => !v)}
|
|
57
|
+
>
|
|
58
|
+
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
|
|
59
|
+
<circle
|
|
60
|
+
cx="8"
|
|
61
|
+
cy="8"
|
|
62
|
+
r="6.75"
|
|
63
|
+
fill="none"
|
|
64
|
+
stroke="currentColor"
|
|
65
|
+
strokeWidth="1.25"
|
|
66
|
+
/>
|
|
67
|
+
<path
|
|
68
|
+
d="M6.25 6.3a1.8 1.8 0 0 1 3.5.55c0 1.2-1.75 1.45-1.75 2.6"
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
strokeWidth="1.25"
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
/>
|
|
74
|
+
<circle cx="8" cy="11.4" r="0.8" fill="currentColor" />
|
|
75
|
+
</svg>
|
|
76
|
+
</button>
|
|
77
|
+
<div ref={popoverRef} id={id} className="comments-help" hidden={!open}>
|
|
78
|
+
<p className="comments-help__title">How comments work</p>
|
|
79
|
+
<ul>
|
|
80
|
+
<li>
|
|
81
|
+
Paste a link — a wiki page, a doc or a ticket that explains
|
|
82
|
+
something about this app.
|
|
83
|
+
</li>
|
|
84
|
+
<li>
|
|
85
|
+
Fix something — "Access is approved by your team lead, not IT."
|
|
86
|
+
</li>
|
|
87
|
+
<li>Ask a question — "Can contractors get access?"</li>
|
|
88
|
+
<li>Attach a screenshot — paste it, drop it or use the paperclip.</li>
|
|
89
|
+
</ul>
|
|
90
|
+
<p className="comments-help__note">
|
|
91
|
+
<ResourceDetailFeedbackNote appSlug={appSlug} appName={appName} />
|
|
92
|
+
{!hasPluginNote && (
|
|
93
|
+
<>
|
|
94
|
+
Every note is reviewed. Facts that check out update the card and
|
|
95
|
+
are marked Used as source.
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
</>
|
|
101
|
+
)
|
|
102
|
+
}
|