datocms-plugin-record-comments 0.3.0 → 1.0.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.
- package/README.md +64 -4
- package/dist/assets/index-D7VcKz6i.css +1 -0
- package/dist/assets/index-UKydYRNj.js +251 -0
- package/dist/index.html +17 -0
- package/package.json +42 -44
- package/build/asset-manifest.json +0 -13
- package/build/index.html +0 -1
- package/build/robots.txt +0 -3
- package/build/static/css/main.13d44d44.css +0 -2
- package/build/static/css/main.13d44d44.css.map +0 -1
- package/build/static/js/main.bce144d4.js +0 -3
- package/build/static/js/main.bce144d4.js.LICENSE.txt +0 -59
- package/build/static/js/main.bce144d4.js.map +0 -1
- package/public/index.html +0 -11
- package/public/robots.txt +0 -3
- package/src/entrypoints/CommentsBar.tsx +0 -347
- package/src/entrypoints/NoLogModal.tsx +0 -43
- package/src/entrypoints/components/Comment.tsx +0 -259
- package/src/entrypoints/styles/comment.module.css +0 -134
- package/src/entrypoints/styles/commentbar.module.css +0 -3
- package/src/entrypoints/styles/modal.module.css +0 -13
- package/src/index.tsx +0 -44
- package/src/react-app-env.d.ts +0 -1
- package/src/types/md5.d.ts +0 -4
- package/src/utils/render.tsx +0 -6
- package/tsconfig.json +0 -26
package/public/index.html
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
</head>
|
|
7
|
-
<body>
|
|
8
|
-
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
9
|
-
<div id="root"></div>
|
|
10
|
-
</body>
|
|
11
|
-
</html>
|
package/public/robots.txt
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import { RenderItemFormSidebarPanelCtx } from 'datocms-plugin-sdk';
|
|
2
|
-
import type { SimpleSchemaTypes } from '@datocms/cma-client-node';
|
|
3
|
-
import { Button, Canvas } from 'datocms-react-ui';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
5
|
-
import Comment from './components/Comment';
|
|
6
|
-
import styles from './styles/commentbar.module.css';
|
|
7
|
-
|
|
8
|
-
type PropTypes = {
|
|
9
|
-
ctx: RenderItemFormSidebarPanelCtx;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type CommentType = {
|
|
13
|
-
dateISO: string;
|
|
14
|
-
comment: string;
|
|
15
|
-
author: {
|
|
16
|
-
name: string;
|
|
17
|
-
email: string;
|
|
18
|
-
};
|
|
19
|
-
usersWhoUpvoted: string[];
|
|
20
|
-
replies?: CommentType[];
|
|
21
|
-
parentCommentISO?: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Regular DatoCMS user
|
|
25
|
-
type RegularUser = {
|
|
26
|
-
type: 'user';
|
|
27
|
-
id: string;
|
|
28
|
-
attributes: {
|
|
29
|
-
email: string;
|
|
30
|
-
first_name: string;
|
|
31
|
-
last_name: string;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// SSO user from external provider
|
|
36
|
-
type SsoUser = {
|
|
37
|
-
type: 'sso_user';
|
|
38
|
-
id: string;
|
|
39
|
-
attributes: {
|
|
40
|
-
email: string;
|
|
41
|
-
name: string;
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Account user (personal account)
|
|
46
|
-
type Account = {
|
|
47
|
-
type: 'account';
|
|
48
|
-
id: string;
|
|
49
|
-
attributes: {
|
|
50
|
-
email: string;
|
|
51
|
-
name: string;
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Organization user
|
|
56
|
-
type Organization = {
|
|
57
|
-
type: 'organization';
|
|
58
|
-
id: string;
|
|
59
|
-
attributes: {
|
|
60
|
-
name: string;
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const isRegularUser = (user: any): user is RegularUser => {
|
|
65
|
-
return (
|
|
66
|
-
user?.type === 'user' &&
|
|
67
|
-
'attributes' in user &&
|
|
68
|
-
'email' in user.attributes &&
|
|
69
|
-
'full_name' in user.attributes
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const isSsoUser = (user: any): user is SsoUser => {
|
|
74
|
-
return (
|
|
75
|
-
user?.type === 'sso_user' &&
|
|
76
|
-
'attributes' in user &&
|
|
77
|
-
'email' in user.attributes &&
|
|
78
|
-
'name' in user.attributes
|
|
79
|
-
);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const isAccount = (user: any): user is Account => {
|
|
83
|
-
return (
|
|
84
|
-
user?.type === 'account' &&
|
|
85
|
-
'attributes' in user &&
|
|
86
|
-
'email' in user.attributes &&
|
|
87
|
-
'name' in user.attributes
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const isOrganization = (user: any): user is Organization => {
|
|
92
|
-
return (
|
|
93
|
-
user?.type === 'organization' &&
|
|
94
|
-
'attributes' in user &&
|
|
95
|
-
'name' in user.attributes
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const CommentsBar = ({ ctx }: PropTypes) => {
|
|
100
|
-
//this component is way too big, i should split it into smaller ones
|
|
101
|
-
|
|
102
|
-
const getUserEmail = (user: typeof ctx.currentUser): string => {
|
|
103
|
-
if (isRegularUser(user)) return user.attributes.email;
|
|
104
|
-
if (isSsoUser(user)) return user.attributes.email;
|
|
105
|
-
if (isAccount(user)) return user.attributes.email;
|
|
106
|
-
if (isOrganization(user)) return `organization-access`;
|
|
107
|
-
return 'unknown@email.com';
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const getUserName = (user: typeof ctx.currentUser): string => {
|
|
111
|
-
if (isRegularUser(user)) {
|
|
112
|
-
return user.attributes.full_name;
|
|
113
|
-
}
|
|
114
|
-
if (isSsoUser(user)) {
|
|
115
|
-
return user.attributes.name;
|
|
116
|
-
}
|
|
117
|
-
if (isAccount(user)) {
|
|
118
|
-
return user.attributes.name;
|
|
119
|
-
}
|
|
120
|
-
if (isOrganization(user)) {
|
|
121
|
-
return user.attributes.name;
|
|
122
|
-
}
|
|
123
|
-
const email = getUserEmail(user);
|
|
124
|
-
return email.split('@')[0];
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const userEmail = getUserEmail(ctx.currentUser);
|
|
128
|
-
const userName = getUserName(ctx.currentUser);
|
|
129
|
-
let foundLog = false;
|
|
130
|
-
|
|
131
|
-
for (const field in ctx.fields) {
|
|
132
|
-
if (
|
|
133
|
-
ctx.fields[field]?.attributes.field_type === 'json' &&
|
|
134
|
-
'comment_log' === ctx.fields[field]?.attributes.api_key
|
|
135
|
-
) {
|
|
136
|
-
foundLog = true;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (foundLog) {
|
|
142
|
-
ctx.disableField('comment_log', true);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const initialState =
|
|
146
|
-
foundLog && ctx.formValues.comment_log
|
|
147
|
-
? (JSON.parse(ctx.formValues.comment_log as string) as CommentType[])
|
|
148
|
-
: [];
|
|
149
|
-
|
|
150
|
-
const [savedComments, setSavedComments] =
|
|
151
|
-
useState<CommentType[]>(initialState);
|
|
152
|
-
|
|
153
|
-
const handleOpenLogModal = async () => {
|
|
154
|
-
const result = await ctx.openModal({
|
|
155
|
-
id: 'NoLogModal',
|
|
156
|
-
title: "Didn't find a log",
|
|
157
|
-
width: 'l',
|
|
158
|
-
});
|
|
159
|
-
if (result === 'goToModelEdit') {
|
|
160
|
-
ctx.navigateTo(`/schema/item_types/${ctx.itemType.id}`);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const saveCommentHandler = () => {
|
|
165
|
-
if (!foundLog) {
|
|
166
|
-
handleOpenLogModal();
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const newComment: CommentType = {
|
|
171
|
-
dateISO: new Date().toISOString(),
|
|
172
|
-
comment: '',
|
|
173
|
-
author: {
|
|
174
|
-
name: userName,
|
|
175
|
-
email: userEmail,
|
|
176
|
-
},
|
|
177
|
-
usersWhoUpvoted: [],
|
|
178
|
-
replies: [],
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
182
|
-
const newComments = [...oldComments];
|
|
183
|
-
newComments.unshift(newComment);
|
|
184
|
-
return newComments;
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const deleteCommentHandler = (
|
|
189
|
-
dateISO: string,
|
|
190
|
-
parentCommentISO: string = ''
|
|
191
|
-
) => {
|
|
192
|
-
if (parentCommentISO) {
|
|
193
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
194
|
-
const newCommentsIntermediary = [...oldComments];
|
|
195
|
-
const parentComment = newCommentsIntermediary.find(
|
|
196
|
-
(comment) => comment.dateISO === parentCommentISO
|
|
197
|
-
);
|
|
198
|
-
parentComment!.replies = parentComment!.replies!.filter(
|
|
199
|
-
(comment) => comment.dateISO !== dateISO
|
|
200
|
-
);
|
|
201
|
-
const newComments = newCommentsIntermediary;
|
|
202
|
-
return newComments;
|
|
203
|
-
});
|
|
204
|
-
} else {
|
|
205
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
206
|
-
const newCommentsIntermediary = [...oldComments];
|
|
207
|
-
const newComments = newCommentsIntermediary.filter(
|
|
208
|
-
(comment) => comment.dateISO !== dateISO
|
|
209
|
-
);
|
|
210
|
-
return newComments;
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const editCommentHandler = (
|
|
216
|
-
dateISO: string,
|
|
217
|
-
newValue: string,
|
|
218
|
-
parentCommentISO: string = ''
|
|
219
|
-
) => {
|
|
220
|
-
if (parentCommentISO) {
|
|
221
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
222
|
-
const newComments = [...oldComments];
|
|
223
|
-
const parentComment = newComments.find(
|
|
224
|
-
(comment) => comment.dateISO === parentCommentISO
|
|
225
|
-
);
|
|
226
|
-
const reply = parentComment!.replies!.find(
|
|
227
|
-
(comment: CommentType) => comment.dateISO === dateISO
|
|
228
|
-
);
|
|
229
|
-
reply!.comment = newValue;
|
|
230
|
-
return newComments;
|
|
231
|
-
});
|
|
232
|
-
} else {
|
|
233
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
234
|
-
const newComments = [...oldComments];
|
|
235
|
-
newComments.find((comment) => comment.dateISO === dateISO)!.comment =
|
|
236
|
-
newValue;
|
|
237
|
-
return newComments;
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const upvoteCommentHandler = (
|
|
243
|
-
dateISO: string,
|
|
244
|
-
userUpvotedThisComment: boolean,
|
|
245
|
-
parentCommentISO: string = ''
|
|
246
|
-
) => {
|
|
247
|
-
if (parentCommentISO) {
|
|
248
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
249
|
-
const newComments = [...oldComments];
|
|
250
|
-
const parentComment = newComments.find(
|
|
251
|
-
(comment) => comment.dateISO === parentCommentISO
|
|
252
|
-
);
|
|
253
|
-
const reply = parentComment!.replies!.find(
|
|
254
|
-
(comment: CommentType) => comment.dateISO === dateISO
|
|
255
|
-
);
|
|
256
|
-
if (userUpvotedThisComment) {
|
|
257
|
-
reply!.usersWhoUpvoted = reply!.usersWhoUpvoted.filter(
|
|
258
|
-
(userWhoUpvotedThisComment) =>
|
|
259
|
-
userWhoUpvotedThisComment !== userEmail
|
|
260
|
-
);
|
|
261
|
-
} else {
|
|
262
|
-
reply!.usersWhoUpvoted.push(userEmail);
|
|
263
|
-
}
|
|
264
|
-
return newComments;
|
|
265
|
-
});
|
|
266
|
-
} else {
|
|
267
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
268
|
-
const newComments = [...oldComments];
|
|
269
|
-
const upvotedComment = newComments.find(
|
|
270
|
-
(comment) => comment.dateISO === dateISO
|
|
271
|
-
);
|
|
272
|
-
if (userUpvotedThisComment) {
|
|
273
|
-
upvotedComment!.usersWhoUpvoted =
|
|
274
|
-
upvotedComment!.usersWhoUpvoted.filter(
|
|
275
|
-
(userWhoUpvotedThisComment) =>
|
|
276
|
-
userWhoUpvotedThisComment !== userEmail
|
|
277
|
-
);
|
|
278
|
-
} else {
|
|
279
|
-
upvotedComment!.usersWhoUpvoted.push(userEmail);
|
|
280
|
-
}
|
|
281
|
-
return newComments;
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const replyCommentHandler = (parentCommentISO: string) => {
|
|
287
|
-
const newComment: CommentType = {
|
|
288
|
-
dateISO: new Date().toISOString(),
|
|
289
|
-
comment: '',
|
|
290
|
-
author: {
|
|
291
|
-
name: userName,
|
|
292
|
-
email: userEmail,
|
|
293
|
-
},
|
|
294
|
-
usersWhoUpvoted: [],
|
|
295
|
-
parentCommentISO,
|
|
296
|
-
};
|
|
297
|
-
setSavedComments((oldComments: CommentType[]) => {
|
|
298
|
-
const newComments = [...oldComments];
|
|
299
|
-
newComments
|
|
300
|
-
.find((comment) => comment.dateISO === parentCommentISO)
|
|
301
|
-
?.replies?.unshift(newComment);
|
|
302
|
-
return newComments;
|
|
303
|
-
});
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
useEffect(() => {
|
|
307
|
-
const objectIsEmpty = !Object.keys(savedComments).length;
|
|
308
|
-
if (foundLog && objectIsEmpty) {
|
|
309
|
-
ctx.setFieldValue('comment_log', null);
|
|
310
|
-
} else if (foundLog) {
|
|
311
|
-
const formatedComments = JSON.stringify(savedComments, null, 2);
|
|
312
|
-
const stateIsEqualToLog =
|
|
313
|
-
formatedComments === (ctx.formValues.comment_log as string);
|
|
314
|
-
if (!stateIsEqualToLog) {
|
|
315
|
-
ctx.setFieldValue('comment_log', formatedComments);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}, [savedComments, ctx, foundLog]);
|
|
319
|
-
|
|
320
|
-
return (
|
|
321
|
-
<Canvas ctx={ctx}>
|
|
322
|
-
<Button
|
|
323
|
-
fullWidth
|
|
324
|
-
className={styles['add-comment-button']}
|
|
325
|
-
onClick={saveCommentHandler}
|
|
326
|
-
>
|
|
327
|
-
Add a new comment...
|
|
328
|
-
</Button>
|
|
329
|
-
{foundLog &&
|
|
330
|
-
savedComments.map((comment) => {
|
|
331
|
-
return (
|
|
332
|
-
<Comment
|
|
333
|
-
key={comment.dateISO}
|
|
334
|
-
deleteComment={deleteCommentHandler}
|
|
335
|
-
editComment={editCommentHandler}
|
|
336
|
-
upvoteComment={upvoteCommentHandler}
|
|
337
|
-
replyComment={replyCommentHandler}
|
|
338
|
-
commentObject={comment}
|
|
339
|
-
currentUserEmail={userEmail}
|
|
340
|
-
/>
|
|
341
|
-
);
|
|
342
|
-
})}
|
|
343
|
-
</Canvas>
|
|
344
|
-
);
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
export default CommentsBar;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { RenderModalCtx } from "datocms-plugin-sdk";
|
|
2
|
-
import { Button, Canvas } from "datocms-react-ui";
|
|
3
|
-
import styles from "./styles/modal.module.css";
|
|
4
|
-
|
|
5
|
-
type PropTypes = {
|
|
6
|
-
ctx: RenderModalCtx;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const NoLogModal = ({ ctx }: PropTypes) => {
|
|
10
|
-
const handleCancel = () => {
|
|
11
|
-
ctx.resolve("cancel");
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const handleGoToModel = () => {
|
|
15
|
-
ctx.resolve("goToModelEdit");
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<Canvas ctx={ctx}>
|
|
20
|
-
<div className={styles["error-explanation"]}>
|
|
21
|
-
<h2>A field is missing from your model!</h2>
|
|
22
|
-
<p>
|
|
23
|
-
You need to create a "comment log" JSON field on this model to save
|
|
24
|
-
comments.
|
|
25
|
-
</p>
|
|
26
|
-
<p className={styles["note"]}>
|
|
27
|
-
Note that the field must be of type "JSON" and its API key must be
|
|
28
|
-
"comment_log"
|
|
29
|
-
</p>
|
|
30
|
-
</div>
|
|
31
|
-
<div className={styles["buttons"]}>
|
|
32
|
-
<Button buttonType="negative" buttonSize="l" onClick={handleCancel}>
|
|
33
|
-
Cancel
|
|
34
|
-
</Button>
|
|
35
|
-
<Button buttonType="primary" buttonSize="l" onClick={handleGoToModel}>
|
|
36
|
-
Go to model editing
|
|
37
|
-
</Button>
|
|
38
|
-
</div>
|
|
39
|
-
</Canvas>
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export default NoLogModal;
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
-
import styles from "../styles/comment.module.css";
|
|
3
|
-
import Textarea from "react-textarea-autosize";
|
|
4
|
-
import ReactTimeAgo from "react-time-ago";
|
|
5
|
-
import { CommentType } from "../CommentsBar";
|
|
6
|
-
import md5 from "md5";
|
|
7
|
-
import {
|
|
8
|
-
faChevronUp,
|
|
9
|
-
faPen,
|
|
10
|
-
faReply,
|
|
11
|
-
faSave,
|
|
12
|
-
faTrashAlt,
|
|
13
|
-
} from "@fortawesome/free-solid-svg-icons";
|
|
14
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
15
|
-
|
|
16
|
-
type commentProps = {
|
|
17
|
-
deleteComment: (dateISO: string, parentCommentISO?: string) => void;
|
|
18
|
-
editComment: (
|
|
19
|
-
dateISO: string,
|
|
20
|
-
newValue: string,
|
|
21
|
-
parentCommentISO?: string
|
|
22
|
-
) => void;
|
|
23
|
-
upvoteComment: (
|
|
24
|
-
dateISO: string,
|
|
25
|
-
userUpvotedThisComment: boolean,
|
|
26
|
-
parentCommentISO?: string
|
|
27
|
-
) => void;
|
|
28
|
-
replyComment: (parentCommentISO: string) => void;
|
|
29
|
-
commentObject: CommentType;
|
|
30
|
-
currentUserEmail: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const Comment: React.FC<commentProps> = (props) => {
|
|
34
|
-
const userUpvotedThisComment = !!props.commentObject.usersWhoUpvoted.find(
|
|
35
|
-
(userWhoUpvoted) => userWhoUpvoted === props.currentUserEmail
|
|
36
|
-
);
|
|
37
|
-
const commentSpan = useRef<HTMLDivElement>(null);
|
|
38
|
-
const [textAreaHeight, setTextAreaHeight] = useState(21);
|
|
39
|
-
const [isEditing, setIsEditing] = useState(!props.commentObject.comment);
|
|
40
|
-
const isNotAReply = "replies" in props.commentObject;
|
|
41
|
-
const userIsAuthor =
|
|
42
|
-
props.commentObject.author.email === props.currentUserEmail;
|
|
43
|
-
const authorProfilePictureUrl =
|
|
44
|
-
"https://www.gravatar.com/avatar/" +
|
|
45
|
-
md5(props.commentObject.author.email || '') +
|
|
46
|
-
"?d=mp";
|
|
47
|
-
|
|
48
|
-
const replyClasses = isNotAReply ? "" : " " + styles.reply;
|
|
49
|
-
const commentClasses = styles.comment + replyClasses;
|
|
50
|
-
|
|
51
|
-
const [textAreaValue, setTextAreaValue] = useState(
|
|
52
|
-
props.commentObject.comment
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const toggleEdit = () => {
|
|
56
|
-
setIsEditing(true);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const editCommentHandler = () => {
|
|
60
|
-
if (!textAreaValue.trim()) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
setIsEditing(false);
|
|
64
|
-
if (isNotAReply) {
|
|
65
|
-
props.editComment(props.commentObject.dateISO, textAreaValue);
|
|
66
|
-
} else {
|
|
67
|
-
props.editComment(
|
|
68
|
-
props.commentObject.dateISO,
|
|
69
|
-
textAreaValue,
|
|
70
|
-
props.commentObject.parentCommentISO
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const deleteCommentHanlder = () => {
|
|
76
|
-
if (isNotAReply) {
|
|
77
|
-
props.deleteComment(props.commentObject.dateISO);
|
|
78
|
-
} else {
|
|
79
|
-
props.deleteComment(
|
|
80
|
-
props.commentObject.dateISO,
|
|
81
|
-
props.commentObject.parentCommentISO
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const upvoteCommentHandler = () => {
|
|
87
|
-
if (isNotAReply) {
|
|
88
|
-
props.upvoteComment(props.commentObject.dateISO, userUpvotedThisComment);
|
|
89
|
-
} else {
|
|
90
|
-
props.upvoteComment(
|
|
91
|
-
props.commentObject.dateISO,
|
|
92
|
-
userUpvotedThisComment,
|
|
93
|
-
props.commentObject.parentCommentISO
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const replyCommentHandler = () => {
|
|
99
|
-
props.replyComment(props.commentObject.dateISO);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const enterKeyHandler = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
103
|
-
if (event.key === "Enter") {
|
|
104
|
-
editCommentHandler();
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const heightChangeHandler = (height: number) => {
|
|
109
|
-
setTextAreaHeight(height);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
const spanHeight = commentSpan.current?.clientHeight;
|
|
114
|
-
setTextAreaHeight(spanHeight!);
|
|
115
|
-
}, [commentSpan]);
|
|
116
|
-
|
|
117
|
-
const focousHandler = (event: React.FocusEvent<HTMLTextAreaElement>) => {
|
|
118
|
-
event.currentTarget.setSelectionRange(
|
|
119
|
-
event.currentTarget.value.length,
|
|
120
|
-
event.currentTarget.value.length
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<div className={commentClasses}>
|
|
126
|
-
{!isNotAReply && (
|
|
127
|
-
<div
|
|
128
|
-
style={{ height: 100 + textAreaHeight }}
|
|
129
|
-
className={styles["reply-line"]}
|
|
130
|
-
></div>
|
|
131
|
-
)}
|
|
132
|
-
<div className={styles["comment-main"]}>
|
|
133
|
-
<div className={styles["comment-body"]}>
|
|
134
|
-
{isEditing && (
|
|
135
|
-
<div className={styles["dialogbox"]}>
|
|
136
|
-
<div className={styles["body"]}>
|
|
137
|
-
<span
|
|
138
|
-
className={styles["tip"] + " " + styles["tip-down"]}
|
|
139
|
-
></span>
|
|
140
|
-
<div className={styles["message"]}>
|
|
141
|
-
<Textarea
|
|
142
|
-
autoFocus
|
|
143
|
-
onFocus={focousHandler}
|
|
144
|
-
onHeightChange={heightChangeHandler}
|
|
145
|
-
onKeyPress={enterKeyHandler}
|
|
146
|
-
onChange={(event) => setTextAreaValue(event.target.value)}
|
|
147
|
-
value={textAreaValue}
|
|
148
|
-
className={styles["comment-textarea"]}
|
|
149
|
-
/>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
)}
|
|
154
|
-
{!isEditing && (
|
|
155
|
-
<div ref={commentSpan} className={styles["dialogbox"]}>
|
|
156
|
-
<div className={styles["body"]}>
|
|
157
|
-
<span
|
|
158
|
-
className={styles["tip"] + " " + styles["tip-down"]}
|
|
159
|
-
></span>
|
|
160
|
-
<div className={styles["message"]}>
|
|
161
|
-
<span>{textAreaValue}</span>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
<div
|
|
167
|
-
className={`${styles["comment__footer"]} ${styles["comments__item__timestamp"]}`}
|
|
168
|
-
>
|
|
169
|
-
<div className={styles["comment-footer"]}>
|
|
170
|
-
<div className={styles["author-info"]}>
|
|
171
|
-
<img
|
|
172
|
-
className={styles["author-profile-picture"]}
|
|
173
|
-
src={authorProfilePictureUrl}
|
|
174
|
-
alt={props.commentObject.author.name}
|
|
175
|
-
/>
|
|
176
|
-
</div>
|
|
177
|
-
{!isNotAReply && (
|
|
178
|
-
<div className={styles["reply-profile-picture-line"]}></div>
|
|
179
|
-
)}
|
|
180
|
-
<div className={styles["comment__buttons"]}>
|
|
181
|
-
<div>
|
|
182
|
-
<span>{props.commentObject.author.name}</span>
|
|
183
|
-
</div>
|
|
184
|
-
<div className={styles["comment__timestamps"]}>
|
|
185
|
-
<ReactTimeAgo date={new Date(props.commentObject.dateISO)} />
|
|
186
|
-
</div>
|
|
187
|
-
<div className={styles["comment__interface"]}>
|
|
188
|
-
<span
|
|
189
|
-
className={userUpvotedThisComment ? styles["upvoted"] : ""}
|
|
190
|
-
>
|
|
191
|
-
{props.commentObject.usersWhoUpvoted.length}
|
|
192
|
-
</span>
|
|
193
|
-
<button
|
|
194
|
-
className={`${styles["comment__button"]} ${
|
|
195
|
-
styles["upvotes"]
|
|
196
|
-
} ${userUpvotedThisComment ? styles["upvoted"] : ""}`}
|
|
197
|
-
onClick={upvoteCommentHandler}
|
|
198
|
-
>
|
|
199
|
-
<FontAwesomeIcon icon={faChevronUp} />
|
|
200
|
-
</button>
|
|
201
|
-
{userIsAuthor && props.commentObject.comment && !isEditing ? (
|
|
202
|
-
<button
|
|
203
|
-
className={styles["comment__button"]}
|
|
204
|
-
onClick={toggleEdit}
|
|
205
|
-
>
|
|
206
|
-
<FontAwesomeIcon icon={faPen} />
|
|
207
|
-
</button>
|
|
208
|
-
) : userIsAuthor ? (
|
|
209
|
-
<button
|
|
210
|
-
className={styles["comment__button"]}
|
|
211
|
-
onClick={editCommentHandler}
|
|
212
|
-
>
|
|
213
|
-
<FontAwesomeIcon icon={faSave} />
|
|
214
|
-
</button>
|
|
215
|
-
) : null}
|
|
216
|
-
{userIsAuthor && (
|
|
217
|
-
<button
|
|
218
|
-
className={styles["comment__button"]}
|
|
219
|
-
onClick={deleteCommentHanlder}
|
|
220
|
-
>
|
|
221
|
-
<FontAwesomeIcon icon={faTrashAlt} />
|
|
222
|
-
</button>
|
|
223
|
-
)}
|
|
224
|
-
{isNotAReply && (
|
|
225
|
-
<button
|
|
226
|
-
className={styles["comment__button"]}
|
|
227
|
-
onClick={replyCommentHandler}
|
|
228
|
-
>
|
|
229
|
-
<FontAwesomeIcon icon={faReply} />
|
|
230
|
-
</button>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
{isNotAReply && (
|
|
239
|
-
<div className={styles["comments__replies__item"]}>
|
|
240
|
-
{props.commentObject.replies?.map((reply) => {
|
|
241
|
-
return (
|
|
242
|
-
<Comment
|
|
243
|
-
key={reply.dateISO}
|
|
244
|
-
deleteComment={props.deleteComment}
|
|
245
|
-
editComment={props.editComment}
|
|
246
|
-
upvoteComment={props.upvoteComment}
|
|
247
|
-
replyComment={props.replyComment}
|
|
248
|
-
commentObject={reply}
|
|
249
|
-
currentUserEmail={props.currentUserEmail}
|
|
250
|
-
/>
|
|
251
|
-
);
|
|
252
|
-
})}
|
|
253
|
-
</div>
|
|
254
|
-
)}
|
|
255
|
-
</div>
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
export default Comment;
|