Merge branch 'refactor/remove-nft-avatar' into development

This commit is contained in:
plebeius
2026-02-19 17:53:05 +08:00
47 changed files with 17 additions and 688 deletions
+1 -9
View File
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { Comment, useAuthorAvatar, useEditedComment, useReplies, useAccount, useAccountComment } from '@plebbit/plebbit-react-hooks';
import { Comment, useEditedComment, useReplies, useAccount, useAccountComment } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js';
import styles from '../../views/post/post.module.css';
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
@@ -14,7 +14,6 @@ import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
import useModQueueStore from '../../stores/use-mod-queue-store';
import { useDirectories } from '../../hooks/use-directories';
import { getBoardPath } from '../../lib/utils/route-utils';
import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store';
import useAuthorAddressClick from '../../hooks/use-author-address-click';
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
@@ -100,8 +99,6 @@ const PostInfo = ({
const hasFailedState = state === 'failed';
const isReply = parentCid;
const { showOmittedReplies } = useShowOmittedReplies();
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
const { hideAvatars } = useAvatarVisibilityStore();
const directories = useDirectories();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : undefined;
const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]);
@@ -287,11 +284,6 @@ const PostInfo = ({
</span>
)}{' '}
</span>
{author?.avatar && !(deleted || removed) && !hideAvatars && avatarImageUrl ? (
<span className={styles.authorAvatar}>
<img src={avatarImageUrl} alt='' />
</span>
) : null}
{showUserID && (
<>
(ID:{' '}
+1 -9
View File
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { Comment, useAuthorAvatar, useEditedComment, useReplies, useAccount, usePublishCommentModeration, useAccountComment } from '@plebbit/plebbit-react-hooks';
import { Comment, useEditedComment, useReplies, useAccount, usePublishCommentModeration, useAccountComment } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js';
import styles from '../../views/post/post.module.css';
import { shouldShowSnow } from '../../lib/snow';
@@ -14,7 +14,6 @@ import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
import useModQueueStore from '../../stores/use-mod-queue-store';
import { useDirectories } from '../../hooks/use-directories';
import { getBoardPath } from '../../lib/utils/route-utils';
import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store';
import useAuthorAddressClick from '../../hooks/use-author-address-click';
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
@@ -65,8 +64,6 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
const { address, shortAddress } = author || {};
const displayName = author?.displayName?.trim();
const authorRole = roles?.[address]?.role?.replace('moderator', 'mod');
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
const { hideAvatars } = useAvatarVisibilityStore();
const params = useParams();
const location = useLocation();
@@ -252,11 +249,6 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
</span>
)}
</span>
{author?.avatar && !(deleted || removed) && !hideAvatars && avatarImageUrl ? (
<span className={styles.authorAvatar}>
<img src={avatarImageUrl} alt='' />
</span>
) : null}
{showUserID && (
<>
(ID: {''}
@@ -18,7 +18,17 @@ const AccountSettingsEditor = ({
const location = useLocation();
const accountJson = useMemo(
() => stringify({ account: { ...account, plebbit: undefined, karma: undefined, plebbitReactOptions: undefined, unreadNotificationCount: undefined } }),
() =>
stringify({
account: {
...account,
author: { ...account?.author, avatar: undefined },
plebbit: undefined,
karma: undefined,
plebbitReactOptions: undefined,
unreadNotificationCount: undefined,
},
}),
[account],
);
@@ -10,7 +10,6 @@ interface SettingsProps {
httpRoutersRef?: RefObject<HTMLTextAreaElement>;
ethRpcRef?: RefObject<HTMLTextAreaElement>;
solRpcRef?: RefObject<HTMLTextAreaElement>;
maticRpcRef?: RefObject<HTMLTextAreaElement>;
p2pRpcRef?: RefObject<HTMLInputElement>;
p2pDataPathRef?: RefObject<HTMLInputElement>;
}
@@ -36,7 +35,7 @@ const IPFSGatewaysSettings = ({ ipfsGatewayUrlsRef, mediaIpfsGatewayUrlRef }: Se
rows={ipfsGatewayUrls?.length || 1}
/>
</div>
<span className={styles.settingTip}>NFT profile pics gateway</span>
<span className={styles.settingTip}>media IPFS gateway</span>
<div>
<input
type='text'
@@ -100,13 +99,12 @@ const HttpRoutersSettings = ({ httpRoutersRef }: SettingsProps) => {
);
};
const BlockchainProvidersSettings = ({ ethRpcRef, solRpcRef, maticRpcRef }: SettingsProps) => {
const BlockchainProvidersSettings = ({ ethRpcRef, solRpcRef }: SettingsProps) => {
const account = useAccount();
const { plebbitOptions } = account || {};
const { chainProviders } = plebbitOptions || {};
const ethRpcDefaultValue = chainProviders?.['eth']?.urls.join('\n');
const solRpcDefaultValue = chainProviders?.['sol']?.urls.join('\n');
const maticRpcDefaultValue = chainProviders?.['matic']?.urls.join('\n');
return (
<div className={styles.blockchainProvidersSettings}>
@@ -132,17 +130,6 @@ const BlockchainProvidersSettings = ({ ethRpcRef, solRpcRef, maticRpcRef }: Sett
rows={chainProviders?.['sol']?.urls?.length || 1}
/>
</div>
<span className={styles.settingTip}>Polygon RPC, for avatar NFTs</span>
<div>
<textarea
defaultValue={maticRpcDefaultValue}
ref={maticRpcRef}
autoCorrect='off'
autoComplete='off'
spellCheck='false'
rows={chainProviders?.['matic']?.urls?.length || 1}
/>
</div>
</div>
);
};
@@ -204,7 +191,6 @@ const AdvancedSettings = () => {
const pubsubProvidersRef = useRef<HTMLTextAreaElement>(null);
const ethRpcRef = useRef<HTMLTextAreaElement>(null);
const solRpcRef = useRef<HTMLTextAreaElement>(null);
const maticRpcRef = useRef<HTMLTextAreaElement>(null);
const httpRoutersRef = useRef<HTMLTextAreaElement>(null);
const p2pRpcRef = useRef<HTMLInputElement>(null);
const p2pDataPathRef = useRef<HTMLInputElement>(null);
@@ -232,11 +218,6 @@ const AdvancedSettings = () => {
.map((url) => url.trim())
.filter((url) => url !== '');
const maticRpcUrls = maticRpcRef.current?.value
.split('\n')
.map((url) => url.trim())
.filter((url) => url !== '');
const httpRoutersOptions = httpRoutersRef.current?.value
.split('\n')
.map((url) => url.trim())
@@ -254,10 +235,6 @@ const AdvancedSettings = () => {
urls: solRpcUrls,
chainId: 101,
},
matic: {
urls: maticRpcUrls,
chainId: 137,
},
};
try {
@@ -314,7 +291,7 @@ const AdvancedSettings = () => {
blockchain providers:
</span>
<span className={styles.categorySettings}>
<BlockchainProvidersSettings ethRpcRef={ethRpcRef} solRpcRef={solRpcRef} maticRpcRef={maticRpcRef} />
<BlockchainProvidersSettings ethRpcRef={ethRpcRef} solRpcRef={solRpcRef} />
</span>
</div>
<div className={styles.category}>
@@ -1,123 +0,0 @@
.avatar {
width: 64px;
height: 64px;
border: 1px solid #aaa;
background-color: var(--avatar-background-color, white);
margin-left: 10px;
overflow: hidden;
}
.avatarPreview {
display: inline-block;
}
.avatar img {
width: 64px;
height: 64px;
}
.emptyAvatar {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
color: var(--avatar-text-color);
}
.avatarSettingsForm {
padding: 5px 0 15px 10px;
}
.settingField {
margin-top: 5px;
margin-left: 5px;
position: relative;
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.settingField input {
flex: 0 1 200px;
min-width: 0;
max-width: 200px;
padding: 2px;
margin-left: 5px;
box-shadow: var(--box-shadow-input);
}
.settingField[class*='step']::before {
margin-left: -5px;
}
.settingField.step1::before {
content: '1.\00a0';
}
.settingField.step2::before {
content: '2.\00a0';
}
.settingField.step3::before {
content: '3.\00a0';
}
.settingField.step4::before {
content: '4.\00a0';
}
.settingField.step5::before {
content: '5.\00a0';
}
.settingField a {
color: var(--post-link-text-color);
text-decoration: var(--post-content-link-text-decoration);
}
.settingField a:hover {
color: var(--post-link-text-color-hover);
text-decoration: var(--post-content-link-text-decoration-hover);
}
.settingTitle {
font-style: inherit;
text-transform: none;
white-space: nowrap;
flex-shrink: 0;
}
.timestampfield {
margin-left: 10px;
}
.buttons {
margin-top: 10px;
margin-left: 5px;
}
.save {
margin-right: 5px;
}
.state {
text-transform: lowercase;
padding-top: 10px;
padding-left: 10px;
max-width: 300px;
color: var(--post-mobile-abbr-text-color);
}
.error {
color: red;
}
.deleteAvatarContainer {
margin-top: 20px;
margin-left: 5px;
}
.removeAvatar {
color: red;
}
@@ -1,238 +0,0 @@
import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { setAccount, useAccount, useAuthorAvatar } from '@plebbit/plebbit-react-hooks';
import styles from './avatar-settings.module.css';
import { Trans, useTranslation } from 'react-i18next';
import LoadingEllipsis from '../../loading-ellipsis';
import ErrorDisplay from '../../error-display/error-display';
import capitalize from 'lodash/capitalize';
const AvatarPreview = ({ avatar }: any) => {
const { t } = useTranslation();
const account = useAccount();
let author = useMemo(() => ({ ...account?.author, avatar }), [account, avatar]);
const { imageUrl, state, error } = useAuthorAvatar({ author });
// if avatar already set, and user hasn't typed anything yet, preview already set author
if (account?.author?.avatar && !avatar?.chainTicker && !avatar?.address && !avatar?.id && !avatar?.signature) {
author = account.author;
}
// not enough data to preview yet
if (!author?.avatar?.address && !author?.avatar?.signature) {
return;
}
const stateText = state !== 'succeeded' ? <LoadingEllipsis string={state} /> : undefined;
return (
<>
<div className={styles.avatar}>
{imageUrl && state !== 'initializing' ? <img src={imageUrl} alt='' /> : <span className={styles.emptyAvatar}>{t('none')}</span>}
</div>
{state !== 'succeeded' && account?.author?.avatar && (
<div className={styles.state}>
{!error && stateText} {error && <ErrorDisplay error={error} />}
</div>
)}
</>
);
};
const AvatarSettings = () => {
const { t } = useTranslation();
const account = useAccount();
const authorAddress = account?.author?.address;
const [chainTicker, setChainTicker] = useState(account?.author?.avatar?.chainTicker);
const [tokenAddress, setTokenAddress] = useState(account?.author?.avatar?.address);
const [tokenId, setTokenId] = useState(account?.author?.avatar?.id);
const [timestamp, setTimestamp] = useState(account?.author?.avatar?.timestamp);
const [signature, setSignature] = useState(account?.author?.avatar?.signature?.signature);
const getNftMessageToSign = (authorAddress: string, timestamp: number, tokenAddress: string, tokenId: string) => {
let messageToSign: any = {};
// the property names must be in this order for the signature to match
// insert props one at a time otherwise babel/webpack will reorder
messageToSign.domainSeparator = 'plebbit-author-avatar';
messageToSign.authorAddress = authorAddress;
messageToSign.timestamp = timestamp;
messageToSign.tokenAddress = tokenAddress;
messageToSign.tokenId = String(tokenId); // must be a type string, not number
// use plain JSON so the user can read what he's signing
messageToSign = JSON.stringify(messageToSign);
return messageToSign;
};
const [hasCopied, setHasCopied] = useState(false);
const copyMessageToSign = () => {
if (!chainTicker) {
return alert(t('missing_chain_ticker'));
}
if (!tokenAddress) {
return alert(t('missing_token_address'));
}
if (!tokenId) {
return alert(t('missing_token_id'));
}
const newTimestamp = Math.floor(Date.now() / 1000);
const messageToSign = getNftMessageToSign(authorAddress, newTimestamp, tokenAddress, tokenId);
// update timestamp every time the user gets a new message to sign
setTimestamp(newTimestamp);
navigator.clipboard.writeText(messageToSign);
setHasCopied(true);
setTimeout(() => {
setHasCopied(false);
}, 2000);
};
// how to resolve and verify NFT signatures https://github.com/plebbit/plebbit-js/blob/master/docs/nft.md
const avatar = {
chainTicker: chainTicker?.toLowerCase() || account?.author?.avatar?.chainTicker,
timestamp,
address: tokenAddress || account?.author?.avatar?.address,
id: tokenId || account?.author?.avatar?.id,
signature: {
signature: signature || account?.author?.avatar?.signature?.signature,
type: 'eip191',
},
};
const save = () => {
if (!chainTicker) {
return alert(t('missing_chain_ticker'));
}
if (!tokenAddress) {
return alert(t('missing_token_address'));
}
if (!tokenId) {
return alert(t('missing_token_id'));
}
if (!signature) {
return alert(t('missing_signature'));
}
setAccount({ ...account, author: { ...account?.author, avatar } });
alert(t('saved'));
};
const _removeAvatar = () => {
if (window.confirm(t('delete_confirm', { value: 'avatar', interpolation: { escapeValue: false } }))) {
setAccount({ ...account, author: { ...account?.author, avatar: undefined } });
setChainTicker(undefined);
setTokenAddress(undefined);
setTokenId(undefined);
setTimestamp(undefined);
setSignature(undefined);
}
};
return (
<div className={styles.avatarSettings}>
<AvatarPreview avatar={avatar} />
<div className={styles.avatarSettingsForm}>
<div className={`${styles.settingField} ${styles.step1}`}>
<span className={styles.settingTitle}>{capitalize(t('chain_ticker'))}: </span>
<input
type='text'
placeholder='eth/sol/matic'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.chainTicker}
onChange={(e) => setChainTicker(e.target.value)}
/>
</div>
<div className={`${styles.settingField} ${styles.step2}`}>
<span className={styles.settingTitle}>
<Trans
i18nKey='token_address_whitelist'
shouldUnescape={true}
components={{
1: (
<Link
key='token-address-link'
to='https://github.com/plebbit/plebbit-react-hooks/blob/557cc3f40b5933a00553ed9c0bc310d2cd7a3b52/src/hooks/authors/author-avatars.ts#L133'
target='_blank'
rel='noopener noreferrer'
/>
),
}}
/>
:{' '}
</span>
<input
type='text'
placeholder='0x...'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.address}
onChange={(e) => setTokenAddress(e.target.value)}
/>
</div>
<div className={`${styles.settingField} ${styles.step3}`}>
<span className={styles.settingTitle}>Token ID: </span>
<input
type='text'
placeholder='123'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.id}
onChange={(e) => setTokenId(e.target.value)}
/>
</div>
<div className={`${styles.settingField} ${styles.step4}`}>
<span className={styles.settingTitle}>
<Trans
i18nKey='copy_message_etherscan'
values={{ copy: hasCopied ? t('copied') : t('copy') }}
components={{
1: <button key='copy-message-button' onClick={copyMessageToSign} />,
// eslint-disable-next-line
2: <a key='etherscan-link' href='https://etherscan.io/verifiedSignatures' target='_blank' rel='noopener noreferrer' />,
}}
/>
</span>
</div>
<div className={styles.settingField}>
<span className={`${styles.settingTitle} ${styles.timestampfield}`}>{capitalize(t('timestamp'))}: </span>
<input
type='text'
placeholder='1234567890'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
value={timestamp || ''}
onChange={(e) => setTimestamp(Number(e.target.value))}
/>
</div>
<div className={`${styles.settingField} ${styles.step5}`}>
<span className={styles.settingTitle}>{capitalize(t('paste_signature'))}: </span>
<input
type='text'
placeholder='0x...'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.signature?.signature}
onChange={(e) => setSignature(e.target.value)}
/>
</div>
<div className={styles.buttons}>
<button className={styles.save} onClick={save}>
{t('save_changes')}
</button>
</div>
<div className={styles.deleteAvatarContainer}>
<button className={styles.removeAvatar} onClick={_removeAvatar}>
{t('delete_avatar')}
</button>
</div>
</div>
</div>
);
};
export default AvatarSettings;
@@ -1 +0,0 @@
export { default } from './avatar-settings';
@@ -102,7 +102,7 @@ const CryptoWalletsForm = ({ account }: { account: Account | undefined }) => {
walletsArray.length > 0 ? (
<div key={selectedWallet} className={styles.walletBox}>
<div className={`${styles.walletField} ${styles.step1}`}>
<span className={styles.walletFieldTitle}>{capitalize(t('chain_ticker'))}: </span>
<span className={styles.walletFieldTitle}>Chain ticker: </span>
<input
type='text'
onChange={(e) => setWalletsArrayProperty(selectedWallet, 'chainTicker', e.target.value)}
@@ -1,6 +1,5 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import useAvatarVisibilityStore from '../../../stores/use-avatar-visibility-store';
import useTheme from '../../../hooks/use-theme';
import packageJson from '../../../../package.json';
import styles from './interface-settings.module.css';
@@ -124,13 +123,8 @@ const InterfaceLanguage = () => {
const InterfaceSettings = () => {
const { t } = useTranslation();
const { hideAvatars, setHideAvatars } = useAvatarVisibilityStore();
const { fitExpandedImagesToScreen, setFitExpandedImagesToScreen } = useExpandedMediaStore();
const handleHideAvatarsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setHideAvatars(e.target.checked);
};
return (
<div className={styles.interfaceSettings}>
<div className={styles.version}>
@@ -152,12 +146,6 @@ const InterfaceSettings = () => {
</label>
<div className={styles.settingTip}>{capitalize(t('fit_expanded_images_to_screen_tip'))}</div>
</div>
<div className={styles.setting}>
<label>
<input type='checkbox' checked={hideAvatars} onChange={handleHideAvatarsChange} /> {capitalize(t('hide_avatars'))}
</label>
<div className={styles.settingTip}>{capitalize(t('hide_avatars_tip'))}</div>
</div>
</div>
);
};
@@ -3,7 +3,6 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import styles from './settings-modal.module.css';
import AccountSettings from './account-settings';
import AvatarSettings from './avatar-settings';
import CryptoAddressSetting from './crypto-address-setting';
import CryptoWalletsSetting from './crypto-wallets-setting';
import InterfaceSettings from './interface-settings';
@@ -38,7 +37,6 @@ const SettingsModal = () => {
const [showInterfaceSettings, setShowInterfaceSettings] = useState(false);
const [showMediaHostingSettings, setShowMediaHostingSettings] = useState(false);
const [showAccountSettings, setShowAccountSettings] = useState(false);
const [showAvatarSettings, setShowAvatarSettings] = useState(false);
const [showCryptoAddressSetting, setShowCryptoAddressSetting] = useState(false);
const [showCryptoWalletSettings, setShowCryptoWalletSettings] = useState(false);
const [showSubscriptionsSettings, setShowSubscriptionsSettings] = useState(false);
@@ -50,7 +48,6 @@ const SettingsModal = () => {
Number(showInterfaceSettings) +
Number(showMediaHostingSettings) +
Number(showAccountSettings) +
Number(showAvatarSettings) +
Number(showCryptoAddressSetting) +
Number(showCryptoWalletSettings) +
Number(showSubscriptionsSettings) +
@@ -62,7 +59,6 @@ const SettingsModal = () => {
if (showInterfaceSettings && 'interface-settings' !== excludeCategoryId) return 'interface-settings';
if (showMediaHostingSettings && 'media-hosting-settings' !== excludeCategoryId) return 'media-hosting-settings';
if (showAccountSettings && 'account-settings' !== excludeCategoryId) return 'account-settings';
if (showAvatarSettings && 'avatar-settings' !== excludeCategoryId) return 'avatar-settings';
if (showCryptoAddressSetting && 'crypto-address-settings' !== excludeCategoryId) return 'crypto-address-settings';
if (showCryptoWalletSettings && 'crypto-wallet-settings' !== excludeCategoryId) return 'crypto-wallet-settings';
if (showSubscriptionsSettings && 'subscriptions-settings' !== excludeCategoryId) return 'subscriptions-settings';
@@ -102,7 +98,6 @@ const SettingsModal = () => {
setShowInterfaceSettings(hash === 'interface-settings');
setShowMediaHostingSettings(hash === 'media-hosting-settings');
setShowAccountSettings(hash === 'account-settings');
setShowAvatarSettings(hash === 'avatar-settings');
setShowCryptoAddressSetting(hash === 'crypto-address-settings');
setShowCryptoWalletSettings(hash === 'crypto-wallet-settings');
setShowSubscriptionsSettings(hash === 'subscriptions-settings');
@@ -116,7 +111,6 @@ const SettingsModal = () => {
setShowInterfaceSettings(newExpandState);
setShowMediaHostingSettings(newExpandState);
setShowAccountSettings(newExpandState);
setShowAvatarSettings(newExpandState);
setShowCryptoAddressSetting(newExpandState);
setShowCryptoWalletSettings(newExpandState);
setShowSubscriptionsSettings(newExpandState);
@@ -158,13 +152,6 @@ const SettingsModal = () => {
</label>
</div>
{showAccountSettings && <AccountSettings />}
<div id='avatar-settings' className={`${styles.setting} ${styles.category}`}>
<label onClick={() => handleCategoryClick('avatar-settings', showAvatarSettings, setShowAvatarSettings)}>
<span className={showAvatarSettings ? styles.hideButton : styles.showButton} />
{t('avatar')}
</label>
</div>
{showAvatarSettings && <AvatarSettings />}
<div id='crypto-address-settings' className={`${styles.setting} ${styles.category}`}>
<label onClick={() => handleCategoryClick('crypto-address-settings', showCryptoAddressSetting, setShowCryptoAddressSetting)}>
<span className={showCryptoAddressSetting ? styles.hideButton : styles.showButton} />
-21
View File
@@ -1,21 +0,0 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AvatarVisibilityState {
hideAvatars: boolean;
setHideAvatars: (hide: boolean) => void;
}
const useAvatarVisibilityStore = create<AvatarVisibilityState>()(
persist(
(set) => ({
hideAvatars: false,
setHideAvatars: (hide) => set({ hideAvatars: hide }),
}),
{
name: 'avatar-visibility-storage',
},
),
);
export default useAvatarVisibilityStore;
-24
View File
@@ -648,20 +648,6 @@
text-decoration: var(--post-content-link-text-decoration-hover);
}
.authorAvatar {
width: 24px;
display: inline-block;
position: relative;
padding-right: 2px;
}
.authorAvatar img {
width: 24px;
max-height: 24px;
position: absolute;
bottom: -7px;
}
@media (max-width: 640px) {
.replyQuotePreview {
margin-right: 10px;
@@ -679,16 +665,6 @@
margin-top: 10px;
margin-left: 5px;
}
.authorAvatar {
width: 20px;
}
.authorAvatar img {
width: 20px;
max-height: 20px;
bottom: -3px;
}
}
@media (min-width: 640px) {