Merge pull request #475 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-08-14 14:39:30 +02:00
committed by GitHub
15 changed files with 202 additions and 84 deletions
@@ -2,6 +2,7 @@
display: inline-block; display: inline-block;
padding: 3px 7px 3px 4px; padding: 3px 7px 3px 4px;
filter: var(--filter80); filter: var(--filter80);
color-scheme: var(--color-scheme, revert) !important;
} }
.modal { .modal {
+17 -12
View File
@@ -5,6 +5,7 @@ import { Comment, useAccount, useAccountComments, useAuthorAvatar, useComment, u
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css'; import styles from '../../views/post/post.module.css';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils'; import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isValidURL } from '../../lib/utils/url-utils'; import { isValidURL } from '../../lib/utils/url-utils';
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
@@ -71,6 +72,9 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
const handleUserAddressClick = useAuthorAddressClick(); const handleUserAddressClick = useAuthorAddressClick();
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length; const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
const userIDBackgroundColor = hashStringToColor(shortAddress || accountShortAddress);
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
return ( return (
<div className={styles.postInfo}> <div className={styles.postInfo}>
{!isHidden && <EditMenu isAccountCommentAuthor={isAccountCommentAuthor} isAccountMod={isAccountMod} isCommentAuthorMod={isCommentAuthorMod} post={post} />} {!isHidden && <EditMenu isAccountCommentAuthor={isAccountCommentAuthor} isAccountMod={isAccountMod} isCommentAuthorMod={isCommentAuthorMod} post={post} />}
@@ -106,10 +110,15 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
<img src={avatarImageUrl} alt='' /> <img src={avatarImageUrl} alt='' />
</span> </span>
)} )}
(u/ (ID: {''}
<Tooltip <Tooltip
children={ children={
<span title={t('highlight_posts')} className={styles.userAddress} onClick={() => handleUserAddressClick(shortAddress || accountShortAddress, postCid)}> <span
title={t('highlight_posts')}
className={styles.userAddress}
onClick={() => handleUserAddressClick(shortAddress || accountShortAddress, postCid)}
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
>
{shortAddress || accountShortAddress} {shortAddress || accountShortAddress}
</span> </span>
} }
@@ -138,7 +147,7 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}> <Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/ c/
</Link> </Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid)}> <span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}>
{shortCid} {shortCid}
</span> </span>
</> </>
@@ -239,8 +248,7 @@ const PostMedia = ({ post }: PostProps) => {
}; };
const PostMessage = ({ post }: PostProps) => { const PostMessage = ({ post }: PostProps) => {
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {}; const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api
// TODO: commentAuthor is not available outside of editedComment, update when available // TODO: commentAuthor is not available outside of editedComment, update when available
// const banned = !!post?.commentAuthor?.banExpiresAt; // const banned = !!post?.commentAuthor?.banExpiresAt;
const { t } = useTranslation(); const { t } = useTranslation();
@@ -249,7 +257,8 @@ const PostMessage = ({ post }: PostProps) => {
const isInPostView = isPostPageView(location.pathname, params); const isInPostView = isPostPageView(location.pathname, params);
const [showOriginal, setShowOriginal] = useState(false); const [showOriginal, setShowOriginal] = useState(false);
const displayContent = content && !isInPostView && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content; const [showFullComment, setShowFullComment] = useState(false);
const displayContent = content && !isInPostView && content.length > 1000 && !showFullComment ? content.slice(0, 1000) : content;
const quotelinkReply = useComment({ commentCid: parentCid }); const quotelinkReply = useComment({ commentCid: parentCid });
const isReply = parentCid; const isReply = parentCid;
@@ -318,14 +327,10 @@ const PostMessage = ({ post }: PostProps) => {
/> />
</span> </span>
)} */} )} */}
{!isReply && content.length > 1000 && !isInPostView && ( {content?.length > 1000 && !isInPostView && !showFullComment && (
<span className={styles.abbr}> <span className={styles.abbr}>
<br /> <br />
<Trans <Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
i18nKey={'comment_too_long'}
shouldUnescape={true}
components={{ 1: <Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} /> }}
/>
</span> </span>
)} )}
{!cid && state === 'pending' && stateString !== 'Failed' && ( {!cid && state === 'pending' && stateString !== 'Failed' && (
@@ -73,7 +73,20 @@ const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void
const ViewOnButton = ({ cid, isDescription, isInAllView, isInSubscriptionsView, isRules, subplebbitAddress, onClose }: PostMenuDesktopProps) => { const ViewOnButton = ({ cid, isDescription, isInAllView, isInSubscriptionsView, isRules, subplebbitAddress, onClose }: PostMenuDesktopProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false); const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
const viewOnOtherClientLink = `p/${isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`;
const getViewOnOtherClientLink = () => {
if (isDescription || isRules) {
if (isInAllView || isInSubscriptionsView) {
return `${isInAllView ? 'all' : isInSubscriptionsView && 'subscriptions'}`;
} else {
return `${subplebbitAddress}/${isDescription ? 'description' : isRules && 'rules'}`;
}
} else {
return `${subplebbitAddress}/c/${cid}`;
}
};
const viewOnOtherClientLink = getViewOnOtherClientLink();
const { refs, floatingStyles } = useFloating({ const { refs, floatingStyles } = useFloating({
placement: 'right-start', placement: 'right-start',
@@ -91,10 +104,10 @@ const ViewOnButton = ({ cid, isDescription, isInAllView, isInSubscriptionsView,
{_.capitalize(t('view_on'))} » {_.capitalize(t('view_on'))} »
{isClientRedirectMenuOpen && ( {isClientRedirectMenuOpen && (
<div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}> <div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}>
<a href={`https://seedit.eth.limo/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'> <a href={`https://seedit.eth.limo/#/p/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Seedit</div> <div className={styles.postMenuItem}>Seedit</div>
</a> </a>
<a href={`https://plebones.eth.limo/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'> <a href={`https://plebones.eth.limo/#/p/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Plebones</div> <div className={styles.postMenuItem}>Plebones</div>
</a> </a>
</div> </div>
@@ -171,7 +184,7 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
<FloatingFocusManager context={context} modal={false}> <FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}> <div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />} {cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{!isInPostPageView && !isDescription && !isRules && ( {!(isInPostPageView && postCid === cid) && !isDescription && !isRules && (
<div <div
className={styles.postMenuItem} className={styles.postMenuItem}
onClick={() => { onClick={() => {
+15 -6
View File
@@ -160,12 +160,11 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
setSubmitStore({ setSubmitStore({
signer: newSigner, signer: newSigner,
author: { author: {
displayName,
address: newSigner.address, address: newSigner.address,
}, },
}); });
} }
}, [anonMode, getNewSigner, setSubmitStore, displayName]); }, [anonMode, getNewSigner, setSubmitStore]);
const onPublishPost = async () => { const onPublishPost = async () => {
if (!title && !content && !link) { if (!title && !content && !link) {
@@ -212,7 +211,6 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
setPublishReplyOptions({ setPublishReplyOptions({
signer: existingSigner, signer: existingSigner,
author: { author: {
displayName,
address: existingSigner.address, address: existingSigner.address,
}, },
}); });
@@ -221,13 +219,12 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
setPublishReplyOptions({ setPublishReplyOptions({
signer: newSigner, signer: newSigner,
author: { author: {
displayName,
address: newSigner.address, address: newSigner.address,
}, },
}); });
} }
} }
}, [address, getExistingSigner, getNewSigner, setPublishReplyOptions, displayName, anonMode]); }, [address, getExistingSigner, getNewSigner, setPublishReplyOptions, anonMode]);
const onPublishReply = () => { const onPublishReply = () => {
const currentContent = textRef.current?.value || ''; const currentContent = textRef.current?.value || '';
@@ -246,6 +243,14 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
publishReply(); publishReply();
}; };
const hasSetInitialDisplayName = useRef(false);
useEffect(() => {
if (!hasSetInitialDisplayName.current && displayName) {
setPublishReplyOptions({ displayName });
hasSetInitialDisplayName.current = true;
}
}, [displayName, setPublishReplyOptions]);
useEffect(() => { useEffect(() => {
if (typeof replyIndex === 'number') { if (typeof replyIndex === 'number') {
resetPublishReplyOptions(); resetPublishReplyOptions();
@@ -276,7 +281,11 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
defaultValue={displayName || undefined} defaultValue={displayName || undefined}
onChange={(e) => { onChange={(e) => {
setAccount({ ...account, author: { ...account?.author, displayName: e.target.value } }); setAccount({ ...account, author: { ...account?.author, displayName: e.target.value } });
setSubmitStore({ displayName: e.target.value }); if (isInPostView) {
setPublishReplyOptions({ displayName: e.target.value });
} else {
setSubmitStore({ displayName: e.target.value });
}
}} }}
/> />
{isInPostView && <button onClick={onPublishReply}>{t('post')}</button>} {isInPostView && <button onClick={onPublishReply}>{t('post')}</button>}
@@ -9,7 +9,7 @@ import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-uti
import useEditCommentPrivileges from '../../../hooks/use-author-privileges'; import useEditCommentPrivileges from '../../../hooks/use-author-privileges';
import useHide from '../../../hooks/use-hide'; import useHide from '../../../hooks/use-hide';
import EditMenu from '../../edit-menu/edit-menu'; import EditMenu from '../../edit-menu/edit-menu';
import { isBoardView, isPostPageView } from '../../../lib/utils/view-utils'; import { isAllView, isBoardView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
interface PostMenuMobileProps { interface PostMenuMobileProps {
@@ -57,7 +57,25 @@ const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void
const ViewOnButtons = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuMobileProps) => { const ViewOnButtons = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuMobileProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`; const location = useLocation();
const params = useParams();
const isInAllView = isAllView(location.pathname, params);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const getViewOnOtherClientLink = () => {
if (isDescription || isRules) {
if (isInAllView || isInSubscriptionsView) {
return `${isInAllView ? 'all' : isInSubscriptionsView && 'subscriptions'}`;
} else {
return `${subplebbitAddress}/${isDescription ? 'description' : isRules && 'rules'}`;
}
} else {
return `${subplebbitAddress}/c/${cid}`;
}
};
const viewOnOtherClientLink = getViewOnOtherClientLink();
return ( return (
<div onClick={onClose}> <div onClick={onClose}>
<a href={`https://seedit.eth.limo/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'> <a href={`https://seedit.eth.limo/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
+17 -12
View File
@@ -5,6 +5,7 @@ import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } fr
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css'; import styles from '../../views/post/post.module.css';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils'; import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useAuthorAddressClick from '../../hooks/use-author-address-click'; import useAuthorAddressClick from '../../hooks/use-author-address-click';
@@ -52,6 +53,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
const handleUserAddressClick = useAuthorAddressClick(); const handleUserAddressClick = useAuthorAddressClick();
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length; const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
const userIDBackgroundColor = hashStringToColor(shortAddress || accountShortAddress);
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
return ( return (
<> <>
<div className={styles.postInfo}> <div className={styles.postInfo}>
@@ -79,10 +83,15 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
<img src={avatarImageUrl} alt='' /> <img src={avatarImageUrl} alt='' />
</span> </span>
)} )}
(u/ (ID: {''}
<Tooltip <Tooltip
children={ children={
<span title={t('highlight_posts')} className={styles.userAddress} onClick={() => handleUserAddressClick(shortAddress || accountShortAddress, postCid)}> <span
title={t('highlight_posts')}
className={styles.userAddress}
onClick={() => handleUserAddressClick(shortAddress || accountShortAddress, postCid)}
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
>
{shortAddress || accountShortAddress} {shortAddress || accountShortAddress}
</span> </span>
} }
@@ -127,7 +136,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}> <Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/ c/
</Link> </Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid)}> <span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}>
{shortCid} {shortCid}
</span> </span>
</> </>
@@ -163,8 +172,7 @@ const ReplyBacklinks = ({ post }: PostProps) => {
const PostMessageMobile = ({ post }: PostProps) => { const PostMessageMobile = ({ post }: PostProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {}; const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api
// TODO: commentAuthor is not available outside of editedComment, update when available // TODO: commentAuthor is not available outside of editedComment, update when available
// const banned = !!post?.commentAuthor?.banExpiresAt; // const banned = !!post?.commentAuthor?.banExpiresAt;
const [showOriginal, setShowOriginal] = useState(false); const [showOriginal, setShowOriginal] = useState(false);
@@ -173,7 +181,8 @@ const PostMessageMobile = ({ post }: PostProps) => {
const location = useLocation(); const location = useLocation();
const isInPostView = isPostPageView(location.pathname, params); const isInPostView = isPostPageView(location.pathname, params);
const displayContent = content && !isInPostView && content.length > 1000 ? content?.slice(0, 1000) : content; const [showFullComment, setShowFullComment] = useState(false);
const displayContent = content && !isInPostView && content.length > 1000 && !showFullComment ? content.slice(0, 1000) : content;
const quotelinkReply = useComment({ commentCid: parentCid }); const quotelinkReply = useComment({ commentCid: parentCid });
const isReply = parentCid; const isReply = parentCid;
@@ -242,14 +251,10 @@ const PostMessageMobile = ({ post }: PostProps) => {
/> />
</span> </span>
)} */} )} */}
{!isReply && content.length > 1000 && !isInPostView && ( {content?.length > 1000 && !isInPostView && !showFullComment && (
<span className={styles.abbr}> <span className={styles.abbr}>
<br /> <br />
<Trans <Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
i18nKey={'comment_too_long'}
shouldUnescape={true}
components={{ 1: <Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} /> }}
/>
</span> </span>
)} )}
{!cid && state === 'pending' && stateString !== 'Failed' && ( {!cid && state === 'pending' && stateString !== 'Failed' && (
+30 -21
View File
@@ -17,14 +17,15 @@ import useAnonMode from '../../hooks/use-anon-mode';
interface ReplyModalProps { interface ReplyModalProps {
closeModal: () => void; closeModal: () => void;
showReplyModal: boolean;
parentCid: string; parentCid: string;
postCid: string; postCid: string;
scrollY: number; scrollY: number;
subplebbitAddress: string;
} }
const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps) => { const ReplyModal = ({ closeModal, showReplyModal, parentCid, postCid, scrollY, subplebbitAddress }: ReplyModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { subplebbitAddress } = useParams() as { subplebbitAddress: string };
const { setPublishReplyOptions, publishReply } = usePublishReply({ cid: parentCid, subplebbitAddress }); const { setPublishReplyOptions, publishReply } = usePublishReply({ cid: parentCid, subplebbitAddress });
const account = useAccount(); const account = useAccount();
const { displayName } = account?.author || {}; const { displayName } = account?.author || {};
@@ -47,7 +48,6 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
setPublishReplyOptions({ setPublishReplyOptions({
signer: existingSigner, signer: existingSigner,
author: { author: {
displayName,
address: existingSigner.address, address: existingSigner.address,
}, },
}); });
@@ -56,13 +56,12 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
setPublishReplyOptions({ setPublishReplyOptions({
signer: newSigner, signer: newSigner,
author: { author: {
displayName,
address: newSigner.address, address: newSigner.address,
}, },
}); });
} }
} }
}, [anonMode, address, getExistingSigner, getNewSigner, displayName, setPublishReplyOptions]); }, [anonMode, address, getExistingSigner, getNewSigner, setPublishReplyOptions]);
useEffect(() => { useEffect(() => {
if (anonMode) { if (anonMode) {
@@ -88,10 +87,17 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
closeModal(); closeModal();
}; };
const hasSetInitialDisplayName = useRef(false);
useEffect(() => {
if (!hasSetInitialDisplayName.current && displayName) {
setPublishReplyOptions({ displayName });
hasSetInitialDisplayName.current = true;
}
}, [displayName, setPublishReplyOptions]);
const nodeRef = useRef<HTMLDivElement>(null); const nodeRef = useRef<HTMLDivElement>(null);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
// on mobile, the position is absolute instead of fixed, so we need to calculate the top position
useEffect(() => { useEffect(() => {
if (nodeRef.current && isMobile) { if (nodeRef.current && isMobile) {
const viewportHeight = window.innerHeight; const viewportHeight = window.innerHeight;
@@ -122,14 +128,15 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
) )
: `The subplebbit might be offline and publishing might fail.`; : `The subplebbit might be offline and publishing might fail.`;
const setTextRef = (ref: HTMLTextAreaElement | null) => { useEffect(() => {
if (ref) { if (showReplyModal && !isMobile) {
textRef.current = ref; setTimeout(() => {
// if (!isMobile && !urlRef.current?.value) { if (textRef.current) {
// ref.focus(); textRef.current.focus();
// } }
}, 0);
} }
}; }, [showReplyModal, isMobile]);
useEffect(() => { useEffect(() => {
if (textRef.current) { if (textRef.current) {
@@ -148,7 +155,6 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
}; };
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
// 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'); const contentWithoutPrefix = e.target.value.slice(contentPrefix.length).replace(/\n/g, '\n\n');
if (textRef.current && textRef.current.value !== contentWithoutPrefix) { if (textRef.current && textRef.current.value !== contentWithoutPrefix) {
setPublishReplyOptions({ content: contentWithoutPrefix }); setPublishReplyOptions({ content: contentWithoutPrefix });
@@ -196,7 +202,7 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
cols={48} cols={48}
rows={4} rows={4}
wrap='soft' wrap='soft'
ref={setTextRef} ref={textRef}
spellCheck={false} spellCheck={false}
defaultValue={contentPrefix + selectedText} defaultValue={contentPrefix + selectedText}
onInput={handleContentInput} onInput={handleContentInput}
@@ -227,12 +233,15 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
</div> </div>
); );
return isMobile ? ( return (
modalContent showReplyModal &&
) : ( (isMobile ? (
<Draggable handle='.replyModalHandle' nodeRef={nodeRef}> modalContent
{modalContent} ) : (
</Draggable> <Draggable handle='.replyModalHandle' nodeRef={nodeRef}>
{modalContent}
</Draggable>
))
); );
}; };
+16 -7
View File
@@ -36,15 +36,21 @@ const useReplyStore = create<ReplyState>((set) => ({
signer: {}, signer: {},
spoiler: {}, spoiler: {},
publishCommentOptions: {}, publishCommentOptions: {},
setReplyStore: (data: SetReplyStoreData) => setReplyStore: (data: SetReplyStoreData) =>
set((state) => { set((state) => {
const { subplebbitAddress, parentCid, author, displayName, content, link, signer, spoiler } = data; const { subplebbitAddress, parentCid, author, displayName, content, link, signer, spoiler } = data;
const updatedAuthor = displayName ? { ...author, displayName } : author;
const updatedAuthor = {
...(state.author[parentCid] || author),
...(displayName ? { displayName } : {}),
};
const publishCommentOptions = { const publishCommentOptions = {
subplebbitAddress, subplebbitAddress,
parentCid, parentCid,
...(data.author ? { author: updatedAuthor } : {}), ...(updatedAuthor ? { author: updatedAuthor } : {}),
...(data.signer ? { signer: data.signer } : {}), ...(signer ? { signer } : {}),
content, content,
link, link,
spoiler, spoiler,
@@ -57,11 +63,12 @@ const useReplyStore = create<ReplyState>((set) => ({
alert(error.message); alert(error.message);
}, },
}; };
return { return {
author: { ...state.author, [parentCid]: updatedAuthor }, author: { ...state.author, [parentCid]: updatedAuthor },
signer: { ...state.signer, [parentCid]: signer },
content: { ...state.content, [parentCid]: content }, content: { ...state.content, [parentCid]: content },
link: { ...state.link, [parentCid]: link }, link: { ...state.link, [parentCid]: link },
signer: { ...state.signer, [parentCid]: signer },
spoiler: { ...state.spoiler, [parentCid]: spoiler }, spoiler: { ...state.spoiler, [parentCid]: spoiler },
publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: publishCommentOptions }, publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: publishCommentOptions },
}; };
@@ -70,9 +77,9 @@ const useReplyStore = create<ReplyState>((set) => ({
resetReplyStore: (parentCid) => resetReplyStore: (parentCid) =>
set((state) => ({ set((state) => ({
author: { ...state.author, [parentCid]: undefined }, author: { ...state.author, [parentCid]: undefined },
signer: { ...state.signer, [parentCid]: undefined },
content: { ...state.content, [parentCid]: undefined }, content: { ...state.content, [parentCid]: undefined },
link: { ...state.link, [parentCid]: undefined }, link: { ...state.link, [parentCid]: undefined },
signer: { ...state.signer, [parentCid]: undefined },
spoiler: { ...state.spoiler, [parentCid]: undefined }, spoiler: { ...state.spoiler, [parentCid]: undefined },
publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: undefined }, publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: undefined },
})), })),
@@ -80,8 +87,9 @@ const useReplyStore = create<ReplyState>((set) => ({
const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress: string }) => { const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress: string }) => {
const parentCid = cid; const parentCid = cid;
const { author, signer, content, link, spoiler, publishCommentOptions } = useReplyStore((state) => ({ const { author, displayName, signer, content, link, spoiler, publishCommentOptions } = useReplyStore((state) => ({
author: state.author[parentCid], author: state.author[parentCid],
displayName: state.author[parentCid]?.displayName,
signer: state.signer[parentCid], signer: state.signer[parentCid],
content: state.content[parentCid], content: state.content[parentCid],
link: state.link[parentCid], link: state.link[parentCid],
@@ -99,6 +107,7 @@ const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress:
setReplyStore({ setReplyStore({
subplebbitAddress, subplebbitAddress,
parentCid, parentCid,
...(anonMode ? { displayName } : {}),
...(anonMode ? { author } : {}), ...(anonMode ? { author } : {}),
content, content,
link, link,
@@ -107,7 +116,7 @@ const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress:
...options, ...options,
}); });
}, },
[subplebbitAddress, parentCid, author, signer, content, link, spoiler, setReplyStore, anonMode], [subplebbitAddress, parentCid, author, displayName, signer, content, link, spoiler, setReplyStore, anonMode],
); );
const resetPublishReplyOptions = useCallback(() => resetReplyStore(parentCid), [parentCid, resetReplyStore]); const resetPublishReplyOptions = useCallback(() => resetReplyStore(parentCid), [parentCid, resetReplyStore]);
+4 -2
View File
@@ -6,6 +6,7 @@ const useReplyModal = () => {
const [showReplyModal, setShowReplyModal] = useState(false); const [showReplyModal, setShowReplyModal] = useState(false);
const [activeCid, setActiveCid] = useState<string | null>(null); const [activeCid, setActiveCid] = useState<string | null>(null);
const [threadCid, setThreadCid] = useState<string | null>(null); const [threadCid, setThreadCid] = useState<string | null>(null);
const [subplebbitAddress, setSubplebbitAddress] = useState<string | null>(null);
const { resetSelectedText, setSelectedText } = useSelectedTextStore(); const { resetSelectedText, setSelectedText } = useSelectedTextStore();
// on mobile, the css position is absolute instead of fixed, so we need to calculate the top position // on mobile, the css position is absolute instead of fixed, so we need to calculate the top position
@@ -23,7 +24,7 @@ const useReplyModal = () => {
text && setSelectedText(`>${text}\n`); text && setSelectedText(`>${text}\n`);
}; };
const openReplyModal = (parentCid: string, postCid: string) => { const openReplyModal = (parentCid: string, postCid: string, subplebbitAddress: string) => {
getSelectedText(); getSelectedText();
if (isMobile) { if (isMobile) {
@@ -37,9 +38,10 @@ const useReplyModal = () => {
setActiveCid(parentCid); setActiveCid(parentCid);
setThreadCid(postCid); setThreadCid(postCid);
setShowReplyModal(true); setShowReplyModal(true);
setSubplebbitAddress(subplebbitAddress);
}; };
return { activeCid, threadCid, closeModal, openReplyModal, scrollY, showReplyModal }; return { activeCid, threadCid, closeModal, openReplyModal, scrollY, showReplyModal, subplebbitAddress };
}; };
export default useReplyModal; export default useReplyModal;
+22
View File
@@ -0,0 +1,22 @@
export function hashStringToColor(str: string): string {
if (!str) {
return '';
}
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
const r = (hash >> 24) & 0xff;
const g = (hash >> 16) & 0xff;
const b = (hash >> 8) & 0xff;
return `rgb(${r}, ${g}, ${b})`;
}
export function getTextColorForBackground(rgb: string): string {
const [r, g, b] = rgb.match(/\d+/g)?.map(Number) || [0, 0, 0];
const brightness = r * 0.299 + g * 0.587 + b * 0.114;
return brightness > 125 ? 'black' : 'white';
}
+6 -6
View File
@@ -23,7 +23,7 @@
--body-background-pattern-image: url("/public/assets/background-fade.png"); --body-background-pattern-image: url("/public/assets/background-fade.png");
--body-font-color: maroon; --body-font-color: maroon;
--body-font-family: arial, helvetica, sans-serif; --body-font-family: arial, helvetica, sans-serif;
--body-font-size: 13px; --body-font-size: 10pt;
/* catalog post */ /* catalog post */
--catalog-post-menu-desktop-btn-color: navy; --catalog-post-menu-desktop-btn-color: navy;
@@ -232,7 +232,7 @@
--body-background-pattern-image: url("/public/assets/background-fade-blue.png"); --body-background-pattern-image: url("/public/assets/background-fade-blue.png");
--body-font-color: #000; --body-font-color: #000;
--body-font-family: arial, helvetica, sans-serif; --body-font-family: arial, helvetica, sans-serif;
--body-font-size: 13px; --body-font-size: 10pt;
/* catalog post */ /* catalog post */
--catalog-post-menu-desktop-btn-color: navy; --catalog-post-menu-desktop-btn-color: navy;
@@ -434,7 +434,7 @@
--body-background-pattern-image: none; --body-background-pattern-image: none;
--body-font-color: maroon; --body-font-color: maroon;
--body-font-family: times new roman, serif; --body-font-family: times new roman, serif;
--body-font-size: 16px; --body-font-size: 12pt;
/* board banner */ /* board banner */
--banner-image-border: none; --banner-image-border: none;
@@ -616,7 +616,7 @@
--body-background-pattern-image: none; --body-background-pattern-image: none;
--body-font-color: #000; --body-font-color: #000;
--body-font-family: times new roman, serif; --body-font-family: times new roman, serif;
--body-font-size: 16px; --body-font-size: 12pt;
/* catalog post */ /* catalog post */
--catalog-post-menu-desktop-btn-color: navy; --catalog-post-menu-desktop-btn-color: navy;
@@ -795,7 +795,7 @@
--body-background-pattern-image: none; --body-background-pattern-image: none;
--body-font-color: #c5c8c6; --body-font-color: #c5c8c6;
--body-font-family: arial, helvetica, sans-serif; --body-font-family: arial, helvetica, sans-serif;
--body-font-size: 13px; --body-font-size: 10pt;
/* board banner */ /* board banner */
--banner-image-border: none; --banner-image-border: none;
@@ -1008,7 +1008,7 @@
--body-background-pattern-image: none; --body-background-pattern-image: none;
--body-font-color: #333; --body-font-color: #333;
--body-font-family: arial, helvetica, sans-serif; --body-font-family: arial, helvetica, sans-serif;
--body-font-size: 13px; --body-font-size: 10pt;
/* board banner */ /* board banner */
--banner-image-border: none; --banner-image-border: none;
+11 -2
View File
@@ -104,7 +104,7 @@ const Board = () => {
const { createdAt, description, error, rules, shortAddress, state, suggested } = subplebbit || {}; const { createdAt, description, error, rules, shortAddress, state, suggested } = subplebbit || {};
const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : subplebbit?.title; const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : subplebbit?.title;
const { activeCid, threadCid, closeModal, openReplyModal, showReplyModal, scrollY } = useReplyModal(); const { activeCid, threadCid, closeModal, openReplyModal, showReplyModal, scrollY, subplebbitAddress: postSubplebbitAddress } = useReplyModal();
const { blocked, unblock } = useBlock({ address: subplebbitAddress }); const { blocked, unblock } = useBlock({ address: subplebbitAddress });
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
@@ -158,7 +158,16 @@ const Board = () => {
return ( return (
<div className={styles.content}> <div className={styles.content}>
{location.pathname.endsWith('/settings') && <SettingsModal />} {location.pathname.endsWith('/settings') && <SettingsModal />}
{showReplyModal && activeCid && threadCid && <ReplyModal closeModal={closeModal} parentCid={activeCid} postCid={threadCid} scrollY={scrollY} />} {activeCid && threadCid && postSubplebbitAddress && (
<ReplyModal
closeModal={closeModal}
parentCid={activeCid}
postCid={threadCid}
scrollY={scrollY}
showReplyModal={showReplyModal}
subplebbitAddress={postSubplebbitAddress}
/>
)}
{feed.length !== 0 ? ( {feed.length !== 0 ? (
<> <>
{rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />} {rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
@@ -18,9 +18,11 @@ interface PopularThreadProps {
} }
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => { const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
const { cid, content, subplebbitAddress, title } = post || {}; const { cid, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post); const commentMediaInfo = getCommentMediaInfo(post);
let content = post?.content?.replace(/\n\n/g, '\n');
return ( return (
<div className={styles.popularThread} key={cid}> <div className={styles.popularThread} key={cid}>
<div className={styles.title}>{boardTitle || boardShortAddress}</div> <div className={styles.title}>{boardTitle || boardShortAddress}</div>
+11 -6
View File
@@ -73,11 +73,14 @@
} }
.userAddress { .userAddress {
padding: 0 5px;
border-radius: 6px;
font-size: 0.8em;
cursor: pointer; cursor: pointer;
} }
.postDesktop .userAddress:hover { .userAddress:hover {
color: var(--button-desktop-text-color-hover); color: var(--post-quotelink-text-color-hover) !important;
} }
.postDesktop .subject { .postDesktop .subject {
@@ -164,12 +167,13 @@
font-size: var(--post-mobile-abbr-font-size); font-size: var(--post-mobile-abbr-font-size);
} }
.postDesktop .abbr a { .postDesktop .abbr a, .postDesktop .abbr span {
color: var(--post-link-text-color); color: var(--post-link-text-color);
text-decoration: var(--post-content-link-text-decoration); text-decoration: var(--post-content-link-text-decoration);
} }
.postDesktop .abbr a:hover { .postDesktop .abbr a:hover, .postDesktop .abbr span:hover {
cursor: pointer;
color: var(--post-link-text-color-hover); color: var(--post-link-text-color-hover);
text-decoration: var(--post-content-link-text-decoration-hover); text-decoration: var(--post-content-link-text-decoration-hover);
} }
@@ -344,13 +348,14 @@
font-size: var(--post-mobile-abbr-font-size); font-size: var(--post-mobile-abbr-font-size);
} }
.postMobile .abbr a { .postMobile .abbr a, .postMobile .abbr span {
color: var(--post-link-text-color); color: var(--post-link-text-color);
text-decoration: var(--post-content-link-text-decoration); text-decoration: var(--post-content-link-text-decoration);
} }
.postMobile .abbr a:hover { .postMobile .abbr a:hover, .postMobile .abbr span:hover {
cursor: pointer;
color: var(--post-link-text-color-hover); color: var(--post-link-text-color-hover);
text-decoration: var(--post-content-link-text-decoration-hover); text-decoration: var(--post-content-link-text-decoration-hover);
} }
+12 -3
View File
@@ -22,7 +22,7 @@ export interface PostProps {
roles?: Role[]; roles?: Role[];
showAllReplies?: boolean; showAllReplies?: boolean;
showReplies?: boolean; showReplies?: boolean;
openReplyModal?: (parentCid: string, postCid: string) => void; openReplyModal?: (parentCid: string, postCid: string, subplebbitAddress: string) => void;
} }
export const Post = ({ post, showAllReplies = false, showReplies = true, openReplyModal }: PostProps) => { export const Post = ({ post, showAllReplies = false, showReplies = true, openReplyModal }: PostProps) => {
@@ -62,7 +62,7 @@ const PostPage = () => {
const subplebbit = useSubplebbit({ subplebbitAddress }); const subplebbit = useSubplebbit({ subplebbitAddress });
const { createdAt, description, rules, shortAddress, suggested, title } = subplebbit; const { createdAt, description, rules, shortAddress, suggested, title } = subplebbit;
const { activeCid, threadCid, closeModal, openReplyModal, showReplyModal, scrollY } = useReplyModal(); const { activeCid, threadCid, closeModal, openReplyModal, showReplyModal, scrollY, subplebbitAddress: postSubplebbitAddress } = useReplyModal();
const comment = useComment({ commentCid }); const comment = useComment({ commentCid });
@@ -91,7 +91,16 @@ const PostPage = () => {
return ( return (
<div className={styles.content}> <div className={styles.content}>
{isInSettigsView && <SettingsModal />} {isInSettigsView && <SettingsModal />}
{showReplyModal && activeCid && threadCid && <ReplyModal closeModal={closeModal} parentCid={activeCid} postCid={threadCid} scrollY={scrollY} />} {activeCid && threadCid && postSubplebbitAddress && (
<ReplyModal
closeModal={closeModal}
parentCid={activeCid}
postCid={threadCid}
scrollY={scrollY}
showReplyModal={showReplyModal}
subplebbitAddress={postSubplebbitAddress}
/>
)}
{/* TODO: remove this replyCount error once api supports scrolling replies pages */} {/* TODO: remove this replyCount error once api supports scrolling replies pages */}
{replyCount > 60 && <span className={styles.error}>Error: this thread has too many replies, some of them cannot be displayed right now.</span>} {replyCount > 60 && <span className={styles.error}>Error: this thread has too many replies, some of them cannot be displayed right now.</span>}
{error && <span className={styles.error}>Error: {error.message}</span>} {error && <span className={styles.error}>Error: {error.message}</span>}