diff --git a/components/ProfilePage/CommentTab.js b/components/ProfilePage/CommentTab.js new file mode 100644 index 00000000..02601ed2 --- /dev/null +++ b/components/ProfilePage/CommentTab.js @@ -0,0 +1,233 @@ +import gql from 'graphql-tag'; +import { useQuery } from '@apollo/react-hooks'; +import { useRouter } from 'next/router'; +import Link from 'next/link'; +import { t, ngettext, msgid } from 'ttag'; +import { makeStyles } from '@material-ui/core/styles'; +import { Tools, TimeRange, LoadMore } from 'components/ListPageControls'; +import { CardHeader, CardContent } from 'components/Card'; +import Infos from 'components/Infos'; +import TimeInfo from 'components/Infos/TimeInfo'; +import ExpandableText from 'components/ExpandableText'; +import ReplyRequestReason from 'components/ReplyRequestReason'; +import Thumbnail from 'components/Thumbnail'; + +const LOAD_USER_COMMENTS = gql` + query LoadUserComments( + $filter: ListReplyRequestFilter! + $orderBy: [ListReplyRequestOrderBy] + $after: String + ) { + ListReplyRequests( + filter: $filter + orderBy: $orderBy + after: $after + first: 15 + ) { + edges { + node { + id + ...ReplyRequestInfo + article { + id + replyRequestCount + replyCount + createdAt + text + ...ThumbnailArticleData + } + } + ...LoadMoreEdge + } + } + } + ${ReplyRequestReason.fragments.ReplyRequestInfo} + ${LoadMore.fragments.LoadMoreEdge} + ${Thumbnail.fragments.ThumbnailArticleData} +`; + +const LOAD_USER_COMMENTS_STAT = gql` + query LoadUserCommentsStat( + $filter: ListReplyRequestFilter! + $orderBy: [ListReplyRequestOrderBy] + ) { + ListReplyRequests(filter: $filter, orderBy: $orderBy) { + totalCount + ...LoadMoreConnectionForStats + } + } + ${LoadMore.fragments.LoadMoreConnectionForStats} +`; + +const useStyles = makeStyles(theme => ({ + tools: { + [theme.breakpoints.up('sm')]: { + marginLeft: 'var(--card-px)', + marginRight: 'var(--card-px)', + }, + }, + divider: { + border: 0, + margin: '16px 0 0', + borderBottom: `1px dashed ${theme.palette.secondary[100]}`, + }, + infos: { + marginBottom: 4, + [theme.breakpoints.up('md')]: { + marginBottom: 12, + }, + }, +})); + +/** + * @param {object} urlQuery - URL query object and urserId + * @param {string} userId - The author ID of article reply to look for + * @returns {object} ListArticleFilter + */ +function urlQuery2Filter(query = {}, userId) { + const filterObj = { userId }; + + const [start, end] = TimeRange.getValues(query); + + if (start) { + filterObj.createdAt = { + ...filterObj.createdAt, + GTE: start, + }; + } + if (end) { + filterObj.createdAt = { + ...filterObj.createdAt, + LTE: end, + }; + } + + return filterObj; +} + +function CommentTab({ userId }) { + const classes = useStyles(); + const { query } = useRouter(); + + const listQueryVars = { + filter: urlQuery2Filter(query, userId), + orderBy: [{ createdAt: 'DESC' }], + }; + + const { + loading, + fetchMore, + data: listCommentsData, + error: listCommentsError, + } = useQuery(LOAD_USER_COMMENTS, { + skip: !userId, + variables: listQueryVars, + notifyOnNetworkStatusChange: true, // Make loading true on `fetchMore` + }); + + // Separate these stats query so that it will be cached by apollo-client and sends no network request + // on page change, but still works when filter options are updated. + // + const { data: listStatData } = useQuery(LOAD_USER_COMMENTS_STAT, { + skip: !userId, + variables: listQueryVars, + }); + + // List data + const commentEdges = listCommentsData?.ListReplyRequests?.edges || []; + const statsData = listStatData?.ListReplyRequests || {}; + const totalCount = statsData?.totalCount; + + if (!userId) { + return null; + } + + return ( + <> + + + + {loading && !totalCount ? ( + {t`Loading...`} + ) : listCommentsError ? ( + {listCommentsError.toString()} + ) : totalCount === 0 ? ( + {t`This user does not provide comments to any message in the specified date range.`} + ) : ( + <> + + {ngettext( + msgid`${totalCount} comment matching criteria`, + `${totalCount} comments matching criteria`, + totalCount + )} + + {commentEdges.map(({ node: { article, ...comment } }) => ( + + + <> + {ngettext( + msgid`${article.replyRequestCount} occurrence`, + `${article.replyRequestCount} occurrences`, + article.replyRequestCount + )} + + + {timeAgo => ( + + {t`First reported ${timeAgo}`} + + )} + + {article.replyCount > 0 && ( + <> + {ngettext( + msgid`${article.replyCount} reply`, + `${article.replyCount} replies`, + article.replyCount + )} + + )} + + + {article.text && ( + {article.text} + )} + +
+ + +
+ ))} + + + fetchMore({ + variables: args, + updateQuery(prev, { fetchMoreResult }) { + if (!fetchMoreResult) return prev; + const newCommentData = fetchMoreResult?.ListReplyRequests; + return { + ...prev, + ListArticles: { + ...newCommentData, + edges: [...commentEdges, ...newCommentData.edges], + }, + }; + }, + }) + } + /> + + )} + + ); +} + +export default CommentTab; diff --git a/components/ProfilePage/ProfilePage.js b/components/ProfilePage/ProfilePage.js index e1a3917b..4f1c4b52 100644 --- a/components/ProfilePage/ProfilePage.js +++ b/components/ProfilePage/ProfilePage.js @@ -14,6 +14,7 @@ import AppLayout from 'components/AppLayout'; import { Card } from 'components/Card'; import UserPageHeader from './UserPageHeader'; import RepliedArticleTab from './RepliedArticleTab'; +import CommentTab from './CommentTab'; import ContributionChart from 'components/ContributionChart'; import { startOfWeek, subDays, format } from 'date-fns'; @@ -63,6 +64,9 @@ const LOAD_CONTRIBUTION = gql` commentedReplies: ListArticleReplyFeedbacks(filter: { userId: $id }) { totalCount } + comments: ListReplyRequests(filter: { userId: $id }) { + totalCount + } } `; @@ -75,6 +79,7 @@ function ProfilePage({ id, slug }) { const { data: contributionData } = useQuery(LOAD_CONTRIBUTION, { variables: { id: data?.GetUser?.id }, skip: !data?.GetUser?.id, + ssr: false, // Speed up SSR }); const isSelf = currentUser && data?.GetUser?.id === currentUser.id; @@ -123,8 +128,11 @@ function ProfilePage({ id, slug }) { let contentElem = null; switch (tab) { case 'replies': - default: contentElem = ; + break; + case 'comments': + contentElem = ; + break; } const today = format(new Date(), 'yyyy-MM-dd'); const aYearAgo = format( @@ -144,6 +152,7 @@ function ProfilePage({ id, slug }) { stats={{ repliedArticles: contributionData?.repliedArticles?.totalCount, commentedReplies: contributionData?.commentedReplies?.totalCount, + comments: contributionData?.comments?.totalCount, }} /> { - router.push({ query: { tab } }); + router.push({ query: { tab, id, slug } }); }} > + {contentElem} diff --git a/components/ProfilePage/RepliedArticleTab.js b/components/ProfilePage/RepliedArticleTab.js index 6fddebac..ef9ce286 100644 --- a/components/ProfilePage/RepliedArticleTab.js +++ b/components/ProfilePage/RepliedArticleTab.js @@ -1,6 +1,7 @@ import gql from 'graphql-tag'; import { useQuery } from '@apollo/react-hooks'; import { useRouter } from 'next/router'; +import Link from 'next/link'; import { t, ngettext, msgid } from 'ttag'; import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -272,7 +273,11 @@ function RepliedArticleTab({ userId }) { )} - {timeAgo => t`First reported ${timeAgo}`} + {timeAgo => ( + + {t`First reported ${timeAgo}`} + + )} diff --git a/components/ProfilePage/Stats.js b/components/ProfilePage/Stats.js index c00e4ba1..8e3e7826 100644 --- a/components/ProfilePage/Stats.js +++ b/components/ProfilePage/Stats.js @@ -58,6 +58,7 @@ function Stats({ stats }) { return (
    +
); diff --git a/components/ProfilePage/UserPageHeader.js b/components/ProfilePage/UserPageHeader.js index a31f70b2..6b6fe4c4 100644 --- a/components/ProfilePage/UserPageHeader.js +++ b/components/ProfilePage/UserPageHeader.js @@ -179,7 +179,7 @@ const useStyles = makeStyles(theme => ({ * * @param {object} props.user * @param {boolean} props.isSelf - If the current user is the one in `user` prop - * @param {{repliedArticles: number, commentedReplies: number}} props.stats + * @param {{repliedArticles: number, commentedReplies: number, comments: number}} props.stats */ function UserPageHeader({ user, isSelf, stats }) { const classes = useStyles(); diff --git a/i18n/zh_TW.po b/i18n/zh_TW.po index 8e82aacc..252e27cf 100644 --- a/i18n/zh_TW.po +++ b/i18n/zh_TW.po @@ -51,19 +51,19 @@ msgstr[0] "${ replyCount } 份回應" msgid "Sort by" msgstr "排序方式" -#: components/ProfilePage/RepliedArticleTab.js:35 +#: components/ProfilePage/RepliedArticleTab.js:36 #: pages/articles.js:166 #: pages/replies.js:143 msgid "Most recently asked" msgstr "最近被查詢" -#: components/ProfilePage/RepliedArticleTab.js:32 +#: components/ProfilePage/RepliedArticleTab.js:33 #: pages/articles.js:168 #: pages/replies.js:142 msgid "Most recently replied" msgstr "最近被回應" -#: components/ProfilePage/RepliedArticleTab.js:36 +#: components/ProfilePage/RepliedArticleTab.js:37 #: pages/articles.js:167 #: pages/replies.js:144 msgid "Most asked" @@ -234,7 +234,7 @@ msgid "" "fighting mis/disinformation in Taiwan." msgstr "一起來幫網路上的可疑訊息寫查核回應、共同教導聊天機器人回話,將多元的闢謠資訊傳遞給在 LINE 上收到謠言的人。" -#: components/ProfilePage/ProfilePage.js:98 +#: components/ProfilePage/ProfilePage.js:103 #: pages/article/[id].js:300 #: pages/article/[id].js:304 #: pages/reply/[id].js:167 @@ -444,7 +444,8 @@ msgid "Why do you think it is not useful?" msgstr "您認為沒有幫助的理由是什麼呢?" #: components/LandingPage/SectionArticles.js:153 -#: components/ProfilePage/RepliedArticleTab.js:250 +#: components/ProfilePage/CommentTab.js:151 +#: components/ProfilePage/RepliedArticleTab.js:251 #: pages/articles.js:181 #: pages/hoax-for-you.js:133 #: pages/replies.js:246 @@ -701,7 +702,8 @@ msgid "Total Visit: ${ totalVisits }" msgstr "${ totalVisits } 次瀏覽" #: components/ListPageDisplays/ReplySearchItem.js:103 -#: components/ProfilePage/RepliedArticleTab.js:268 +#: components/ProfilePage/CommentTab.js:169 +#: components/ProfilePage/RepliedArticleTab.js:269 #: pages/replies.js:256 #, javascript-format msgid "${ article.replyRequestCount } occurrence" @@ -924,19 +926,19 @@ msgstr "你的個人頁面網址會變成 ${ profileURL }" msgid "Bio" msgstr "自我介紹" -#: components/ProfilePage/ProfilePage.js:111 +#: components/ProfilePage/ProfilePage.js:116 msgid "User not found" msgstr "找不到使用者" -#: components/ProfilePage/ProfilePage.js:114 +#: components/ProfilePage/ProfilePage.js:119 msgid "The user does not exist" msgstr "此使用者不存在" -#: components/ProfilePage/ProfilePage.js:165 +#: components/ProfilePage/ProfilePage.js:174 msgid "Replied messages" msgstr "查核回應" -#: components/ProfilePage/RepliedArticleTab.js:254 +#: components/ProfilePage/RepliedArticleTab.js:255 msgid "No replied messages." msgstr "無查核回應" @@ -944,7 +946,7 @@ msgstr "無查核回應" msgid "replied messages" msgstr "查核回應" -#: components/ProfilePage/Stats.js:61 +#: components/ProfilePage/Stats.js:62 msgid "voted replies" msgstr "評價回應" @@ -2295,7 +2297,8 @@ msgstr "目前已經存有4萬5千筆以上查證過的資訊,透過投入資 #: components/ListPageDisplays/ArticleCard.js:111 #: components/ListPageDisplays/ReplySearchItem.js:112 -#: components/ProfilePage/RepliedArticleTab.js:275 +#: components/ProfilePage/CommentTab.js:178 +#: components/ProfilePage/RepliedArticleTab.js:278 #: pages/article/[id].js:370 #: pages/replies.js:263 msgid "First reported ${ timeAgo }" @@ -2385,11 +2388,11 @@ msgstr "注意:這裏不是討論區!請針對上面的「可疑訊息」撰 msgid "Comments from people reporting this message" msgstr "網友回報補充" -#: components/ProfilePage/RepliedArticleTab.js:34 +#: components/ProfilePage/RepliedArticleTab.js:35 msgid "Most recently replied by any user" msgstr "最近有人回應" -#: components/ProfilePage/RepliedArticleTab.js:258 +#: components/ProfilePage/RepliedArticleTab.js:259 #, javascript-format msgid "${ totalCount } message matching criteria" msgid_plural "${ totalCount } messages matching criteria" @@ -2440,6 +2443,31 @@ msgstr "語音訊息" msgid "Format" msgstr "格式" +#: components/ProfilePage/CommentTab.js:155 +msgid "" +"This user does not provide comments to any message in the specified date " +"range." +msgstr "這名使用者在指定日期範圍內沒有提供任何回報補充" + +#: components/ProfilePage/CommentTab.js:159 +#, javascript-format +msgid "${ totalCount } comment matching criteria" +msgid_plural "${ totalCount } comments matching criteria" +msgstr[0] "${ totalCount } 份符合篩選條件的回報補充" + +#: components/ProfilePage/ProfilePage.js:175 +msgid "Comments" +msgstr "回報補充" + +#: components/ProfilePage/Stats.js:61 +msgid "comments" +msgstr "回報補充" + +#: components/ProfilePage/CommentTab.js:184 +msgid "${ article.replyCount } reply" +msgid_plural "${ article.replyCount } replies" +msgstr[0] "${ article.replyCount } 則回應" + #: pages/index.js:29 msgctxt "site title" msgid "Cofacts"