mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'refactor/remove-nft-avatar' into development
This commit is contained in:
@@ -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:{' '}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user