mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
@@ -2,6 +2,7 @@
|
||||
display: inline-block;
|
||||
padding: 3px 7px 3px 4px;
|
||||
filter: var(--filter80);
|
||||
color-scheme: var(--color-scheme, revert) !important;
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Comment, useAccount, useAccountComments, useAuthorAvatar, useComment, u
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import styles from '../../views/post/post.module.css';
|
||||
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-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 numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
|
||||
|
||||
const userIDBackgroundColor = hashStringToColor(shortAddress || accountShortAddress);
|
||||
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
|
||||
|
||||
return (
|
||||
<div className={styles.postInfo}>
|
||||
{!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='' />
|
||||
</span>
|
||||
)}
|
||||
(u/
|
||||
(ID: {''}
|
||||
<Tooltip
|
||||
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}
|
||||
</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()}>
|
||||
c/
|
||||
</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}
|
||||
</span>
|
||||
</>
|
||||
@@ -239,8 +248,7 @@ const PostMedia = ({ post }: PostProps) => {
|
||||
};
|
||||
|
||||
const PostMessage = ({ post }: PostProps) => {
|
||||
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {};
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
|
||||
// TODO: commentAuthor is not available outside of editedComment, update when available
|
||||
// const banned = !!post?.commentAuthor?.banExpiresAt;
|
||||
const { t } = useTranslation();
|
||||
@@ -249,7 +257,8 @@ const PostMessage = ({ post }: PostProps) => {
|
||||
const isInPostView = isPostPageView(location.pathname, params);
|
||||
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 isReply = parentCid;
|
||||
@@ -318,14 +327,10 @@ const PostMessage = ({ post }: PostProps) => {
|
||||
/>
|
||||
</span>
|
||||
)} */}
|
||||
{!isReply && content.length > 1000 && !isInPostView && (
|
||||
{content?.length > 1000 && !isInPostView && !showFullComment && (
|
||||
<span className={styles.abbr}>
|
||||
<br />
|
||||
<Trans
|
||||
i18nKey={'comment_too_long'}
|
||||
shouldUnescape={true}
|
||||
components={{ 1: <Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} /> }}
|
||||
/>
|
||||
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
|
||||
</span>
|
||||
)}
|
||||
{!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 { t } = useTranslation();
|
||||
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({
|
||||
placement: 'right-start',
|
||||
@@ -91,10 +104,10 @@ const ViewOnButton = ({ cid, isDescription, isInAllView, isInSubscriptionsView,
|
||||
{_.capitalize(t('view_on'))} »
|
||||
{isClientRedirectMenuOpen && (
|
||||
<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>
|
||||
</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>
|
||||
</a>
|
||||
</div>
|
||||
@@ -171,7 +184,7 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
|
||||
<FloatingFocusManager context={context} modal={false}>
|
||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
|
||||
{!isInPostPageView && !isDescription && !isRules && (
|
||||
{!(isInPostPageView && postCid === cid) && !isDescription && !isRules && (
|
||||
<div
|
||||
className={styles.postMenuItem}
|
||||
onClick={() => {
|
||||
|
||||
@@ -160,12 +160,11 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
setSubmitStore({
|
||||
signer: newSigner,
|
||||
author: {
|
||||
displayName,
|
||||
address: newSigner.address,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [anonMode, getNewSigner, setSubmitStore, displayName]);
|
||||
}, [anonMode, getNewSigner, setSubmitStore]);
|
||||
|
||||
const onPublishPost = async () => {
|
||||
if (!title && !content && !link) {
|
||||
@@ -212,7 +211,6 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
setPublishReplyOptions({
|
||||
signer: existingSigner,
|
||||
author: {
|
||||
displayName,
|
||||
address: existingSigner.address,
|
||||
},
|
||||
});
|
||||
@@ -221,13 +219,12 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
setPublishReplyOptions({
|
||||
signer: newSigner,
|
||||
author: {
|
||||
displayName,
|
||||
address: newSigner.address,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [address, getExistingSigner, getNewSigner, setPublishReplyOptions, displayName, anonMode]);
|
||||
}, [address, getExistingSigner, getNewSigner, setPublishReplyOptions, anonMode]);
|
||||
|
||||
const onPublishReply = () => {
|
||||
const currentContent = textRef.current?.value || '';
|
||||
@@ -246,6 +243,14 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
publishReply();
|
||||
};
|
||||
|
||||
const hasSetInitialDisplayName = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!hasSetInitialDisplayName.current && displayName) {
|
||||
setPublishReplyOptions({ displayName });
|
||||
hasSetInitialDisplayName.current = true;
|
||||
}
|
||||
}, [displayName, setPublishReplyOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof replyIndex === 'number') {
|
||||
resetPublishReplyOptions();
|
||||
@@ -276,7 +281,11 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
defaultValue={displayName || undefined}
|
||||
onChange={(e) => {
|
||||
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>}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-uti
|
||||
import useEditCommentPrivileges from '../../../hooks/use-author-privileges';
|
||||
import useHide from '../../../hooks/use-hide';
|
||||
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';
|
||||
|
||||
interface PostMenuMobileProps {
|
||||
@@ -57,7 +57,25 @@ const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void
|
||||
|
||||
const ViewOnButtons = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuMobileProps) => {
|
||||
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 (
|
||||
<div onClick={onClose}>
|
||||
<a href={`https://seedit.eth.limo/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } fr
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import styles from '../../views/post/post.module.css';
|
||||
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 { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import useAuthorAddressClick from '../../hooks/use-author-address-click';
|
||||
@@ -52,6 +53,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
||||
const handleUserAddressClick = useAuthorAddressClick();
|
||||
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
|
||||
|
||||
const userIDBackgroundColor = hashStringToColor(shortAddress || accountShortAddress);
|
||||
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.postInfo}>
|
||||
@@ -79,10 +83,15 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
||||
<img src={avatarImageUrl} alt='' />
|
||||
</span>
|
||||
)}
|
||||
(u/
|
||||
(ID: {''}
|
||||
<Tooltip
|
||||
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}
|
||||
</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()}>
|
||||
c/
|
||||
</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}
|
||||
</span>
|
||||
</>
|
||||
@@ -163,8 +172,7 @@ const ReplyBacklinks = ({ post }: PostProps) => {
|
||||
|
||||
const PostMessageMobile = ({ post }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {};
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
|
||||
// TODO: commentAuthor is not available outside of editedComment, update when available
|
||||
// const banned = !!post?.commentAuthor?.banExpiresAt;
|
||||
const [showOriginal, setShowOriginal] = useState(false);
|
||||
@@ -173,7 +181,8 @@ const PostMessageMobile = ({ post }: PostProps) => {
|
||||
const location = useLocation();
|
||||
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 isReply = parentCid;
|
||||
@@ -242,14 +251,10 @@ const PostMessageMobile = ({ post }: PostProps) => {
|
||||
/>
|
||||
</span>
|
||||
)} */}
|
||||
{!isReply && content.length > 1000 && !isInPostView && (
|
||||
{content?.length > 1000 && !isInPostView && !showFullComment && (
|
||||
<span className={styles.abbr}>
|
||||
<br />
|
||||
<Trans
|
||||
i18nKey={'comment_too_long'}
|
||||
shouldUnescape={true}
|
||||
components={{ 1: <Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} /> }}
|
||||
/>
|
||||
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
|
||||
</span>
|
||||
)}
|
||||
{!cid && state === 'pending' && stateString !== 'Failed' && (
|
||||
|
||||
@@ -17,14 +17,15 @@ import useAnonMode from '../../hooks/use-anon-mode';
|
||||
|
||||
interface ReplyModalProps {
|
||||
closeModal: () => void;
|
||||
showReplyModal: boolean;
|
||||
parentCid: string;
|
||||
postCid: string;
|
||||
scrollY: number;
|
||||
subplebbitAddress: string;
|
||||
}
|
||||
|
||||
const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps) => {
|
||||
const ReplyModal = ({ closeModal, showReplyModal, parentCid, postCid, scrollY, subplebbitAddress }: ReplyModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { subplebbitAddress } = useParams() as { subplebbitAddress: string };
|
||||
const { setPublishReplyOptions, publishReply } = usePublishReply({ cid: parentCid, subplebbitAddress });
|
||||
const account = useAccount();
|
||||
const { displayName } = account?.author || {};
|
||||
@@ -47,7 +48,6 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
setPublishReplyOptions({
|
||||
signer: existingSigner,
|
||||
author: {
|
||||
displayName,
|
||||
address: existingSigner.address,
|
||||
},
|
||||
});
|
||||
@@ -56,13 +56,12 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
setPublishReplyOptions({
|
||||
signer: newSigner,
|
||||
author: {
|
||||
displayName,
|
||||
address: newSigner.address,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [anonMode, address, getExistingSigner, getNewSigner, displayName, setPublishReplyOptions]);
|
||||
}, [anonMode, address, getExistingSigner, getNewSigner, setPublishReplyOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (anonMode) {
|
||||
@@ -88,10 +87,17 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const hasSetInitialDisplayName = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!hasSetInitialDisplayName.current && displayName) {
|
||||
setPublishReplyOptions({ displayName });
|
||||
hasSetInitialDisplayName.current = true;
|
||||
}
|
||||
}, [displayName, setPublishReplyOptions]);
|
||||
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// on mobile, the position is absolute instead of fixed, so we need to calculate the top position
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && isMobile) {
|
||||
const viewportHeight = window.innerHeight;
|
||||
@@ -122,14 +128,15 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
)
|
||||
: `The subplebbit might be offline and publishing might fail.`;
|
||||
|
||||
const setTextRef = (ref: HTMLTextAreaElement | null) => {
|
||||
if (ref) {
|
||||
textRef.current = ref;
|
||||
// if (!isMobile && !urlRef.current?.value) {
|
||||
// ref.focus();
|
||||
// }
|
||||
useEffect(() => {
|
||||
if (showReplyModal && !isMobile) {
|
||||
setTimeout(() => {
|
||||
if (textRef.current) {
|
||||
textRef.current.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
}, [showReplyModal, isMobile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current) {
|
||||
@@ -148,7 +155,6 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
};
|
||||
|
||||
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');
|
||||
if (textRef.current && textRef.current.value !== contentWithoutPrefix) {
|
||||
setPublishReplyOptions({ content: contentWithoutPrefix });
|
||||
@@ -196,7 +202,7 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
cols={48}
|
||||
rows={4}
|
||||
wrap='soft'
|
||||
ref={setTextRef}
|
||||
ref={textRef}
|
||||
spellCheck={false}
|
||||
defaultValue={contentPrefix + selectedText}
|
||||
onInput={handleContentInput}
|
||||
@@ -227,12 +233,15 @@ const ReplyModal = ({ closeModal, parentCid, postCid, scrollY }: ReplyModalProps
|
||||
</div>
|
||||
);
|
||||
|
||||
return isMobile ? (
|
||||
modalContent
|
||||
) : (
|
||||
<Draggable handle='.replyModalHandle' nodeRef={nodeRef}>
|
||||
{modalContent}
|
||||
</Draggable>
|
||||
return (
|
||||
showReplyModal &&
|
||||
(isMobile ? (
|
||||
modalContent
|
||||
) : (
|
||||
<Draggable handle='.replyModalHandle' nodeRef={nodeRef}>
|
||||
{modalContent}
|
||||
</Draggable>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -36,15 +36,21 @@ const useReplyStore = create<ReplyState>((set) => ({
|
||||
signer: {},
|
||||
spoiler: {},
|
||||
publishCommentOptions: {},
|
||||
|
||||
setReplyStore: (data: SetReplyStoreData) =>
|
||||
set((state) => {
|
||||
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 = {
|
||||
subplebbitAddress,
|
||||
parentCid,
|
||||
...(data.author ? { author: updatedAuthor } : {}),
|
||||
...(data.signer ? { signer: data.signer } : {}),
|
||||
...(updatedAuthor ? { author: updatedAuthor } : {}),
|
||||
...(signer ? { signer } : {}),
|
||||
content,
|
||||
link,
|
||||
spoiler,
|
||||
@@ -57,11 +63,12 @@ const useReplyStore = create<ReplyState>((set) => ({
|
||||
alert(error.message);
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
author: { ...state.author, [parentCid]: updatedAuthor },
|
||||
signer: { ...state.signer, [parentCid]: signer },
|
||||
content: { ...state.content, [parentCid]: content },
|
||||
link: { ...state.link, [parentCid]: link },
|
||||
signer: { ...state.signer, [parentCid]: signer },
|
||||
spoiler: { ...state.spoiler, [parentCid]: spoiler },
|
||||
publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: publishCommentOptions },
|
||||
};
|
||||
@@ -70,9 +77,9 @@ const useReplyStore = create<ReplyState>((set) => ({
|
||||
resetReplyStore: (parentCid) =>
|
||||
set((state) => ({
|
||||
author: { ...state.author, [parentCid]: undefined },
|
||||
signer: { ...state.signer, [parentCid]: undefined },
|
||||
content: { ...state.content, [parentCid]: undefined },
|
||||
link: { ...state.link, [parentCid]: undefined },
|
||||
signer: { ...state.signer, [parentCid]: undefined },
|
||||
spoiler: { ...state.spoiler, [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 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],
|
||||
displayName: state.author[parentCid]?.displayName,
|
||||
signer: state.signer[parentCid],
|
||||
content: state.content[parentCid],
|
||||
link: state.link[parentCid],
|
||||
@@ -99,6 +107,7 @@ const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress:
|
||||
setReplyStore({
|
||||
subplebbitAddress,
|
||||
parentCid,
|
||||
...(anonMode ? { displayName } : {}),
|
||||
...(anonMode ? { author } : {}),
|
||||
content,
|
||||
link,
|
||||
@@ -107,7 +116,7 @@ const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress:
|
||||
...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]);
|
||||
|
||||
@@ -6,6 +6,7 @@ const useReplyModal = () => {
|
||||
const [showReplyModal, setShowReplyModal] = useState(false);
|
||||
const [activeCid, setActiveCid] = useState<string | null>(null);
|
||||
const [threadCid, setThreadCid] = useState<string | null>(null);
|
||||
const [subplebbitAddress, setSubplebbitAddress] = useState<string | null>(null);
|
||||
const { resetSelectedText, setSelectedText } = useSelectedTextStore();
|
||||
|
||||
// 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`);
|
||||
};
|
||||
|
||||
const openReplyModal = (parentCid: string, postCid: string) => {
|
||||
const openReplyModal = (parentCid: string, postCid: string, subplebbitAddress: string) => {
|
||||
getSelectedText();
|
||||
|
||||
if (isMobile) {
|
||||
@@ -37,9 +38,10 @@ const useReplyModal = () => {
|
||||
setActiveCid(parentCid);
|
||||
setThreadCid(postCid);
|
||||
setShowReplyModal(true);
|
||||
setSubplebbitAddress(subplebbitAddress);
|
||||
};
|
||||
|
||||
return { activeCid, threadCid, closeModal, openReplyModal, scrollY, showReplyModal };
|
||||
return { activeCid, threadCid, closeModal, openReplyModal, scrollY, showReplyModal, subplebbitAddress };
|
||||
};
|
||||
|
||||
export default useReplyModal;
|
||||
|
||||
@@ -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
@@ -23,7 +23,7 @@
|
||||
--body-background-pattern-image: url("/public/assets/background-fade.png");
|
||||
--body-font-color: maroon;
|
||||
--body-font-family: arial, helvetica, sans-serif;
|
||||
--body-font-size: 13px;
|
||||
--body-font-size: 10pt;
|
||||
|
||||
/* catalog post */
|
||||
--catalog-post-menu-desktop-btn-color: navy;
|
||||
@@ -232,7 +232,7 @@
|
||||
--body-background-pattern-image: url("/public/assets/background-fade-blue.png");
|
||||
--body-font-color: #000;
|
||||
--body-font-family: arial, helvetica, sans-serif;
|
||||
--body-font-size: 13px;
|
||||
--body-font-size: 10pt;
|
||||
|
||||
/* catalog post */
|
||||
--catalog-post-menu-desktop-btn-color: navy;
|
||||
@@ -434,7 +434,7 @@
|
||||
--body-background-pattern-image: none;
|
||||
--body-font-color: maroon;
|
||||
--body-font-family: times new roman, serif;
|
||||
--body-font-size: 16px;
|
||||
--body-font-size: 12pt;
|
||||
|
||||
/* board banner */
|
||||
--banner-image-border: none;
|
||||
@@ -616,7 +616,7 @@
|
||||
--body-background-pattern-image: none;
|
||||
--body-font-color: #000;
|
||||
--body-font-family: times new roman, serif;
|
||||
--body-font-size: 16px;
|
||||
--body-font-size: 12pt;
|
||||
|
||||
/* catalog post */
|
||||
--catalog-post-menu-desktop-btn-color: navy;
|
||||
@@ -795,7 +795,7 @@
|
||||
--body-background-pattern-image: none;
|
||||
--body-font-color: #c5c8c6;
|
||||
--body-font-family: arial, helvetica, sans-serif;
|
||||
--body-font-size: 13px;
|
||||
--body-font-size: 10pt;
|
||||
|
||||
/* board banner */
|
||||
--banner-image-border: none;
|
||||
@@ -1008,7 +1008,7 @@
|
||||
--body-background-pattern-image: none;
|
||||
--body-font-color: #333;
|
||||
--body-font-family: arial, helvetica, sans-serif;
|
||||
--body-font-size: 13px;
|
||||
--body-font-size: 10pt;
|
||||
|
||||
/* board banner */
|
||||
--banner-image-border: none;
|
||||
|
||||
@@ -104,7 +104,7 @@ const Board = () => {
|
||||
const { createdAt, description, error, rules, shortAddress, state, suggested } = subplebbit || {};
|
||||
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 loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
|
||||
@@ -158,7 +158,16 @@ const Board = () => {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
{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 ? (
|
||||
<>
|
||||
{rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
||||
|
||||
@@ -18,9 +18,11 @@ interface PopularThreadProps {
|
||||
}
|
||||
|
||||
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
|
||||
const { cid, content, subplebbitAddress, title } = post || {};
|
||||
const { cid, subplebbitAddress, title } = post || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(post);
|
||||
|
||||
let content = post?.content?.replace(/\n\n/g, '\n');
|
||||
|
||||
return (
|
||||
<div className={styles.popularThread} key={cid}>
|
||||
<div className={styles.title}>{boardTitle || boardShortAddress}</div>
|
||||
|
||||
@@ -73,11 +73,14 @@
|
||||
}
|
||||
|
||||
.userAddress {
|
||||
padding: 0 5px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.postDesktop .userAddress:hover {
|
||||
color: var(--button-desktop-text-color-hover);
|
||||
.userAddress:hover {
|
||||
color: var(--post-quotelink-text-color-hover) !important;
|
||||
}
|
||||
|
||||
.postDesktop .subject {
|
||||
@@ -164,12 +167,13 @@
|
||||
font-size: var(--post-mobile-abbr-font-size);
|
||||
}
|
||||
|
||||
.postDesktop .abbr a {
|
||||
.postDesktop .abbr a, .postDesktop .abbr span {
|
||||
color: var(--post-link-text-color);
|
||||
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);
|
||||
text-decoration: var(--post-content-link-text-decoration-hover);
|
||||
}
|
||||
@@ -344,13 +348,14 @@
|
||||
font-size: var(--post-mobile-abbr-font-size);
|
||||
}
|
||||
|
||||
.postMobile .abbr a {
|
||||
.postMobile .abbr a, .postMobile .abbr span {
|
||||
color: var(--post-link-text-color);
|
||||
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);
|
||||
text-decoration: var(--post-content-link-text-decoration-hover);
|
||||
}
|
||||
|
||||
+12
-3
@@ -22,7 +22,7 @@ export interface PostProps {
|
||||
roles?: Role[];
|
||||
showAllReplies?: 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) => {
|
||||
@@ -62,7 +62,7 @@ const PostPage = () => {
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||
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 });
|
||||
|
||||
@@ -91,7 +91,16 @@ const PostPage = () => {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
{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 */}
|
||||
{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>}
|
||||
|
||||
Reference in New Issue
Block a user