9
9
Anchor ,
10
10
Box ,
11
11
Paper ,
12
+ Switch ,
13
+ Tooltip ,
12
14
} from "@mantine/core" ;
13
15
import { differenceInSeconds } from "date-fns" ;
14
16
import React , { useCallback , useState } from "react" ;
@@ -23,6 +25,8 @@ import {
23
25
} from "../api/hooks" ;
24
26
import { useUser } from "../auth" ;
25
27
import useConfirm from "../hooks/useConfirm" ;
28
+ import useToggle from "../hooks/useToggle" ;
29
+
26
30
import { Answer , AnswerSection } from "../interfaces" ;
27
31
import { copy } from "../utils/clipboard" ;
28
32
import CodeBlock from "./code-block" ;
@@ -88,27 +92,33 @@ const AnswerComponent: React.FC<Props> = ({
88
92
89
93
const [ draftText , setDraftText ] = useState ( "" ) ;
90
94
const [ undoStack , setUndoStack ] = useState < UndoStack > ( { prev : [ ] , next : [ ] } ) ;
95
+ const [ answerIsAnonymous , toggleAnonymity ] = useToggle ( false ) ;
96
+ const [ hasCommentDraft , setHasCommentDraft ] = useState ( false ) ;
97
+
91
98
const startEdit = useCallback ( ( ) => {
92
99
setDraftText ( answer ?. text ?? "" ) ;
100
+ if ( answer ?. isAnonymous ) {
101
+ toggleAnonymity ( true ) ;
102
+ }
93
103
setEditing ( true ) ;
94
- } , [ answer ] ) ;
104
+ } , [ answer , toggleAnonymity ] ) ;
95
105
const onCancel = useCallback ( ( ) => {
96
106
setEditing ( false ) ;
97
107
if ( answer === undefined && onDelete ) onDelete ( ) ;
98
108
} , [ onDelete , answer ] ) ;
99
109
const save = useCallback ( ( ) => {
100
- if ( section ) update ( section . oid , draftText ) ;
101
- } , [ section , draftText , update ] ) ;
110
+ if ( section ) update ( section . oid , draftText , answerIsAnonymous ) ;
111
+ } , [ section , draftText , update , answerIsAnonymous ] ) ;
102
112
const remove = useCallback ( ( ) => {
103
113
if ( answer ) confirm ( "Remove answer?" , ( ) => removeAnswer ( answer . oid ) ) ;
104
114
} , [ confirm , removeAnswer , answer ] ) ;
105
- const [ hasCommentDraft , setHasCommentDraft ] = useState ( false ) ;
106
115
107
116
const flaggedLoading = setFlaggedLoading || resetFlaggedLoading ;
108
117
const canEdit = section && onSectionChanged && ( answer ?. canEdit || false ) ;
109
118
const canRemove =
110
119
section && onSectionChanged && ( isAdmin || answer ?. canEdit || false ) ;
111
120
const { username } = useUser ( ) ! ;
121
+
112
122
return (
113
123
< >
114
124
{ modals }
@@ -131,18 +141,44 @@ const AnswerComponent: React.FC<Props> = ({
131
141
</ Text >
132
142
</ Link >
133
143
) }
134
- < Anchor
135
- component = { Link }
136
- to = { `/user/${ answer ?. authorId ?? username } ` }
137
- className = { displayNameClasses . shrinkableDisplayName }
138
- >
139
- < Text fw = { 700 } component = "span" >
140
- { answer ?. authorDisplayName ?? "(Draft)" }
141
- </ Text >
142
- < Text ml = "0.3em" c = "dimmed" component = "span" >
143
- @{ answer ?. authorId ?? username }
144
- </ Text >
145
- </ Anchor >
144
+ { answer ?. isAnonymous ? (
145
+ isAdmin ? (
146
+ // Admin view of anonymous posts - clickable
147
+ < Anchor
148
+ component = { Link }
149
+ to = { `/user/${ answer . authorId } ` }
150
+ className = { displayNameClasses . shrinkableDisplayName }
151
+ >
152
+ < Text fw = { 700 } component = "span" >
153
+ { answer . authorDisplayName } < Text c = "dimmed" component = "span" > (Posted anonymously)</ Text >
154
+ </ Text >
155
+ </ Anchor >
156
+ ) : answer ?. canEdit ? (
157
+ // User's own anonymous post
158
+ < Text fw = { 700 } component = "span" >
159
+ { answer . authorDisplayName } < Text c = "dimmed" component = "span" > (Your anonymous post)</ Text >
160
+ </ Text >
161
+ ) : (
162
+ // Regular user view of anonymous posts - not clickable
163
+ < Text fw = { 700 } component = "span" >
164
+ { answer . authorDisplayName }
165
+ </ Text >
166
+ )
167
+ ) : (
168
+ // Regular non-anonymous posts
169
+ < Anchor
170
+ component = { Link }
171
+ to = { `/user/${ answer ?. authorId ?? username } ` }
172
+ className = { displayNameClasses . shrinkableDisplayName }
173
+ >
174
+ < Text fw = { 700 } component = "span" >
175
+ { answer ?. authorDisplayName ?? "(Draft)" }
176
+ </ Text >
177
+ < Text ml = "0.3em" c = "dimmed" component = "span" >
178
+ @{ answer ?. authorId ?? username }
179
+ </ Text >
180
+ </ Anchor >
181
+ ) }
146
182
< Text c = "dimmed" mx = { 6 } component = "span" >
147
183
·
148
184
</ Text >
@@ -297,6 +333,26 @@ const AnswerComponent: React.FC<Props> = ({
297
333
</ Card . Section >
298
334
) }
299
335
< Group justify = "right" >
336
+ { ( answer === undefined || editing ) && (
337
+ < Tooltip
338
+ label = "Your answer will appear as 'Anonymous' to regular users, but admins will still be able to see your username. Anonymous answers won't appear on your public profile."
339
+ multiline
340
+ maw = { 300 }
341
+ withArrow
342
+ withinPortal
343
+ >
344
+ < div >
345
+ < Switch
346
+ label = { answer ?. isAnonymous ? "Post Anonymously (currently anonymous)" : "Post Anonymously" }
347
+ onChange = { ( ) => {
348
+ console . log ( "Toggling anonymity" ) ;
349
+ toggleAnonymity ( ) ;
350
+ } }
351
+ checked = { answerIsAnonymous }
352
+ />
353
+ </ div >
354
+ </ Tooltip >
355
+ ) }
300
356
{ ( answer === undefined || editing ) && (
301
357
< Button
302
358
size = "sm"
@@ -322,69 +378,69 @@ const AnswerComponent: React.FC<Props> = ({
322
378
{ onSectionChanged && ! editing && (
323
379
< Flex align = "center" >
324
380
{ answer !== undefined && (
325
- < Button . Group >
326
- < Button
327
- size = "sm"
328
- onClick = { ( ) => setHasCommentDraft ( true ) }
329
- leftSection = { < IconPlus /> }
330
- disabled = { hasCommentDraft }
331
- color = "dark"
332
- >
333
- Add Comment
334
- </ Button >
335
- < Menu withinPortal >
336
- < Menu . Target >
337
- < Button leftSection = { < IconDots /> } color = "dark" > More</ Button >
338
- </ Menu . Target >
339
- < Menu . Dropdown >
340
- { answer . flagged === 0 && (
341
- < Menu . Item
342
- leftSection = { < IconFlag /> }
343
- onClick = { ( ) => setFlagged ( answer . oid , true ) }
344
- >
345
- Flag as Inappropriate
346
- </ Menu . Item >
347
- ) }
348
- < Menu . Item
349
- leftSection = { < IconLink /> }
350
- onClick = { ( ) =>
351
- copy (
352
- `${ document . location . origin } /exams/${ answer . filename } #${ answer . longId } ` ,
353
- )
354
- }
355
- >
356
- Copy Permalink
357
- </ Menu . Item >
358
- { isAdmin && answer . flagged > 0 && (
381
+ < Button . Group >
382
+ < Button
383
+ size = "sm"
384
+ onClick = { ( ) => setHasCommentDraft ( true ) }
385
+ leftSection = { < IconPlus /> }
386
+ disabled = { hasCommentDraft }
387
+ color = "dark"
388
+ >
389
+ Add Comment
390
+ </ Button >
391
+ < Menu withinPortal >
392
+ < Menu . Target >
393
+ < Button leftSection = { < IconDots /> } color = "dark" > More</ Button >
394
+ </ Menu . Target >
395
+ < Menu . Dropdown >
396
+ { answer . flagged === 0 && (
397
+ < Menu . Item
398
+ leftSection = { < IconFlag /> }
399
+ onClick = { ( ) => setFlagged ( answer . oid , true ) }
400
+ >
401
+ Flag as Inappropriate
402
+ </ Menu . Item >
403
+ ) }
359
404
< Menu . Item
360
- leftSection = { < IconFlag /> }
361
- onClick = { ( ) => resetFlagged ( answer . oid ) }
405
+ leftSection = { < IconLink /> }
406
+ onClick = { ( ) =>
407
+ copy (
408
+ `${ document . location . origin } /exams/${ answer . filename } #${ answer . longId } ` ,
409
+ )
410
+ }
362
411
>
363
- Remove all inappropriate flags
412
+ Copy Permalink
364
413
</ Menu . Item >
365
- ) }
366
- { ! editing && canEdit && (
414
+ { isAdmin && answer . flagged > 0 && (
415
+ < Menu . Item
416
+ leftSection = { < IconFlag /> }
417
+ onClick = { ( ) => resetFlagged ( answer . oid ) }
418
+ >
419
+ Remove all inappropriate flags
420
+ </ Menu . Item >
421
+ ) }
422
+ { ! editing && canEdit && (
423
+ < Menu . Item
424
+ leftSection = { < IconEdit /> }
425
+ onClick = { startEdit }
426
+ >
427
+ Edit
428
+ </ Menu . Item >
429
+ ) }
430
+ { answer && canRemove && (
431
+ < Menu . Item leftSection = { < IconTrash /> } onClick = { remove } >
432
+ Delete
433
+ </ Menu . Item >
434
+ ) }
367
435
< Menu . Item
368
- leftSection = { < IconEdit /> }
369
- onClick = { startEdit }
436
+ leftSection = { < IconCode /> }
437
+ onClick = { toggleViewSource }
370
438
>
371
- Edit
372
- </ Menu . Item >
373
- ) }
374
- { answer && canRemove && (
375
- < Menu . Item leftSection = { < IconTrash /> } onClick = { remove } >
376
- Delete
439
+ Toggle Source Code Mode
377
440
</ Menu . Item >
378
- ) }
379
- < Menu . Item
380
- leftSection = { < IconCode /> }
381
- onClick = { toggleViewSource }
382
- >
383
- Toggle Source Code Mode
384
- </ Menu . Item >
385
- </ Menu . Dropdown >
386
- </ Menu >
387
- </ Button . Group >
441
+ </ Menu . Dropdown >
442
+ </ Menu >
443
+ </ Button . Group >
388
444
) }
389
445
</ Flex >
390
446
) }
@@ -405,4 +461,4 @@ const AnswerComponent: React.FC<Props> = ({
405
461
) ;
406
462
} ;
407
463
408
- export default AnswerComponent ;
464
+ export default AnswerComponent ;
0 commit comments