datocms-plugin-record-comments 0.2.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/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,3 +0,0 @@
1
- # https://www.robotstxt.org/robotstxt.html
2
- User-agent: *
3
- Disallow:
@@ -1,254 +0,0 @@
1
- import { RenderItemFormSidebarPanelCtx } from "datocms-plugin-sdk";
2
- import {
3
- AccountAttributes,
4
- UserAttributes,
5
- } from "datocms-plugin-sdk/dist/types/SiteApiSchema";
6
- import { Button, Canvas } from "datocms-react-ui";
7
- import { useEffect, useState } from "react";
8
- import Comment from "./components/Comment";
9
- import styles from "./styles/commentbar.module.css";
10
-
11
- type PropTypes = {
12
- ctx: RenderItemFormSidebarPanelCtx;
13
- };
14
-
15
- export type CommentType = {
16
- dateISO: string;
17
- comment: string;
18
- author: {
19
- name: string;
20
- email: string;
21
- };
22
- usersWhoUpvoted: string[];
23
- replies?: CommentType[];
24
- parentCommentISO?: string;
25
- };
26
-
27
- const CommentsBar = ({ ctx }: PropTypes) => {
28
- //this component is way too big, i should split it into smaller ones
29
- const userEmail = (ctx.currentUser.attributes as UserAttributes)
30
- .email as string;
31
- const userName =
32
- (ctx.currentUser.attributes as UserAttributes).full_name ||
33
- `${(ctx.currentUser.attributes! as AccountAttributes).first_name} ${
34
- (ctx.currentUser.attributes as AccountAttributes).last_name
35
- }`;
36
- let foundLog = false;
37
-
38
- for (const field in ctx.fields) {
39
- if (
40
- ctx.fields[field]?.attributes.field_type === "json" &&
41
- "comment_log" === ctx.fields[field]?.attributes.api_key
42
- ) {
43
- foundLog = true;
44
- break;
45
- }
46
- }
47
-
48
- if (foundLog) {
49
- ctx.disableField("comment_log", true);
50
- }
51
-
52
- const initialState =
53
- foundLog && ctx.formValues.comment_log
54
- ? (JSON.parse(ctx.formValues.comment_log as string) as CommentType[])
55
- : [];
56
-
57
- const [savedComments, setSavedComments] =
58
- useState<CommentType[]>(initialState);
59
-
60
- const handleOpenLogModal = async () => {
61
- const result = await ctx.openModal({
62
- id: "NoLogModal",
63
- title: "Didn't find a log",
64
- width: "l",
65
- });
66
- if (result === "goToModelEdit") {
67
- ctx.navigateTo(`/admin/item_types/${ctx.itemType.id}`);
68
- }
69
- };
70
-
71
- const saveCommentHandler = () => {
72
- if (!foundLog) {
73
- handleOpenLogModal();
74
- return;
75
- }
76
-
77
- const newComment: CommentType = {
78
- dateISO: new Date().toISOString(),
79
- comment: "",
80
- author: {
81
- name: userName,
82
- email: userEmail,
83
- },
84
- usersWhoUpvoted: [],
85
- replies: [],
86
- };
87
-
88
- setSavedComments((oldComments: CommentType[]) => {
89
- const newComments = [...oldComments];
90
- newComments.unshift(newComment);
91
- return newComments;
92
- });
93
- };
94
-
95
- const deleteCommentHandler = (
96
- dateISO: string,
97
- parentCommentISO: string = ""
98
- ) => {
99
- if (parentCommentISO) {
100
- setSavedComments((oldComments: CommentType[]) => {
101
- const newCommentsIntermediary = [...oldComments];
102
- const parentComment = newCommentsIntermediary.find(
103
- (comment) => comment.dateISO === parentCommentISO
104
- );
105
- parentComment!.replies = parentComment!.replies!.filter(
106
- (comment) => comment.dateISO !== dateISO
107
- );
108
- const newComments = newCommentsIntermediary;
109
- return newComments;
110
- });
111
- } else {
112
- setSavedComments((oldComments: CommentType[]) => {
113
- const newCommentsIntermediary = [...oldComments];
114
- const newComments = newCommentsIntermediary.filter(
115
- (comment) => comment.dateISO !== dateISO
116
- );
117
- return newComments;
118
- });
119
- }
120
- };
121
-
122
- const editCommentHandler = (
123
- dateISO: string,
124
- newValue: string,
125
- parentCommentISO: string = ""
126
- ) => {
127
- if (parentCommentISO) {
128
- setSavedComments((oldComments: CommentType[]) => {
129
- const newComments = [...oldComments];
130
- const parentComment = newComments.find(
131
- (comment) => comment.dateISO === parentCommentISO
132
- );
133
- const reply = parentComment!.replies!.find(
134
- (comment: CommentType) => comment.dateISO === dateISO
135
- );
136
- reply!.comment = newValue;
137
- return newComments;
138
- });
139
- } else {
140
- setSavedComments((oldComments: CommentType[]) => {
141
- const newComments = [...oldComments];
142
- newComments.find((comment) => comment.dateISO === dateISO)!.comment =
143
- newValue;
144
- return newComments;
145
- });
146
- }
147
- };
148
-
149
- const upvoteCommentHandler = (
150
- dateISO: string,
151
- userUpvotedThisComment: boolean,
152
- parentCommentISO: string = ""
153
- ) => {
154
- if (parentCommentISO) {
155
- setSavedComments((oldComments: CommentType[]) => {
156
- const newComments = [...oldComments];
157
- const parentComment = newComments.find(
158
- (comment) => comment.dateISO === parentCommentISO
159
- );
160
- const reply = parentComment!.replies!.find(
161
- (comment: CommentType) => comment.dateISO === dateISO
162
- );
163
- if (userUpvotedThisComment) {
164
- reply!.usersWhoUpvoted = reply!.usersWhoUpvoted.filter(
165
- (userWhoUpvotedThisComment) =>
166
- userWhoUpvotedThisComment !== userEmail
167
- );
168
- } else {
169
- reply!.usersWhoUpvoted.push(userEmail);
170
- }
171
- return newComments;
172
- });
173
- } else {
174
- setSavedComments((oldComments: CommentType[]) => {
175
- const newComments = [...oldComments];
176
- const upvotedComment = newComments.find(
177
- (comment) => comment.dateISO === dateISO
178
- );
179
- if (userUpvotedThisComment) {
180
- upvotedComment!.usersWhoUpvoted =
181
- upvotedComment!.usersWhoUpvoted.filter(
182
- (userWhoUpvotedThisComment) =>
183
- userWhoUpvotedThisComment !== userEmail
184
- );
185
- } else {
186
- upvotedComment!.usersWhoUpvoted.push(userEmail);
187
- }
188
- return newComments;
189
- });
190
- }
191
- };
192
-
193
- const replyCommentHandler = (parentCommentISO: string) => {
194
- const newComment: CommentType = {
195
- dateISO: new Date().toISOString(),
196
- comment: "",
197
- author: {
198
- name: userName,
199
- email: userEmail,
200
- },
201
- usersWhoUpvoted: [],
202
- parentCommentISO,
203
- };
204
- setSavedComments((oldComments: CommentType[]) => {
205
- const newComments = [...oldComments];
206
- newComments
207
- .find((comment) => comment.dateISO === parentCommentISO)
208
- ?.replies?.unshift(newComment);
209
- return newComments;
210
- });
211
- };
212
-
213
- useEffect(() => {
214
- const objectIsEmpty = !Object.keys(savedComments).length;
215
- if (foundLog && objectIsEmpty) {
216
- ctx.setFieldValue("comment_log", null);
217
- } else if (foundLog) {
218
- const formatedComments = JSON.stringify(savedComments, null, 2);
219
- const stateIsEqualToLog =
220
- formatedComments === (ctx.formValues.comment_log as string);
221
- if (!stateIsEqualToLog) {
222
- ctx.setFieldValue("comment_log", formatedComments);
223
- }
224
- }
225
- }, [savedComments, ctx, foundLog]);
226
-
227
- return (
228
- <Canvas ctx={ctx}>
229
- <Button
230
- fullWidth
231
- className={styles["add-comment-button"]}
232
- onClick={saveCommentHandler}
233
- >
234
- Add a new comment...
235
- </Button>
236
- {foundLog &&
237
- savedComments.map((comment) => {
238
- return (
239
- <Comment
240
- key={comment.dateISO}
241
- deleteComment={deleteCommentHandler}
242
- editComment={editCommentHandler}
243
- upvoteComment={upvoteCommentHandler}
244
- replyComment={replyCommentHandler}
245
- commentObject={comment}
246
- currentUserEmail={userEmail}
247
- />
248
- );
249
- })}
250
- </Canvas>
251
- );
252
- };
253
-
254
- 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,260 +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 {
7
- faChevronUp,
8
- faPen,
9
- faReply,
10
- faSave,
11
- faTrashAlt,
12
- } from "@fortawesome/free-solid-svg-icons";
13
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
14
-
15
- type commentProps = {
16
- deleteComment: (dateISO: string, parentCommentISO?: string) => void;
17
- editComment: (
18
- dateISO: string,
19
- newValue: string,
20
- parentCommentISO?: string
21
- ) => void;
22
- upvoteComment: (
23
- dateISO: string,
24
- userUpvotedThisComment: boolean,
25
- parentCommentISO?: string
26
- ) => void;
27
- replyComment: (parentCommentISO: string) => void;
28
- commentObject: CommentType;
29
- currentUserEmail: string;
30
- };
31
-
32
- const Comment: React.FC<commentProps> = (props) => {
33
- //this component is way too big, i should split it into smaller ones
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 md5 = require("md5");
44
- const authorProfilePictureUrl =
45
- "https://www.gravatar.com/avatar/" +
46
- md5(props.commentObject.author.email) +
47
- "?d=mp";
48
-
49
- const replyClasses = isNotAReply ? "" : " " + styles.reply;
50
- const commentClasses = styles.comment + replyClasses;
51
-
52
- const [textAreaValue, setTextAreaValue] = useState(
53
- props.commentObject.comment
54
- );
55
-
56
- const toggleEdit = () => {
57
- setIsEditing(true);
58
- };
59
-
60
- const editCommentHandler = () => {
61
- if (!textAreaValue.trim()) {
62
- return;
63
- }
64
- setIsEditing(false);
65
- if (isNotAReply) {
66
- props.editComment(props.commentObject.dateISO, textAreaValue);
67
- } else {
68
- props.editComment(
69
- props.commentObject.dateISO,
70
- textAreaValue,
71
- props.commentObject.parentCommentISO
72
- );
73
- }
74
- };
75
-
76
- const deleteCommentHanlder = () => {
77
- if (isNotAReply) {
78
- props.deleteComment(props.commentObject.dateISO);
79
- } else {
80
- props.deleteComment(
81
- props.commentObject.dateISO,
82
- props.commentObject.parentCommentISO
83
- );
84
- }
85
- };
86
-
87
- const upvoteCommentHandler = () => {
88
- if (isNotAReply) {
89
- props.upvoteComment(props.commentObject.dateISO, userUpvotedThisComment);
90
- } else {
91
- props.upvoteComment(
92
- props.commentObject.dateISO,
93
- userUpvotedThisComment,
94
- props.commentObject.parentCommentISO
95
- );
96
- }
97
- };
98
-
99
- const replyCommentHandler = () => {
100
- props.replyComment(props.commentObject.dateISO);
101
- };
102
-
103
- const enterKeyHandler = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
104
- if (event.key === "Enter") {
105
- editCommentHandler();
106
- }
107
- };
108
-
109
- const heightChangeHandler = (height: number) => {
110
- setTextAreaHeight(height);
111
- };
112
-
113
- useEffect(() => {
114
- const spanHeight = commentSpan.current?.clientHeight;
115
- setTextAreaHeight(spanHeight!);
116
- }, [commentSpan]);
117
-
118
- const focousHandler = (event: React.FocusEvent<HTMLTextAreaElement>) => {
119
- event.currentTarget.setSelectionRange(
120
- event.currentTarget.value.length,
121
- event.currentTarget.value.length
122
- );
123
- };
124
-
125
- return (
126
- <div className={commentClasses}>
127
- {!isNotAReply && (
128
- <div
129
- style={{ height: 100 + textAreaHeight }}
130
- className={styles["reply-line"]}
131
- ></div>
132
- )}
133
- <div className={styles["comment-main"]}>
134
- <div className={styles["comment-body"]}>
135
- {isEditing && (
136
- <div className={styles["dialogbox"]}>
137
- <div className={styles["body"]}>
138
- <span
139
- className={styles["tip"] + " " + styles["tip-down"]}
140
- ></span>
141
- <div className={styles["message"]}>
142
- <Textarea
143
- autoFocus
144
- onFocus={focousHandler}
145
- onHeightChange={heightChangeHandler}
146
- onKeyPress={enterKeyHandler}
147
- onChange={(event) => setTextAreaValue(event.target.value)}
148
- value={textAreaValue}
149
- className={styles["comment-textarea"]}
150
- />
151
- </div>
152
- </div>
153
- </div>
154
- )}
155
- {!isEditing && (
156
- <div ref={commentSpan} className={styles["dialogbox"]}>
157
- <div className={styles["body"]}>
158
- <span
159
- className={styles["tip"] + " " + styles["tip-down"]}
160
- ></span>
161
- <div className={styles["message"]}>
162
- <span>{textAreaValue}</span>
163
- </div>
164
- </div>
165
- </div>
166
- )}
167
- <div
168
- className={`${styles["comment__footer"]} ${styles["comments__item__timestamp"]}`}
169
- >
170
- <div className={styles["comment-footer"]}>
171
- <div className={styles["author-info"]}>
172
- <img
173
- className={styles["author-profile-picture"]}
174
- src={authorProfilePictureUrl}
175
- alt={props.commentObject.author.name}
176
- />
177
- </div>
178
- {!isNotAReply && (
179
- <div className={styles["reply-profile-picture-line"]}></div>
180
- )}
181
- <div className={styles["comment__buttons"]}>
182
- <div>
183
- <span>{props.commentObject.author.name}</span>
184
- </div>
185
- <div className={styles["comment__timestamps"]}>
186
- <ReactTimeAgo date={new Date(props.commentObject.dateISO)} />
187
- </div>
188
- <div className={styles["comment__interface"]}>
189
- <span
190
- className={userUpvotedThisComment ? styles["upvoted"] : ""}
191
- >
192
- {props.commentObject.usersWhoUpvoted.length}
193
- </span>
194
- <button
195
- className={`${styles["comment__button"]} ${
196
- styles["upvotes"]
197
- } ${userUpvotedThisComment ? styles["upvoted"] : ""}`}
198
- onClick={upvoteCommentHandler}
199
- >
200
- <FontAwesomeIcon icon={faChevronUp} />
201
- </button>
202
- {userIsAuthor && props.commentObject.comment && !isEditing ? (
203
- <button
204
- className={styles["comment__button"]}
205
- onClick={toggleEdit}
206
- >
207
- <FontAwesomeIcon icon={faPen} />
208
- </button>
209
- ) : userIsAuthor ? (
210
- <button
211
- className={styles["comment__button"]}
212
- onClick={editCommentHandler}
213
- >
214
- <FontAwesomeIcon icon={faSave} />
215
- </button>
216
- ) : null}
217
- {userIsAuthor && (
218
- <button
219
- className={styles["comment__button"]}
220
- onClick={deleteCommentHanlder}
221
- >
222
- <FontAwesomeIcon icon={faTrashAlt} />
223
- </button>
224
- )}
225
- {isNotAReply && (
226
- <button
227
- className={styles["comment__button"]}
228
- onClick={replyCommentHandler}
229
- >
230
- <FontAwesomeIcon icon={faReply} />
231
- </button>
232
- )}
233
- </div>
234
- </div>
235
- </div>
236
- </div>
237
- </div>
238
- </div>
239
- {isNotAReply && (
240
- <div className={styles["comments__replies__item"]}>
241
- {props.commentObject.replies?.map((reply) => {
242
- return (
243
- <Comment
244
- key={reply.dateISO}
245
- deleteComment={props.deleteComment}
246
- editComment={props.editComment}
247
- upvoteComment={props.upvoteComment}
248
- replyComment={props.replyComment}
249
- commentObject={reply}
250
- currentUserEmail={props.currentUserEmail}
251
- />
252
- );
253
- })}
254
- </div>
255
- )}
256
- </div>
257
- );
258
- };
259
-
260
- export default Comment;
@@ -1,134 +0,0 @@
1
- .comment-main {
2
- display: flex;
3
- justify-content: space-around;
4
- }
5
-
6
- .author-profile-picture {
7
- border-radius: 5px;
8
- width: 50px;
9
- }
10
-
11
- .comment__buttons {
12
- display: flex;
13
- justify-content: left;
14
- }
15
-
16
- .comment__button {
17
- background-color: transparent;
18
- border: none;
19
- color: var(--light-body-color);
20
- }
21
-
22
- .comment__button:hover {
23
- cursor: pointer;
24
- color: var(--dark-color);
25
- }
26
-
27
- .comment {
28
- margin-bottom: 3em;
29
- }
30
-
31
- .reply {
32
- margin-bottom: 0;
33
- }
34
-
35
- .author-info {
36
- display: flex;
37
- flex-direction: column;
38
- align-items: center;
39
- padding: 1em;
40
- flex-grow: 1;
41
- }
42
-
43
- .reply {
44
- margin-left: 30px;
45
- }
46
-
47
- .comment-body {
48
- flex-grow: 8;
49
- }
50
-
51
- .comment-textarea {
52
- background-color: var(--lighter-bg-color);
53
- border: solid var(--border-color);
54
- resize: none;
55
- font: inherit;
56
- width: 100%;
57
- }
58
-
59
- .comment-footer {
60
- font-size: var(--font-size-s);
61
- display: flex;
62
- justify-content: center;
63
- align-items: center;
64
- }
65
-
66
- .comment__buttons {
67
- display: flex;
68
- flex-direction: column;
69
- align-items: baseline;
70
- width: 90%;
71
- }
72
-
73
- .comment__timestamps {
74
- color: var(--light-body-color);
75
- font-size: var(--font-size-xs);
76
- }
77
-
78
- .tip {
79
- width: 0px;
80
- height: 0px;
81
- position: absolute;
82
- background: transparent;
83
- border: 10px solid var(--light-bg-color);
84
- }
85
-
86
- .tip-down {
87
- bottom: -25px;
88
- left: 10px;
89
- border-right-color: transparent;
90
- border-left-color: transparent;
91
- border-bottom-color: transparent;
92
- }
93
-
94
- .dialogbox .body {
95
- position: relative;
96
- max-width: 300px;
97
- height: auto;
98
- padding: 5px;
99
- background-color: var(--light-bg-color);
100
- border-radius: 3px;
101
- border: 5px solid var(--light-bg-color);
102
- }
103
-
104
- .body .message {
105
- border-radius: 3px;
106
- font-size: var(--font-size-s);
107
- }
108
-
109
- .reply-line {
110
- content: "";
111
- width: 2px;
112
- height: 80px;
113
- background: var(--darker-border-color);
114
- position: absolute;
115
- left: 32px;
116
- }
117
-
118
- .reply-profile-picture-line {
119
- content: "";
120
- width: 30px;
121
- height: 2px;
122
- background: var(--darker-border-color);
123
- position: absolute;
124
- left: 32px;
125
- z-index: -1;
126
- }
127
-
128
- .upvoted {
129
- color: var(--dark-color);
130
- }
131
-
132
- .comment__interface {
133
- color: var(--light-body-color);
134
- }
@@ -1,3 +0,0 @@
1
- .add-comment-button {
2
- margin-bottom: 1.4em;
3
- }