1
+ import * as React from 'react' ;
2
+ import { useContext , useState , useEffect , useRef } from 'react' ;
3
+
4
+ import Markdown from './markdown' ;
5
+ import { Spaced , nbsp } from './space' ;
6
+ import { Avatar , AuthorLink } from './user' ;
7
+ import Timestamp from './timestamp' ;
8
+ import { Comment } from '../src/common/comment' ;
9
+ import { PullRequest } from './cache' ;
10
+ import PullRequestContext from './context' ;
11
+ import { editIcon , deleteIcon } from './icon' ;
12
+
13
+ export type Props = Partial < Comment & PullRequest > & {
14
+ headerInEditMode ?: boolean
15
+ isPRDescription ?: boolean
16
+ } ;
17
+
18
+ export function CommentView ( comment : Props ) {
19
+ const { id, pullRequestReviewId, canEdit, canDelete, bodyHTML, body, isPRDescription } = comment ;
20
+ const [ bodyMd , setBodyMd ] = useState ( body ) ;
21
+ const { deleteComment, editComment, setDescription, pr } = useContext ( PullRequestContext ) ;
22
+ const currentDraft = pr . pendingCommentDrafts && pr . pendingCommentDrafts [ id ] ;
23
+ const [ inEditMode , setEditMode ] = useState ( ! ! currentDraft ) ;
24
+ const [ showActionBar , setShowActionBar ] = useState ( false ) ;
25
+
26
+ useEffect ( ( ) => {
27
+ if ( body !== bodyMd ) {
28
+ setBodyMd ( body ) ;
29
+ }
30
+ } , [ body ] ) ;
31
+
32
+ if ( inEditMode ) {
33
+ return React . cloneElement (
34
+ comment . headerInEditMode
35
+ ? < CommentBox for = { comment } /> : < > </ > , { } , [
36
+ < EditComment id = { id }
37
+ body = { currentDraft || body }
38
+ onCancel = {
39
+ ( ) => {
40
+ if ( pr . pendingCommentDrafts ) {
41
+ delete pr . pendingCommentDrafts [ id ] ;
42
+ }
43
+ setEditMode ( false ) ;
44
+ }
45
+ }
46
+ onSave = {
47
+ async text => {
48
+ try {
49
+ if ( isPRDescription ) {
50
+ await setDescription ( text ) ;
51
+ } else {
52
+ await editComment ( { comment : comment as Comment , text } ) ;
53
+ }
54
+ setBodyMd ( text ) ;
55
+ } finally {
56
+ setEditMode ( false ) ;
57
+ }
58
+ }
59
+ } />
60
+ ] ) ;
61
+ }
62
+
63
+ return < CommentBox
64
+ for = { comment }
65
+ onMouseEnter = { ( ) => setShowActionBar ( true ) }
66
+ onMouseLeave = { ( ) => setShowActionBar ( false ) }
67
+ > { ( ( canEdit || canDelete ) && showActionBar )
68
+ ? < div className = 'action-bar comment-actions' >
69
+ { canEdit ? < button onClick = { ( ) => setEditMode ( true ) } > { editIcon } </ button > : null }
70
+ { canDelete ? < button onClick = { ( ) => deleteComment ( { id, pullRequestReviewId } ) } > { deleteIcon } </ button > : null }
71
+ </ div >
72
+ : null
73
+ }
74
+ < CommentBody bodyHTML = { bodyHTML } body = { bodyMd } />
75
+ </ CommentBox > ;
76
+ }
77
+
78
+ type CommentBoxProps = {
79
+ for : Partial < Comment & PullRequest >
80
+ header ?: React . ReactChild
81
+ onMouseEnter ?: any
82
+ onMouseLeave ?: any
83
+ children ?: any
84
+ } ;
85
+
86
+ function CommentBox ( {
87
+ for : comment ,
88
+ onMouseEnter, onMouseLeave, children } : CommentBoxProps ) {
89
+ const { user, author, createdAt, htmlUrl } = comment ;
90
+ console . log ( 'comment=' , comment )
91
+ return < div className = 'comment-container comment review-comment'
92
+ { ...{ onMouseEnter, onMouseLeave} }
93
+ >
94
+ < div className = 'review-comment-container' >
95
+ < div className = 'review-comment-header' >
96
+ < Spaced >
97
+ < Avatar for = { user || author } />
98
+ < AuthorLink for = { user || author } />
99
+ {
100
+ createdAt
101
+ ? < >
102
+ commented{ nbsp }
103
+ < Timestamp href = { htmlUrl } date = { createdAt } />
104
+ </ >
105
+ : < em > pending</ em >
106
+ }
107
+ </ Spaced >
108
+ </ div >
109
+ { children }
110
+ </ div >
111
+ </ div > ;
112
+ }
113
+
114
+ function EditComment ( { id, body, onCancel, onSave } : { id : number , body : string , onCancel : ( ) => void , onSave : ( body : string ) => void } ) {
115
+ const draftComment = useRef < { body : string , dirty : boolean } > ( { body, dirty : false } ) ;
116
+ const { updateDraft } = useContext ( PullRequestContext ) ;
117
+ useEffect ( ( ) => {
118
+ const interval = setInterval (
119
+ ( ) => {
120
+ if ( draftComment . current . dirty ) {
121
+ updateDraft ( id , draftComment . current . body ) ;
122
+ draftComment . current . dirty = false ;
123
+ }
124
+ } ,
125
+ 500 ) ;
126
+ return ( ) => clearInterval ( interval ) ;
127
+ } ) ;
128
+ return < form onSubmit = {
129
+ event => {
130
+ event . preventDefault ( ) ;
131
+ const { markdown } : any = event . target ;
132
+ onSave ( markdown . value ) ;
133
+ }
134
+ } >
135
+ < textarea
136
+ name = 'markdown'
137
+ defaultValue = { body }
138
+ onInput = {
139
+ e => {
140
+ draftComment . current . body = ( e . target as any ) . value ;
141
+ draftComment . current . dirty = true ;
142
+ }
143
+ }
144
+ />
145
+ < div className = 'form-actions' >
146
+ < button className = 'secondary' onClick = { onCancel } > Cancel</ button >
147
+ < input type = 'submit' value = 'Save' />
148
+ </ div >
149
+ </ form > ;
150
+ }
151
+
152
+ export interface Embodied {
153
+ bodyHTML ?: string ;
154
+ body ?: string ;
155
+ }
156
+
157
+ export const CommentBody = ( { bodyHTML, body } : Embodied ) =>
158
+ body
159
+ ? < Markdown className = 'comment-body' src = { body } />
160
+ :
161
+ bodyHTML
162
+ ? < div className = 'comment-body'
163
+ dangerouslySetInnerHTML = { { __html : bodyHTML } } />
164
+ :
165
+ < div className = 'comment-body' > < em > No description provided.</ em > </ div > ;
166
+
167
+ export function AddComment ( { pendingCommentText } : PullRequest ) {
168
+ const { updatePR, comment } = useContext ( PullRequestContext ) ;
169
+ return < form id = 'comment-form' className = 'comment-form main-comment-form' onSubmit = { onSubmit } >
170
+ < textarea id = 'comment-textarea'
171
+ name = 'body'
172
+ onInput = { ( { target } ) =>
173
+ updatePR ( { pendingCommentText : ( target as any ) . value } ) }
174
+ value = { pendingCommentText }
175
+ placeholder = 'Leave a comment' />
176
+ < div className = 'form-actions' >
177
+ < button id = 'close' className = 'secondary' > Close Pull Request</ button >
178
+ < button id = 'request-changes'
179
+ disabled = { ! pendingCommentText }
180
+ className = 'secondary' > Request Changes</ button >
181
+ < button id = 'approve'
182
+ className = 'secondary' > Approve</ button >
183
+ < input id = 'reply'
184
+ value = 'Comment'
185
+ type = 'submit'
186
+ className = 'reply-button'
187
+ disabled = { ! pendingCommentText } />
188
+ </ div >
189
+ </ form > ;
190
+
191
+ function onSubmit ( evt ) {
192
+ evt . preventDefault ( ) ;
193
+ comment ( ( evt . target as any ) . body . value ) ;
194
+ }
195
+ }
0 commit comments