gatsby-core-theme 44.4.45 → 44.4.46
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/components/atoms/comment-votes/comment-votes.module.scss +34 -0
- package/src/components/atoms/comment-votes/index.js +92 -0
- package/src/components/molecules/comment/comment.module.scss +0 -31
- package/src/components/molecules/comment/index.js +5 -83
- package/src/components/organisms/comments/comment-tree/index.js +6 -7
- package/src/components/organisms/comments/index.js +13 -24
- package/src/context/VotesProvider.js +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [44.4.46](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.4.45...v44.4.46) (2025-09-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* comment vote logic ([a09a3bd](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/a09a3bd5ef047976362b12099fb37e84c682cb69))
|
|
7
|
+
|
|
1
8
|
## [44.4.45](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.4.44...v44.4.45) (2025-08-29)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.buttonGroup{
|
|
2
|
+
background: #EAE5E0;
|
|
3
|
+
font-size: 1.2rem;
|
|
4
|
+
padding: .5rem .8rem;
|
|
5
|
+
height: 2.8rem;
|
|
6
|
+
gap: .4rem;
|
|
7
|
+
color: #0F172A;
|
|
8
|
+
|
|
9
|
+
@include flex-align(center, center);
|
|
10
|
+
|
|
11
|
+
&.left{
|
|
12
|
+
border-bottom-left-radius: 5rem;
|
|
13
|
+
border-top-left-radius: 5rem;
|
|
14
|
+
padding-right: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&.right{
|
|
18
|
+
border-bottom-right-radius: 5rem;
|
|
19
|
+
border-top-right-radius: 5rem;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.buttonGroupIcon{
|
|
24
|
+
width: 1.6rem;
|
|
25
|
+
height: 1.6rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.buttonGroup[disabled]{
|
|
29
|
+
cursor: default !important;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.disabled > *{
|
|
33
|
+
opacity: .3;
|
|
34
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
4
|
+
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
5
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
6
|
+
import React, { useContext } from 'react';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import LazyImage from '~hooks/lazy-image';
|
|
10
|
+
import useTranslate from '~hooks/useTranslate/useTranslate';
|
|
11
|
+
import { useVote, VotesContext } from '../../../context/VotesProvider';
|
|
12
|
+
import styles from './comment-votes.module.scss';
|
|
13
|
+
|
|
14
|
+
const CommentVotes = ({
|
|
15
|
+
comment,
|
|
16
|
+
voteUpAria = 'Like Button',
|
|
17
|
+
voteDownAria = 'Dislike Button'
|
|
18
|
+
}) => {
|
|
19
|
+
const { updateVote } = useContext(VotesContext);
|
|
20
|
+
|
|
21
|
+
const userVote = useVote(comment.comment_id);
|
|
22
|
+
|
|
23
|
+
const handleVote = async (like = false, commentId) => {
|
|
24
|
+
const data = like
|
|
25
|
+
? {
|
|
26
|
+
"vote_up": "increase",
|
|
27
|
+
"commentID": commentId
|
|
28
|
+
}
|
|
29
|
+
: {
|
|
30
|
+
"vote_down": "increase",
|
|
31
|
+
"commentID": commentId
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const headers = {
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
Accept: "application/json",
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
axios
|
|
43
|
+
.put('/api/put-vote', data, headers)
|
|
44
|
+
.then((response) => {
|
|
45
|
+
response.ok = response.status === 200;
|
|
46
|
+
resolve(response);
|
|
47
|
+
})
|
|
48
|
+
.catch((error) => reject(error?.response?.data?.errors?.join() || error.message));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return <>
|
|
53
|
+
<button
|
|
54
|
+
onClick={async () => {
|
|
55
|
+
updateVote(comment.comment_id, 'vote_up');
|
|
56
|
+
await handleVote(true, comment.comment_id)
|
|
57
|
+
}}
|
|
58
|
+
aria-label={useTranslate('vote_up_aria_label', voteUpAria)}
|
|
59
|
+
type='button'
|
|
60
|
+
className={`${styles.buttonGroup} ${styles.left} ${userVote === 'vote_down' && styles.disabled || ''}`}
|
|
61
|
+
disabled={Boolean(userVote)}
|
|
62
|
+
>
|
|
63
|
+
<LazyImage className={styles.buttonGroupIcon} src='/images/like.svg' />
|
|
64
|
+
<span>{userVote === 'vote_up' ? comment.votes_up + 1 : comment.votes_up}</span>
|
|
65
|
+
</button>
|
|
66
|
+
<button
|
|
67
|
+
onClick={async () => {
|
|
68
|
+
updateVote(comment.comment_id, 'vote_down');
|
|
69
|
+
await handleVote(false, comment.comment_id)
|
|
70
|
+
}}
|
|
71
|
+
aria-label={useTranslate('vote_down_aria_label', voteDownAria)}
|
|
72
|
+
type='button'
|
|
73
|
+
className={`${styles.buttonGroup} ${styles.right} ${userVote === 'vote_up' && styles.disabled || ''}`}
|
|
74
|
+
disabled={Boolean(userVote)}
|
|
75
|
+
>
|
|
76
|
+
<LazyImage className={styles.buttonGroupIcon} src='/images/dislike.svg' />
|
|
77
|
+
<span>{userVote === 'vote_down' ? comment.votes_down + 1 : comment.votes_down}</span>
|
|
78
|
+
</button>
|
|
79
|
+
</>
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
CommentVotes.propTypes = {
|
|
83
|
+
voteUpAria: PropTypes.string,
|
|
84
|
+
voteDownAria: PropTypes.string,
|
|
85
|
+
comment: PropTypes.shape({
|
|
86
|
+
comment_id: PropTypes.string,
|
|
87
|
+
votes_up: PropTypes.number,
|
|
88
|
+
votes_down: PropTypes.number
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default CommentVotes;
|
|
@@ -59,33 +59,6 @@
|
|
|
59
59
|
@include flex-direction(row);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
.buttonGroup{
|
|
63
|
-
background: #EAE5E0;
|
|
64
|
-
font-size: 1.2rem;
|
|
65
|
-
padding: .5rem .8rem;
|
|
66
|
-
height: 2.8rem;
|
|
67
|
-
gap: .4rem;
|
|
68
|
-
color: #0F172A;
|
|
69
|
-
|
|
70
|
-
@include flex-align(center, center);
|
|
71
|
-
|
|
72
|
-
&.left{
|
|
73
|
-
border-bottom-left-radius: 5rem;
|
|
74
|
-
border-top-left-radius: 5rem;
|
|
75
|
-
padding-right: 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
&.right{
|
|
79
|
-
border-bottom-right-radius: 5rem;
|
|
80
|
-
border-top-right-radius: 5rem;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.buttonGroupIcon{
|
|
85
|
-
width: 1.6rem;
|
|
86
|
-
height: 1.6rem;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
62
|
.replyButton{
|
|
90
63
|
border-radius: 10rem;
|
|
91
64
|
border: 1.5px solid #161128;
|
|
@@ -101,10 +74,6 @@
|
|
|
101
74
|
display: grid;
|
|
102
75
|
}
|
|
103
76
|
|
|
104
|
-
.disabled > *{
|
|
105
|
-
opacity: .3;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
77
|
.buttonGroup[disabled]{
|
|
109
78
|
cursor: default;
|
|
110
79
|
}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
4
4
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
5
5
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
6
|
-
import React, {
|
|
7
|
-
import axios from 'axios';
|
|
6
|
+
import React, { useState } from 'react';
|
|
8
7
|
import PropTypes from 'prop-types';
|
|
9
8
|
import LazyImage from '~hooks/lazy-image';
|
|
10
9
|
import LeaveCommentForm from '../leave-comment-form';
|
|
11
10
|
import useTranslate from '~hooks/useTranslate/useTranslate';
|
|
11
|
+
import CommentVotes from '../../atoms/comment-votes';
|
|
12
12
|
import styles from './comment.module.scss';
|
|
13
13
|
|
|
14
14
|
const Comment = ({
|
|
@@ -16,71 +16,21 @@ const Comment = ({
|
|
|
16
16
|
comment,
|
|
17
17
|
authors,
|
|
18
18
|
isReply,
|
|
19
|
-
userInteractions,
|
|
20
19
|
showVotes = true
|
|
21
20
|
}) => {
|
|
22
21
|
const commentName = comment?.author_id ? authors?.[comment?.author_id]?.name : comment.author_name;
|
|
23
22
|
const commentJobTitle = comment?.author_id ? authors?.[comment?.author_id]?.author_title : undefined;
|
|
23
|
+
|
|
24
24
|
const date = new Date(comment.updated_at);
|
|
25
25
|
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
26
26
|
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
27
27
|
const year = date.getUTCFullYear();
|
|
28
|
-
const [reply, setReply] = useState(false);
|
|
29
|
-
const [likes, setLikes] = useState(comment.votes_up);
|
|
30
|
-
const [dislikes, setDislikes] = useState(comment.votes_down);
|
|
31
|
-
const currentUserInteractions = useRef(userInteractions);
|
|
32
28
|
|
|
33
29
|
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
34
30
|
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
35
31
|
const commentDate = `${day}/${month}/${year} | ${hours}:${minutes}`;
|
|
36
|
-
|
|
37
|
-
const handleVote = async (like = false, commentId) => {
|
|
38
|
-
const data = like
|
|
39
|
-
? {
|
|
40
|
-
"vote_up": "increase",
|
|
41
|
-
"commentID": commentId
|
|
42
|
-
}
|
|
43
|
-
: {
|
|
44
|
-
"vote_down": "increase",
|
|
45
|
-
"commentID": commentId
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const headers = {
|
|
49
|
-
headers: {
|
|
50
|
-
"Content-Type": "application/json",
|
|
51
|
-
Accept: "application/json",
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const existingCookie = document.cookie
|
|
56
|
-
.split('; ')
|
|
57
|
-
.find((row) => row.startsWith('comments='))
|
|
58
|
-
?.split('=')[1] || '';
|
|
59
|
-
|
|
60
|
-
const ids = existingCookie.split(',').filter(Boolean);
|
|
61
|
-
|
|
62
|
-
const alreadyVoted = ids.some((entry) => entry.startsWith(`${commentId}:`));
|
|
63
|
-
if (!alreadyVoted) {
|
|
64
|
-
ids.push(`${commentId}: ${like ? 'vote_up' : 'vote_down'}`);
|
|
65
|
-
document.cookie = `comments=${ids.join(',')}; path=/;`;
|
|
66
|
-
currentUserInteractions.current = ids;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return new Promise((resolve, reject) => {
|
|
70
|
-
axios
|
|
71
|
-
.put('/api/put-vote', data, headers)
|
|
72
|
-
.then((response) => {
|
|
73
|
-
response.ok = response.status === 200;
|
|
74
|
-
resolve(response);
|
|
75
|
-
})
|
|
76
|
-
.catch((error) => reject(error?.response?.data?.errors?.join() || error.message));
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
const userVote = currentUserInteractions.current?.find(v => v.startsWith(`${comment.comment_id}:`))?.split(': ')[1];
|
|
80
|
-
|
|
81
|
-
const displayedLikes = userVote === 'vote_down' ? likes + 1 : likes;
|
|
82
|
-
const displayedDislikes = userVote === 'vote_down' ? dislikes + 1 : dislikes;
|
|
83
32
|
|
|
33
|
+
const [reply, setReply] = useState(false);
|
|
84
34
|
|
|
85
35
|
return <div className={`${styles.commentContainer} ${isReply ? styles.isReply : ''}`}>
|
|
86
36
|
<div className={styles.commentTopArea}>
|
|
@@ -94,34 +44,7 @@ const Comment = ({
|
|
|
94
44
|
</div>
|
|
95
45
|
<div className={styles.commentActions}>
|
|
96
46
|
{showVotes && (
|
|
97
|
-
|
|
98
|
-
<button
|
|
99
|
-
onClick={async () => {
|
|
100
|
-
await handleVote(true, comment.comment_id)
|
|
101
|
-
setLikes((prev) => prev + 1)
|
|
102
|
-
}}
|
|
103
|
-
aria-label='Like Button'
|
|
104
|
-
type='button'
|
|
105
|
-
className={`${styles.buttonGroup} ${styles.left} ${userVote === 'vote_down' && styles.disabled || ''}`}
|
|
106
|
-
disabled={userVote === 'vote_up'}
|
|
107
|
-
>
|
|
108
|
-
<LazyImage className={styles.buttonGroupIcon} src='/images/like.svg' />
|
|
109
|
-
<span>{displayedLikes}</span>
|
|
110
|
-
</button>
|
|
111
|
-
<button
|
|
112
|
-
onClick={async () => {
|
|
113
|
-
await handleVote(false, comment.comment_id)
|
|
114
|
-
setDislikes((prev) => prev + 1)
|
|
115
|
-
}}
|
|
116
|
-
aria-label='Dislike Button'
|
|
117
|
-
type='button'
|
|
118
|
-
className={`${styles.buttonGroup} ${styles.right} ${userVote === 'vote_up' && styles.disabled || ''}`}
|
|
119
|
-
disabled={userVote === 'vote_down'}
|
|
120
|
-
>
|
|
121
|
-
<LazyImage className={styles.buttonGroupIcon} src='/images/dislike.svg' />
|
|
122
|
-
<span>{displayedDislikes}</span>
|
|
123
|
-
</button>
|
|
124
|
-
</>
|
|
47
|
+
<CommentVotes comment={comment} />
|
|
125
48
|
)}
|
|
126
49
|
{!isReply && (
|
|
127
50
|
<button aria-label='Reply Button' onClick={() => setReply(!reply)} type='button' className={styles.replyButton}>
|
|
@@ -149,7 +72,6 @@ Comment.propTypes = {
|
|
|
149
72
|
}),
|
|
150
73
|
authors: PropTypes.shape({}),
|
|
151
74
|
isReply: PropTypes.bool,
|
|
152
|
-
userInteractions: PropTypes.arrayOf(PropTypes.string),
|
|
153
75
|
showVotes: PropTypes.bool
|
|
154
76
|
};
|
|
155
77
|
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import PropTypes, { arrayOf } from 'prop-types';
|
|
4
|
-
import keygen from '~helpers/keygen';
|
|
5
4
|
import Comment from "../../../molecules/comment";
|
|
6
5
|
|
|
7
6
|
import styles from './comment-tree.module.scss';
|
|
8
7
|
|
|
9
|
-
const CommentTree = React.memo(({ pageContext, comment, authors, depth = 0
|
|
10
|
-
<div key={
|
|
11
|
-
<Comment pageContext={pageContext} comment={comment} authors={authors} isReply={depth > 0}
|
|
8
|
+
const CommentTree = React.memo(({ pageContext, comment, authors, depth = 0 }) =>
|
|
9
|
+
<div key={comment.comment_id} className={`${styles?.[`comment_${depth}`]} ${depth > 0 && styles.isReply}`}>
|
|
10
|
+
<Comment pageContext={pageContext} comment={comment} authors={authors} isReply={depth > 0} />
|
|
12
11
|
{comment.replies?.map(reply => (
|
|
13
12
|
<CommentTree
|
|
14
|
-
key={
|
|
13
|
+
key={reply.comment_id}
|
|
15
14
|
comment={reply}
|
|
16
15
|
authors={authors}
|
|
17
16
|
depth={depth + 1}
|
|
@@ -24,11 +23,11 @@ const CommentTree = React.memo(({ pageContext, comment, authors, depth = 0, cook
|
|
|
24
23
|
CommentTree.propTypes = {
|
|
25
24
|
pageContext: PropTypes.shape({}),
|
|
26
25
|
comment: PropTypes.shape({
|
|
27
|
-
replies: arrayOf(PropTypes.shape({}))
|
|
26
|
+
replies: arrayOf(PropTypes.shape({})),
|
|
27
|
+
comment_id: PropTypes.number
|
|
28
28
|
}),
|
|
29
29
|
authors: PropTypes.shape({}),
|
|
30
30
|
depth: PropTypes.number,
|
|
31
|
-
cookieComments: PropTypes.arrayOf(PropTypes.string)
|
|
32
31
|
};
|
|
33
32
|
|
|
34
33
|
export default CommentTree;
|
|
@@ -1,39 +1,28 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import CommentTree from './comment-tree';
|
|
4
4
|
import LeaveCommentForm from '../../molecules/leave-comment-form';
|
|
5
|
+
import { VotesProvider } from '../../../context/VotesProvider';
|
|
5
6
|
|
|
6
7
|
const Comments = ({ pageContext }) => {
|
|
7
8
|
const { comments, authors } = pageContext;
|
|
8
|
-
const [cookieComments, setCookieComments] = useState([]);
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
const storedVotes =
|
|
12
|
-
document.cookie
|
|
13
|
-
.split('; ')
|
|
14
|
-
.find((row) => row.startsWith('comments='))
|
|
15
|
-
?.split('=')[1]
|
|
16
|
-
?.split(',')
|
|
17
|
-
.filter(Boolean) || [];
|
|
18
|
-
|
|
19
|
-
setCookieComments(storedVotes);
|
|
20
|
-
}, []);
|
|
21
9
|
|
|
22
10
|
if (!comments) return null;
|
|
23
11
|
|
|
24
12
|
return (
|
|
25
13
|
<div>
|
|
26
14
|
<LeaveCommentForm page={pageContext.page} />
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
15
|
+
<VotesProvider>
|
|
16
|
+
{comments.map((comment) => (
|
|
17
|
+
<CommentTree
|
|
18
|
+
key={comment.comment_id}
|
|
19
|
+
pageContext={pageContext}
|
|
20
|
+
comment={comment}
|
|
21
|
+
authors={authors?.authors}
|
|
22
|
+
depth={0}
|
|
23
|
+
/>
|
|
24
|
+
))}
|
|
25
|
+
</VotesProvider>
|
|
37
26
|
</div>
|
|
38
27
|
);
|
|
39
28
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, {createContext, useContext, useEffect, useState} from "react";
|
|
2
|
+
|
|
3
|
+
export const VotesContext = createContext({});
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line react/prop-types
|
|
6
|
+
export const VotesProvider = ({ children }) => {
|
|
7
|
+
const [votes, setVotes] = useState({});
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const storedVotes =
|
|
11
|
+
document.cookie
|
|
12
|
+
.split("; ")
|
|
13
|
+
.find((row) => row.startsWith("comments="))
|
|
14
|
+
?.split("=")[1]
|
|
15
|
+
?.split(",")
|
|
16
|
+
.filter(Boolean) || [];
|
|
17
|
+
const votesMap = storedVotes.reduce((acc, entry) => {
|
|
18
|
+
const [id, vote] = entry.split(":");
|
|
19
|
+
if (id && vote) acc[id] = vote;
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
setVotes(votesMap);
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const updateVote = (commentId, voteValue) => {
|
|
26
|
+
setVotes((prevVotes) => {
|
|
27
|
+
const newVotes = { ...prevVotes, [commentId]: voteValue };
|
|
28
|
+
|
|
29
|
+
// Convert votes object to cookie string
|
|
30
|
+
const cookieValue = Object.entries(newVotes)
|
|
31
|
+
.map(([id, vote]) => `${id}:${vote}`)
|
|
32
|
+
.join(",");
|
|
33
|
+
|
|
34
|
+
// Set the cookie (expires in 1 year)
|
|
35
|
+
document.cookie = `comments=${cookieValue}; path=/;`;
|
|
36
|
+
|
|
37
|
+
return newVotes;
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<VotesContext.Provider value={{votes, updateVote}}>{children}</VotesContext.Provider>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const useVote = (commentId) => {
|
|
47
|
+
const { votes } = useContext(VotesContext);
|
|
48
|
+
return votes?.[commentId];
|
|
49
|
+
};
|