From 9a5b0ffb0c6c800d4d7a415c3eaac44fb671544d Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Thu, 25 Jul 2024 11:44:23 +0200 Subject: [PATCH 01/16] move file --- src/hooks/use-is-subplebbit-offline.ts | 2 +- .../use-subplebbits-loading-start-timestamps-store.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{hooks => stores}/use-subplebbits-loading-start-timestamps-store.ts (100%) diff --git a/src/hooks/use-is-subplebbit-offline.ts b/src/hooks/use-is-subplebbit-offline.ts index f94c2f64..f97cad92 100644 --- a/src/hooks/use-is-subplebbit-offline.ts +++ b/src/hooks/use-is-subplebbit-offline.ts @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import { Subplebbit } from '@plebbit/plebbit-react-hooks'; import { getFormattedTimeAgo } from '../lib/utils/time-utils'; import useSubplebbitOfflineStore from '../stores/use-subplebbit-offline-store'; -import useSubplebbitsLoadingStartTimestamps from './use-subplebbits-loading-start-timestamps-store'; +import useSubplebbitsLoadingStartTimestamps from '../stores/use-subplebbits-loading-start-timestamps-store'; const useIsSubplebbitOffline = (subplebbit: Subplebbit) => { const { t } = useTranslation(); diff --git a/src/hooks/use-subplebbits-loading-start-timestamps-store.ts b/src/stores/use-subplebbits-loading-start-timestamps-store.ts similarity index 100% rename from src/hooks/use-subplebbits-loading-start-timestamps-store.ts rename to src/stores/use-subplebbits-loading-start-timestamps-store.ts From 48b55ed972e8d20d108086175b7838f43d2983a6 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Sat, 27 Jul 2024 17:41:09 +0200 Subject: [PATCH 02/16] fix(catalog post preview): special characters were able to overflow --- src/components/catalog-row/catalog-row.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/catalog-row/catalog-row.module.css b/src/components/catalog-row/catalog-row.module.css index 5f0e7333..ba5cce64 100644 --- a/src/components/catalog-row/catalog-row.module.css +++ b/src/components/catalog-row/catalog-row.module.css @@ -124,6 +124,7 @@ color: var(--catalog-post-preview-color); font-family: arial, helvetica, sans-serif; font-size: 10pt; + overflow: hidden; } .postPreview .postSubject { From 568ab16b0670f42ad1fe68bbb691dd5ca141f8e4 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Wed, 31 Jul 2024 15:46:02 +0200 Subject: [PATCH 03/16] show avatar for OP as well --- src/components/post-desktop/post-desktop.tsx | 2 +- src/components/post-mobile/post-mobile.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index 1a149f92..b76d0adc 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -101,7 +101,7 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }: {!(isDescription || isRules) && ( <> - {isReply && author?.avatar && ( + {author?.avatar && ( diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 0eb84770..a3cb51e8 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -74,7 +74,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P {!(isDescription || isRules) && ( <> - {isReply && author?.avatar && ( + {author?.avatar && ( From ba4906058db17c474308c501ea756ee504140f6d Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Wed, 31 Jul 2024 15:48:26 +0200 Subject: [PATCH 04/16] fix(post): edited timestamp showed html on mobile --- src/components/post-mobile/post-mobile.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index a3cb51e8..3a6dc329 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -202,7 +202,12 @@ const PostMessageMobile = ({ post }: PostProps) => { {showOriginal && }
- {t('comment_edited_at_timestamp', { timestamp: getFormattedDate(edit?.timestamp), interpolation: { escapeValue: false } })}{' '} + } /> }} + />{' '} {reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} } {showOriginal ? ( Date: Thu, 1 Aug 2024 14:40:07 +0200 Subject: [PATCH 05/16] fix(post): 'comment too long' link was broken for description and rules --- src/components/post-desktop/post-desktop.tsx | 7 ++++++- src/components/post-mobile/post-mobile.tsx | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index b76d0adc..6c9b6493 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -240,6 +240,7 @@ const PostMedia = ({ post }: PostProps) => { const PostMessage = ({ post }: PostProps) => { const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api // TODO: commentAuthor is not available outside of editedComment, update when available // const banned = !!post?.commentAuthor?.banExpiresAt; const { t } = useTranslation(); @@ -320,7 +321,11 @@ const PostMessage = ({ post }: PostProps) => { {!isReply && content.length > 1000 && !isInPostView && (
- }} /> + }} + />
)} {!cid && state === 'pending' && stateString !== 'Failed' && ( diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 3a6dc329..01e9c3af 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -164,6 +164,7 @@ const ReplyBacklinks = ({ post }: PostProps) => { const PostMessageMobile = ({ post }: PostProps) => { const { t } = useTranslation(); const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api // TODO: commentAuthor is not available outside of editedComment, update when available // const banned = !!post?.commentAuthor?.banExpiresAt; const [showOriginal, setShowOriginal] = useState(false); @@ -244,7 +245,11 @@ const PostMessageMobile = ({ post }: PostProps) => { {!isReply && content.length > 1000 && !isInPostView && (
- }} /> + }} + />
)} {!cid && state === 'pending' && stateString !== 'Failed' && ( From 8598d1056ee9b08cb146b129356c28c14a8ec79a Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Sat, 3 Aug 2024 13:21:09 +0200 Subject: [PATCH 06/16] feat(feed): show account comments instantly in the feed once published, instead of waiting for the feed to update --- src/components/post-desktop/post-desktop.tsx | 7 +- src/hooks/use-catalog-feed-rows.ts | 113 +++++++++++++++++++ src/views/board/board.tsx | 17 ++- src/views/catalog/catalog.tsx | 70 +----------- 4 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 src/hooks/use-catalog-feed-rows.ts diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index 6c9b6493..77860263 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { Link, useLocation, useParams } from 'react-router-dom'; -import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks'; +import { Comment, useAccount, useAccountComments, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks'; import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; import styles from '../../views/post/post.module.css'; import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils'; @@ -383,6 +383,9 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies const visiblelinksCount = useCountLinksInReplies(post, 5); const totalLinksCount = useCountLinksInReplies(post); const replyCount = replies?.length; + const { accountComments } = useAccountComments(); + const isPostInAccountComments = accountComments?.find((comment) => comment.cid === cid); + const repliesCount = pinned ? replyCount : replyCount - 5; const linksCount = pinned ? totalLinksCount : totalLinksCount - visiblelinksCount; const { showOmittedReplies, setShowOmittedReplies } = useShowOmittedReplies(); @@ -439,7 +442,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies )}
)} - {post?.replyCount === undefined && !isInPendingPostView && ( + {post?.replyCount === undefined && !isPostInAccountComments && !isInPendingPostView && ( diff --git a/src/hooks/use-catalog-feed-rows.ts b/src/hooks/use-catalog-feed-rows.ts new file mode 100644 index 00000000..e2119e80 --- /dev/null +++ b/src/hooks/use-catalog-feed-rows.ts @@ -0,0 +1,113 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams, useLocation } from 'react-router-dom'; +import { useAccountComments, Subplebbit } from '@plebbit/plebbit-react-hooks'; +import { isAllView } from '../lib/utils/view-utils'; +import { useMultisubMetadata } from './use-default-subplebbits'; +import useCatalogFiltersStore from '../stores/use-catalog-filters-store'; +import _ from 'lodash'; +import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils'; + +const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit) => { + const { t } = useTranslation(); + const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {}; + const { avatarUrl } = suggested || {}; + const { showTextOnlyThreads } = useCatalogFiltersStore(); + + const location = useLocation(); + const isInAllView = isAllView(location.pathname, useParams()); + const multisub = useMultisubMetadata(); + + const { accountComments } = useAccountComments(); + + const feedWithFakePostsOnTop = useMemo(() => { + if (!isFeedLoaded) { + return []; // prevent rules and description from appearing while feed is loading + } + + if (!description && !rules && !isInAllView) { + return feed; + } + + const _feed = [...feed]; + + // show account comments instantly in the feed instead of waiting for the next feed update, then hide them when they are in the feed + accountComments.forEach((comment) => { + const { cid, deleted, link, postCid, removed, subplebbitAddress } = comment || {}; + const commentMediaInfo = getCommentMediaInfo(comment); + const isMediaShowed = getHasThumbnail(commentMediaInfo, link); + + if ( + !deleted && + !removed && + (!showTextOnlyThreads || (showTextOnlyThreads && !isMediaShowed)) && + cid && + cid === postCid && + subplebbitAddress === address && + !_feed.some((feedItem) => feedItem.cid === cid) + ) { + _feed.unshift({ + ...comment, + isAccountComment: true, + }); + } + }); + + if ((description && description.length > 0 && (showTextOnlyThreads || (!showTextOnlyThreads && suggested?.avatarUrl))) || isInAllView) { + _feed.unshift({ + isDescription: true, + subplebbitAddress: address, + timestamp: createdAt, + author: { displayName: `## ${t('board_mods')}` }, + content: isInAllView ? multisub?.description : description, + link: avatarUrl, + title: t('welcome_to_board', { board: isInAllView ? multisub?.title : title || `p/${shortAddress}`, interpolation: { escapeValue: false } }), + pinned: true, + locked: true, + }); + } + + if (rules && rules.length > 0 && showTextOnlyThreads) { + _feed.unshift({ + isRules: true, + subplebbitAddress: address, + timestamp: createdAt, + author: { displayName: `## ${t('board_mods')}` }, + content: rules.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n'), + title: _.capitalize(t('rules')), + pinned: true, + locked: true, + }); + } + + return _feed; + }, [ + accountComments, + feed, + description, + rules, + address, + isFeedLoaded, + createdAt, + title, + shortAddress, + avatarUrl, + t, + isInAllView, + multisub, + showTextOnlyThreads, + suggested?.avatarUrl, + ]); + + const rows = useMemo(() => { + const rows = []; + for (let i = 0; i < feedWithFakePostsOnTop.length; i += columnCount) { + rows.push(feedWithFakePostsOnTop.slice(i, i + columnCount)); + } + return rows; + }, [feedWithFakePostsOnTop, columnCount]); + + return rows; +}; + +export default useCatalogFeedRows; diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx index eb1a87ad..90ef5d96 100644 --- a/src/views/board/board.tsx +++ b/src/views/board/board.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef } from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { useAccount, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks'; +import { useAccount, useAccountComments, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { useTranslation } from 'react-i18next'; import styles from './board.module.css'; @@ -20,6 +20,20 @@ import SubplebbitRules from '../../components/subplebbit-rules'; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; +// show account comments instantly in the feed instead of waiting for the next feed update, then hide them when they are in the feed +const AccountCommentsNotYetInFeed = ({ subplebbitAddress, feed }: { subplebbitAddress: string; feed: any }) => { + const { accountComments } = useAccountComments(); + + const _feed = [...feed]; + + const filteredComments = accountComments.filter((comment) => { + const { cid, deleted, postCid, removed } = comment || {}; + return !deleted && !removed && cid && cid === postCid && comment?.subplebbitAddress === subplebbitAddress && !_feed.some((post) => post.cid === cid); + }); + + return filteredComments.map((comment) => ); +}; + const Board = () => { const { t } = useTranslation(); const location = useLocation(); @@ -161,6 +175,7 @@ const Board = () => { title={title} /> )} + {subplebbitAddress && } )} { - const { t } = useTranslation(); - const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {}; - const { avatarUrl } = suggested || {}; - const { showTextOnlyThreads } = useCatalogFiltersStore(); - - const location = useLocation(); - const isInAllView = isAllView(location.pathname, useParams()); - const multisub = useMultisubMetadata(); - - const feedWithDescriptionAndRules = useMemo(() => { - if (!isFeedLoaded) { - return []; // prevent rules and description from appearing while feed is loading - } - - if (!description && !rules && !isInAllView) { - return feed; - } - - const _feed = [...feed]; - if ((description && description.length > 0 && (showTextOnlyThreads || (!showTextOnlyThreads && suggested?.avatarUrl))) || isInAllView) { - _feed.unshift({ - isDescription: true, - subplebbitAddress: address, - timestamp: createdAt, - author: { displayName: `## ${t('board_mods')}` }, - content: isInAllView ? multisub?.description : description, - link: avatarUrl, - title: t('welcome_to_board', { board: isInAllView ? multisub?.title : title || `p/${shortAddress}`, interpolation: { escapeValue: false } }), - pinned: true, - locked: true, - }); - } - if (rules && rules.length > 0 && showTextOnlyThreads) { - _feed.unshift({ - isRules: true, - subplebbitAddress: address, - timestamp: createdAt, - author: { displayName: `## ${t('board_mods')}` }, - content: rules.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n'), - title: _.capitalize(t('rules')), - pinned: true, - locked: true, - }); - } - return _feed; - }, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub, showTextOnlyThreads, suggested?.avatarUrl]); - - const rows = useMemo(() => { - const rows = []; - for (let i = 0; i < feedWithDescriptionAndRules.length; i += columnCount) { - rows.push(feedWithDescriptionAndRules.slice(i, i + columnCount)); - } - return rows; - }, [feedWithDescriptionAndRules, columnCount]); - - return rows; -}; - const catalogFilter = (comment: Comment) => { const commentMediaInfo = getCommentMediaInfo(comment); const hasThumbnail = getHasThumbnail(commentMediaInfo, comment?.link); @@ -212,7 +152,7 @@ const Catalog = () => { const isFeedLoaded = feed.length > 0 || state === 'failed'; - const rows = useFeedRows(columnCount, feed, isFeedLoaded, subplebbit); + const rows = useCatalogFeedRows(columnCount, feed, isFeedLoaded, subplebbit); // save the last Virtuoso state to restore it when navigating back const virtuosoRef = useRef(null); From 934b70bded75ed7cae294129556d4cda28e0b0ee Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Sat, 3 Aug 2024 14:51:14 +0200 Subject: [PATCH 07/16] only show account comment in feed if state is 'succeeded' --- src/hooks/use-catalog-feed-rows.ts | 3 ++- src/views/board/board.tsx | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/hooks/use-catalog-feed-rows.ts b/src/hooks/use-catalog-feed-rows.ts index e2119e80..815bd311 100644 --- a/src/hooks/use-catalog-feed-rows.ts +++ b/src/hooks/use-catalog-feed-rows.ts @@ -33,13 +33,14 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea // show account comments instantly in the feed instead of waiting for the next feed update, then hide them when they are in the feed accountComments.forEach((comment) => { - const { cid, deleted, link, postCid, removed, subplebbitAddress } = comment || {}; + const { cid, deleted, link, postCid, removed, state, subplebbitAddress } = comment || {}; const commentMediaInfo = getCommentMediaInfo(comment); const isMediaShowed = getHasThumbnail(commentMediaInfo, link); if ( !deleted && !removed && + state === 'succeeded' && (!showTextOnlyThreads || (showTextOnlyThreads && !isMediaShowed)) && cid && cid === postCid && diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx index 90ef5d96..b19671dd 100644 --- a/src/views/board/board.tsx +++ b/src/views/board/board.tsx @@ -27,8 +27,16 @@ const AccountCommentsNotYetInFeed = ({ subplebbitAddress, feed }: { subplebbitAd const _feed = [...feed]; const filteredComments = accountComments.filter((comment) => { - const { cid, deleted, postCid, removed } = comment || {}; - return !deleted && !removed && cid && cid === postCid && comment?.subplebbitAddress === subplebbitAddress && !_feed.some((post) => post.cid === cid); + const { cid, deleted, postCid, removed, state } = comment || {}; + return ( + !deleted && + !removed && + state === 'succeeded' && + cid && + cid === postCid && + comment?.subplebbitAddress === subplebbitAddress && + !_feed.some((post) => post.cid === cid) + ); }); return filteredComments.map((comment) => ); From 4103fabb12c2b7f57c6c07808af4b7c213408852 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Sat, 3 Aug 2024 22:41:40 +0200 Subject: [PATCH 08/16] feat(post form): add link media info for static or animated gifs --- src/components/post-form/post-form.module.css | 1 + src/components/post-form/post-form.tsx | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/post-form/post-form.module.css b/src/components/post-form/post-form.module.css index 822084b7..82373d42 100644 --- a/src/components/post-form/post-form.module.css +++ b/src/components/post-form/post-form.module.css @@ -89,6 +89,7 @@ .linkType { font-weight: normal; + text-transform: lowercase; } .linkField input[type="text"] { diff --git a/src/components/post-form/post-form.tsx b/src/components/post-form/post-form.tsx index 2da9cd37..67291a5d 100644 --- a/src/components/post-form/post-form.tsx +++ b/src/components/post-form/post-form.tsx @@ -23,6 +23,7 @@ import useChallengesStore from '../../stores/use-challenges-store'; import styles from './post-form.module.css'; import _ from 'lodash'; import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline'; +import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; type SubmitState = { subplebbitAddress: string | undefined; @@ -71,7 +72,16 @@ const useSubmitStore = create((set) => ({ export const LinkTypePreviewer = ({ link }: { link: string }) => { const { t } = useTranslation(); const mediaInfo = getLinkMediaInfo(link); - return isValidURL(link) ? mediaInfo?.type : t('invalid_url'); + let type = mediaInfo?.type; + const gifFrameUrl = useFetchGifFirstFrame(mediaInfo?.url); + + if (type === 'gif' && gifFrameUrl !== null) { + type = t('animated_gif'); + } else if (type === 'gif' && gifFrameUrl === null) { + type = t('static_gif'); + } + + return isValidURL(link) ? type : t('invalid_url'); }; const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { From a992912e109f4640f363c56851a4d7ee3e8dba0a Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Sun, 4 Aug 2024 15:40:09 +0200 Subject: [PATCH 09/16] show newly published account comments after pinned threads --- src/hooks/use-catalog-feed-rows.ts | 29 +++-- src/views/board/board.tsx | 177 +++++++++++++++-------------- 2 files changed, 111 insertions(+), 95 deletions(-) diff --git a/src/hooks/use-catalog-feed-rows.ts b/src/hooks/use-catalog-feed-rows.ts index 815bd311..fdd86b6a 100644 --- a/src/hooks/use-catalog-feed-rows.ts +++ b/src/hooks/use-catalog-feed-rows.ts @@ -31,29 +31,38 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea const _feed = [...feed]; - // show account comments instantly in the feed instead of waiting for the next feed update, then hide them when they are in the feed - accountComments.forEach((comment) => { + // show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update + const filteredComments = accountComments.filter((comment) => { const { cid, deleted, link, postCid, removed, state, subplebbitAddress } = comment || {}; const commentMediaInfo = getCommentMediaInfo(comment); const isMediaShowed = getHasThumbnail(commentMediaInfo, link); - if ( + return ( !deleted && !removed && state === 'succeeded' && - (!showTextOnlyThreads || (showTextOnlyThreads && !isMediaShowed)) && + (showTextOnlyThreads || (!showTextOnlyThreads && isMediaShowed)) && cid && cid === postCid && subplebbitAddress === address && !_feed.some((feedItem) => feedItem.cid === cid) - ) { - _feed.unshift({ - ...comment, - isAccountComment: true, - }); - } + ); }); + // show newest account comment at the top of the feed but after pinned posts + const lastPinnedIndex = _feed.map((post) => post.pinned).lastIndexOf(true); + if (filteredComments.length > 0) { + _feed.splice( + lastPinnedIndex + 1, + 0, + ...filteredComments.map((comment) => ({ + ...comment, + isAccountComment: true, + })), + ); + } + + // add subplebbit description and rules as fake posts at the top of the feed if ((description && description.length > 0 && (showTextOnlyThreads || (!showTextOnlyThreads && suggested?.avatarUrl))) || isInAllView) { _feed.unshift({ isDescription: true, diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx index b19671dd..f10ef3f2 100644 --- a/src/views/board/board.tsx +++ b/src/views/board/board.tsx @@ -20,28 +20,6 @@ import SubplebbitRules from '../../components/subplebbit-rules'; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; -// show account comments instantly in the feed instead of waiting for the next feed update, then hide them when they are in the feed -const AccountCommentsNotYetInFeed = ({ subplebbitAddress, feed }: { subplebbitAddress: string; feed: any }) => { - const { accountComments } = useAccountComments(); - - const _feed = [...feed]; - - const filteredComments = accountComments.filter((comment) => { - const { cid, deleted, postCid, removed, state } = comment || {}; - return ( - !deleted && - !removed && - state === 'succeeded' && - cid && - cid === postCid && - comment?.subplebbitAddress === subplebbitAddress && - !_feed.some((post) => post.cid === cid) - ); - }); - - return filteredComments.map((comment) => ); -}; - const Board = () => { const { t } = useTranslation(); const location = useLocation(); @@ -67,22 +45,60 @@ const Board = () => { const { sortType } = useSortingStore(); const { timeFilterSeconds } = useTimeFilter(); - const feedOptions: any = { - subplebbitAddresses, - sortType, - postsPerPage: isInAllView || isInSubscriptionsView ? 5 : 25, - }; - - if (isInAllView || isInSubscriptionsView) { - feedOptions.newerThan = timeFilterSeconds; - } + const feedOptions: any = useMemo( + () => ({ + subplebbitAddresses, + sortType, + postsPerPage: isInAllView || isInSubscriptionsView ? 5 : 25, + ...(isInAllView || isInSubscriptionsView ? { newerThan: timeFilterSeconds } : {}), + }), + [subplebbitAddresses, sortType, timeFilterSeconds, isInAllView, isInSubscriptionsView], + ); const { feed, hasMore, loadMore, reset } = useFeed(feedOptions); + const { accountComments } = useAccountComments(); + + const resetTriggeredRef = useRef(false); const setResetFunction = useFeedResetStore((state) => state.setResetFunction); useEffect(() => { setResetFunction(reset); - }, [reset, setResetFunction]); + }, [reset, setResetFunction, feed]); + + // show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update + const filteredComments = useMemo( + () => + accountComments.filter((comment) => { + const { cid, deleted, postCid, removed, state } = comment || {}; + return ( + !deleted && + !removed && + state === 'succeeded' && + cid && + cid === postCid && + comment?.subplebbitAddress === subplebbitAddress && + !feed.some((post) => post.cid === cid) + ); + }), + [accountComments, subplebbitAddress, feed], + ); + + // show newest account comment at the top of the feed but after pinned posts + const combinedFeed = useMemo(() => { + const newFeed = [...feed]; + const lastPinnedIndex = newFeed.map((post) => post.pinned).lastIndexOf(true); + if (filteredComments.length > 0) { + newFeed.splice(lastPinnedIndex + 1, 0, ...filteredComments); + } + return newFeed; + }, [feed, filteredComments]); + + useEffect(() => { + if (filteredComments.length > 0 && !resetTriggeredRef.current) { + reset(); + resetTriggeredRef.current = true; + } + }, [filteredComments, reset]); const subplebbit = useSubplebbit({ subplebbitAddress }); const { createdAt, description, error, rules, shortAddress, state, suggested } = subplebbit || {}; @@ -90,6 +106,7 @@ const Board = () => { const { activeCid, closeModal, openReplyModal, showReplyModal, scrollY } = useReplyModal(); + const { blocked, unblock } = useBlock({ address: subplebbitAddress }); const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); const loadingString = (
@@ -97,6 +114,10 @@ const Board = () => { {state} ) : isInSubscriptionsView && subscriptions?.length === 0 ? ( t('not_subscribed_to_any_board') + ) : blocked ? ( + 'you have blocked this board' + ) : !hasMore && feed.length === 0 ? ( + t('no_posts') ) : ( )} @@ -109,40 +130,8 @@ const Board = () => {
); - const { blocked, unblock } = useBlock({ address: subplebbitAddress }); - const Footer = () => { - let footerContent; - if (feed.length === 0) { - if (blocked) { - footerContent = 'you have blocked this board'; - } else { - footerContent = t('no_posts'); - } - } - if (hasMore || subplebbitAddresses.length === 0) { - footerContent = loadingString; - } - return ( -
- {footerContent} - {blocked && ( - <> -   [ - { - unblock(); - reset(); - }} - > - Unblock - - ] - - )} -
- ); + return
{loadingString}
; }; // save the last Virtuoso state to restore it when navigating back @@ -170,7 +159,7 @@ const Board = () => {
{location.pathname.endsWith('/settings') && } {showReplyModal && activeCid && } - {feed.length > 0 && ( + {feed.length !== 0 ? ( <> {rules && rules.length > 0 && } {((description && description.length > 0) || isInAllView) && ( @@ -183,26 +172,44 @@ const Board = () => { title={title} /> )} - {subplebbitAddress && } - - )} - { - const { deleted, locked, removed } = post || {}; - const isThreadClosed = deleted || locked || removed; + { + const { deleted, locked, removed } = post || {}; + const isThreadClosed = deleted || locked || removed; - return alert(t('thread_closed_alert')) : openReplyModal} />; - }} - useWindowScroll={true} - components={{ Footer }} - endReached={loadMore} - ref={virtuosoRef} - restoreStateFrom={lastVirtuosoState} - initialScrollTop={lastVirtuosoState?.scrollTop} - /> + return alert(t('thread_closed_alert')) : openReplyModal} />; + }} + useWindowScroll={true} + components={{ Footer }} + endReached={loadMore} + ref={virtuosoRef} + restoreStateFrom={lastVirtuosoState} + initialScrollTop={lastVirtuosoState?.scrollTop} + /> + + ) : ( +
+ {loadingString} + {blocked && ( + <> +   [ + { + unblock(); + reset(); + }} + > + Unblock + + ] + + )} +
+ )}
); }; From f06c50ef33a3b5fc82c8532c144fd87e7be291d4 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Mon, 5 Aug 2024 11:58:33 +0200 Subject: [PATCH 10/16] fix(theme): changing theme in sfw sub wouldn't change it for p/all and p/subscriptions. it should because sfw is the default --- src/hooks/use-initial-theme.ts | 6 ++---- src/hooks/use-theme.ts | 12 ++++-------- src/stores/use-theme-store.ts | 6 ------ 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/hooks/use-initial-theme.ts b/src/hooks/use-initial-theme.ts index 571b06f2..be572afc 100644 --- a/src/hooks/use-initial-theme.ts +++ b/src/hooks/use-initial-theme.ts @@ -19,10 +19,8 @@ const useInitialTheme = () => { if (isInPendingPostView) { return currentTheme || 'yotsuba'; - } else if (isInAllView) { - return getTheme('all') || 'yotsuba-b'; - } else if (isInSubscriptionsView) { - return getTheme('subscriptions') || 'yotsuba-b'; + } else if (isInAllView || isInSubscriptionsView) { + return getTheme('sfw') || 'yotsuba-b'; } else if (isInHomeView || isInNotFoundView) { return 'yotsuba'; } else if (subplebbitAddress) { diff --git a/src/hooks/use-theme.ts b/src/hooks/use-theme.ts index b6b7734b..4af9d43f 100644 --- a/src/hooks/use-theme.ts +++ b/src/hooks/use-theme.ts @@ -44,10 +44,8 @@ const useTheme = (): [string, (theme: string) => void] => { const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); let storedTheme = null; - if (isInAllView) { - storedTheme = getTheme('all'); - } else if (isInSubscriptionsView) { - storedTheme = getTheme('subscriptions'); + if (isInAllView || isInSubscriptionsView) { + storedTheme = getTheme('sfw'); } else if (subplebbitAddress) { const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress); if (subplebbit && subplebbit.tags && subplebbit.tags.some((tag) => nsfwTags.includes(tag))) { @@ -67,10 +65,8 @@ const useTheme = (): [string, (theme: string) => void] => { const isInAllView = isAllView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); - if (isInAllView) { - await setThemeStore('all', newTheme); - } else if (isInSubscriptionsView) { - await setThemeStore('subscriptions', newTheme); + if (isInAllView || isInSubscriptionsView) { + await setThemeStore('sfw', newTheme); } else if (subplebbitAddress) { const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress); if (subplebbit && subplebbit.tags && subplebbit.tags.some((tag) => nsfwTags.includes(tag))) { diff --git a/src/stores/use-theme-store.ts b/src/stores/use-theme-store.ts index cda02a4f..b46b9e74 100644 --- a/src/stores/use-theme-store.ts +++ b/src/stores/use-theme-store.ts @@ -5,8 +5,6 @@ interface ThemeState { themes: { nsfw: string; sfw: string; - all: string; - subscriptions: string; }; currentTheme: string | null; setTheme: (category: keyof ThemeState['themes'], theme: string) => void; @@ -23,8 +21,6 @@ const useThemeStore = create((set: StoreApi['setState'], themes: { nsfw: 'yotsuba', sfw: 'yotsuba-b', - all: 'yotsuba-b', - subscriptions: 'yotsuba-b', }, currentTheme: null, setTheme: async (category, theme) => { @@ -44,8 +40,6 @@ const useThemeStore = create((set: StoreApi['setState'], const themes: Record = { nsfw: 'yotsuba', sfw: 'yotsuba-b', - all: 'yotsuba-b', - subscriptions: 'yotsuba-b', }; entries.forEach(([key, value]) => { themes[key] = value; From db67a9452c048c5abc954cbae345269495ab6c65 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Tue, 6 Aug 2024 20:12:07 +0200 Subject: [PATCH 11/16] feat(settings): add anon mode - automatically use a different user ID in each thread --- src/components/post-desktop/post-desktop.tsx | 2 +- src/components/post-form/post-form.tsx | 112 +++++++++++++++--- src/components/post-mobile/post-mobile.tsx | 2 +- src/components/reply-modal/reply-modal.tsx | 51 ++++++-- .../account-settings.module.css | 25 +++- .../account-settings/account-settings.tsx | 17 ++- src/hooks/use-anon-mode.ts | 52 ++++++++ src/hooks/use-reply-modal.ts | 10 +- src/hooks/use-reply.ts | 70 +++++------ src/stores/use-anon-mode-store.ts | 60 ++++++++++ src/views/board/board.tsx | 4 +- src/views/post/post.tsx | 6 +- 12 files changed, 334 insertions(+), 77 deletions(-) create mode 100644 src/hooks/use-anon-mode.ts create mode 100644 src/stores/use-anon-mode-store.ts diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index 77860263..556c9860 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -138,7 +138,7 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }: !cid && e.preventDefault()}> c/ - openReplyModal && openReplyModal(cid)}> + openReplyModal && openReplyModal(cid, postCid)}> {shortCid} diff --git a/src/components/post-form/post-form.tsx b/src/components/post-form/post-form.tsx index 67291a5d..fb749158 100644 --- a/src/components/post-form/post-form.tsx +++ b/src/components/post-form/post-form.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { @@ -24,8 +24,11 @@ import styles from './post-form.module.css'; import _ from 'lodash'; import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; +import useAnonMode from '../../hooks/use-anon-mode'; type SubmitState = { + author: any | undefined; + signer: any | undefined; subplebbitAddress: string | undefined; title: string | undefined; content: string | undefined; @@ -39,15 +42,19 @@ type SubmitState = { const { addChallenge } = useChallengesStore.getState(); const useSubmitStore = create((set) => ({ + author: undefined, + signer: undefined, subplebbitAddress: undefined, title: undefined, content: undefined, link: undefined, spoiler: undefined, publishCommentOptions: {}, - setSubmitStore: ({ subplebbitAddress, title, content, link, spoiler }) => + setSubmitStore: ({ author, signer, subplebbitAddress, title, content, link, spoiler }) => set((state) => { const nextState = { ...state }; + if (author !== undefined) nextState.author = author; + if (signer !== undefined) nextState.signer = signer; if (subplebbitAddress !== undefined) nextState.subplebbitAddress = subplebbitAddress; if (title !== undefined) nextState.title = title || undefined; if (content !== undefined) nextState.content = content || undefined; @@ -55,7 +62,13 @@ const useSubmitStore = create((set) => ({ if (spoiler !== undefined) nextState.spoiler = spoiler || undefined; nextState.publishCommentOptions = { - ...nextState, + author: nextState.author, + signer: nextState.signer, + subplebbitAddress: nextState.subplebbitAddress, + title: nextState.title, + content: nextState.content, + link: nextState.link, + spoiler: nextState.spoiler, onChallenge: (...args: any) => addChallenge(args), onChallengeVerification: alertChallengeVerificationFailed, onError: (error: Error) => { @@ -66,7 +79,17 @@ const useSubmitStore = create((set) => ({ }; return nextState; }), - resetSubmitStore: () => set({ subplebbitAddress: undefined, title: undefined, content: undefined, link: undefined, spoiler: undefined, publishCommentOptions: {} }), + resetSubmitStore: () => + set({ + author: undefined, + signer: undefined, + subplebbitAddress: undefined, + title: undefined, + content: undefined, + link: undefined, + spoiler: undefined, + publishCommentOptions: {}, + }), })); export const LinkTypePreviewer = ({ link }: { link: string }) => { @@ -84,10 +107,11 @@ export const LinkTypePreviewer = ({ link }: { link: string }) => { return isValidURL(link) ? type : t('invalid_url'); }; -const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { +const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid: string }) => { const { t } = useTranslation(); const account = useAccount(); - const { displayName } = account?.author || {}; + const author = account?.author || {}; + const { displayName } = author || {}; const [url, setUrl] = useState(''); const { title, content, link, publishCommentOptions, setSubmitStore, resetSubmitStore } = useSubmitStore(); const { index, publishComment } = usePublishComment(publishCommentOptions); @@ -102,6 +126,10 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { const subscriptions = account?.subscriptions || []; const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses(); + const { anonMode, getNewSigner, getExistingSigner } = useAnonMode(postCid); + const comment = useComment({ commentCid: postCid }); + const address = comment?.author?.address; + const resetFields = () => { if (textRef.current) { textRef.current.value = ''; @@ -114,17 +142,32 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { } }; - const onPublishPost = () => { + const getAnonAddressForPost = async () => { + if (anonMode) { + const newSigner = (await getNewSigner()) || {}; + setSubmitStore({ + signer: newSigner, + author: { + displayName, + address: newSigner.address, + }, + }); + } + }; + + const onPublishPost = async () => { if (!title && !content && !link) { alert(`Cannot post empty comment`); return; } if (link && !isValidURL(link)) { - alert(`Invalid link`); + alert('The provided link is not a valid URL.'); return; } - publishComment(); + getAnonAddressForPost().then(() => { + publishComment(); + }); }; const params = useParams(); @@ -149,7 +192,32 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { // in post page, publish a reply to the post const isInPostView = isPostPageView(location.pathname, params); const cid = params?.commentCid as string; - const { setContent, resetContent, replyIndex, publishReply } = useReply({ cid, subplebbitAddress }); + const { setPublishReplyOptions, resetPublishReplyOptions, replyIndex, publishReply } = useReply({ cid, subplebbitAddress }); + + const getAnonAddressForReply = async () => { + if (anonMode) { + const existingSigner = getExistingSigner(address); + if (existingSigner) { + console.log('onPublishReply - existingSigner:', existingSigner); + setPublishReplyOptions({ + signer: existingSigner, + author: { + displayName, + address: existingSigner.address, + }, + }); + } else { + const newSigner = (await getNewSigner()) || {}; + setPublishReplyOptions({ + signer: newSigner, + author: { + displayName, + address: newSigner.address, + }, + }); + } + } + }; const onPublishReply = () => { const currentContent = textRef.current?.value || ''; @@ -164,16 +232,19 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { alert('The provided link is not a valid URL.'); return; } - publishReply(); + + getAnonAddressForReply().then(() => { + publishReply(); + }); }; useEffect(() => { if (typeof replyIndex === 'number') { - resetContent(); + resetPublishReplyOptions(); resetFields(); closeForm(); } - }, [replyIndex, resetContent, closeForm]); + }, [replyIndex, resetPublishReplyOptions, closeForm]); return ( @@ -215,7 +286,7 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { ref={textRef} onChange={(e) => { const content = e.target.value.replace(/\n/g, '\n\n'); - isInPostView ? setContent.content(content) : setSubmitStore({ content }); + isInPostView ? setPublishReplyOptions({ content }) : setSubmitStore({ content }); }} /> @@ -231,7 +302,7 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { ref={urlRef} onChange={(e) => { setUrl(e.target.value); - isInPostView ? setContent.link(e.target.value) : setSubmitStore({ link: e.target.value }); + isInPostView ? setPublishReplyOptions({ link: e.target.value }) : setSubmitStore({ link: e.target.value }); }} /> {url && } @@ -242,7 +313,10 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => {
[ ] @@ -293,7 +367,7 @@ const PostForm = () => { comment = editedComment; } - const { deleted, locked, removed } = comment || {}; + const { deleted, locked, removed, postCid } = comment || {}; const isThreadClosed = deleted || locked || removed || isInDescriptionView || isInRulesView; const [showForm, setShowForm] = useState(false); @@ -320,7 +394,7 @@ const PostForm = () => { ] ) : ( - setShowForm(false)} /> + setShowForm(false)} postCid={postCid} /> )}
@@ -336,7 +410,7 @@ const PostForm = () => { - {showForm && setShowForm(false)} />} + {showForm && setShowForm(false)} postCid={postCid} />}
)} diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 01e9c3af..025b65e5 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -127,7 +127,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P !cid && e.preventDefault()}> c/ - openReplyModal && openReplyModal(cid)}> + openReplyModal && openReplyModal(cid, postCid)}> {shortCid} diff --git a/src/components/reply-modal/reply-modal.tsx b/src/components/reply-modal/reply-modal.tsx index c8842b93..2020722d 100644 --- a/src/components/reply-modal/reply-modal.tsx +++ b/src/components/reply-modal/reply-modal.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { useLocation, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import Draggable from 'react-draggable'; -import { setAccount, useAccount, useSubplebbit } from '@plebbit/plebbit-react-hooks'; +import { setAccount, useAccount, useComment, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; import { getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { isValidURL } from '../../lib/utils/url-utils'; @@ -13,17 +13,19 @@ import useIsMobile from '../../hooks/use-is-mobile'; import styles from './reply-modal.module.css'; import { LinkTypePreviewer } from '../post-form'; import _ from 'lodash'; +import useAnonMode from '../../hooks/use-anon-mode'; interface ReplyModalProps { closeModal: () => void; parentCid: string; + postCid: string; scrollY: number; } -const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { +const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps) => { const { t } = useTranslation(); const { subplebbitAddress } = useParams() as { subplebbitAddress: string }; - const { setContent, publishReply } = useReply({ cid: parentCid, subplebbitAddress }); + const { setPublishReplyOptions, publishReply } = useReply({ cid: parentCid, subplebbitAddress }); const account = useAccount(); const { displayName } = account?.author || {}; const [url, setUrl] = useState(''); @@ -31,7 +33,35 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { const urlRef = useRef(null); const { selectedText } = useSelectedTextStore(); - const onPublishReply = () => { + const { anonMode, getNewSigner, getExistingSigner } = useAnonMode(postCid); + const comment = useComment({ commentCid: postCid }); + const address = comment?.author?.address; + + const getAnonAddressForReply = async () => { + if (anonMode) { + const existingSigner = getExistingSigner(address); + if (existingSigner) { + setPublishReplyOptions({ + signer: existingSigner, + author: { + displayName, + address: existingSigner.address, + }, + }); + } else { + const newSigner = (await getNewSigner()) || {}; + setPublishReplyOptions({ + signer: newSigner, + author: { + displayName, + address: newSigner.address, + }, + }); + } + } + }; + + const onPublishReply = async () => { const currentContent = textRef.current?.value || ''; const currentUrl = urlRef.current?.value || ''; @@ -44,8 +74,11 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { alert('The provided link is not a valid URL.'); return; } - publishReply(); - closeModal(); + + getAnonAddressForReply().then(() => { + publishReply(); + closeModal(); + }); }; const nodeRef = useRef(null); @@ -109,7 +142,7 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { // remove the prefix from the content to publish, and also add newlines for markdown const contentWithoutPrefix = e.target.value.slice(contentPrefix.length).replace(/\n/g, '\n\n'); if (textRef.current && textRef.current.value !== contentWithoutPrefix) { - setContent.content(contentWithoutPrefix); + setPublishReplyOptions({ content: contentWithoutPrefix }); } }; @@ -142,7 +175,7 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { placeholder={_.capitalize(t('link'))} onChange={(e) => { setUrl(e.target.value); - setContent.link(e.target.value); + setPublishReplyOptions({ link: e.target.value }); }} />
@@ -169,7 +202,7 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { [ ] diff --git a/src/components/settings-modal/account-settings/account-settings.module.css b/src/components/settings-modal/account-settings/account-settings.module.css index 2371bb88..1fd66e37 100644 --- a/src/components/settings-modal/account-settings/account-settings.module.css +++ b/src/components/settings-modal/account-settings/account-settings.module.css @@ -36,11 +36,32 @@ } .deleteAccount { - color: red; display: block; - margin-top: 10px; +} + +.deleteAccount:not(:disabled) { + color: red; } .setting .createAccount { margin-left: 5px; +} + +.settingTip { + display: block; + font-size: 0.85em; + margin: 2px 0 10px 0; + padding-left: 19px; +} + +.anonMode { + padding: 10px 0; +} + +.anonMode label { + text-transform: capitalize; +} + +.anonMode input[type="checkbox"] { + margin-right: 3px; } \ No newline at end of file diff --git a/src/components/settings-modal/account-settings/account-settings.tsx b/src/components/settings-modal/account-settings/account-settings.tsx index df7e74b6..c8a48dc8 100644 --- a/src/components/settings-modal/account-settings/account-settings.tsx +++ b/src/components/settings-modal/account-settings/account-settings.tsx @@ -3,6 +3,21 @@ import { useTranslation } from 'react-i18next'; import { createAccount, deleteAccount, exportAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts } from '@plebbit/plebbit-react-hooks'; import stringify from 'json-stringify-pretty-compact'; import styles from './account-settings.module.css'; +import useAnonModeStore from '../../../stores/use-anon-mode-store'; + +const AnonMode = () => { + const anonMode = useAnonModeStore((state) => state.anonMode); + const setAnonMode = useAnonModeStore((state) => state.setAnonMode); + + return ( +
+ + Automatically use a different user ID in each thread +
+ ); +}; const AccountSettings = () => { const { t } = useTranslation(); @@ -167,7 +182,7 @@ const AccountSettings = () => {