mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: add challenge modal
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
.container {
|
||||
position: fixed;
|
||||
top: calc(50% - 150px);
|
||||
left: calc(50% - 150px);
|
||||
display: block;
|
||||
padding: 2px;
|
||||
font-size: 10pt;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
z-index: 1000;
|
||||
background-color: var(--challenge-modal-background-color);
|
||||
border: var(--challenge-modal-border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--challenge-modal-title-font-size);
|
||||
text-align: center;
|
||||
margin-bottom: 1px;
|
||||
padding: 0;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
cursor: move;
|
||||
font-weight: var(--challenge-modal-title-font-weight);
|
||||
background-color: var(--challenge-modal-title-background-color);
|
||||
color: var(--challenge-modal-title-text-color);
|
||||
border: var(--challenge-modal-title-border);
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
all: unset;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
margin-bottom: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: none;
|
||||
image-rendering: pixelated;
|
||||
background-image: var(--close-button-background-image);
|
||||
}
|
||||
|
||||
.container input, .container textarea {
|
||||
border: 1px solid #aaa;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 10pt;
|
||||
outline: medium none;
|
||||
width: 298px;
|
||||
padding: 2px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.content textarea {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.challengeAnswer {
|
||||
font-size: 11px !important;
|
||||
padding: 0px 2px;
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
.challengeMediaWrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.challengeFooter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.challengeFooter button {
|
||||
margin: 5px 5px 0 0;
|
||||
cursor: pointer;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { useState } from 'react';
|
||||
import { Challenge as ChallengeType, useComment } from '@plebbit/plebbit-react-hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useChallenges from '../../hooks/use-challenges';
|
||||
import styles from './challenge-modal.module.css';
|
||||
import { getPublicationType } from '../../lib/utils/challenge-utils';
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
interface ChallengeProps {
|
||||
challenge: ChallengeType;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const challenges = challenge?.[0]?.challenges;
|
||||
const publication = challenge?.[1];
|
||||
|
||||
const publicationType = getPublicationType(publication);
|
||||
const { author, content, link, title } = publication || {};
|
||||
const { displayName } = author || {};
|
||||
|
||||
const [currentChallengeIndex, setCurrentChallengeIndex] = useState(0);
|
||||
const [answers, setAnswers] = useState<string[]>([]);
|
||||
|
||||
const isTextChallenge = challenges[currentChallengeIndex].type === 'text/plain';
|
||||
const isImageChallenge = challenges[currentChallengeIndex].type === 'image/png';
|
||||
|
||||
const onAnswersChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswers((prevAnswers) => {
|
||||
const updatedAnswers = [...prevAnswers];
|
||||
updatedAnswers[currentChallengeIndex] = e.target.value;
|
||||
return updatedAnswers;
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
publication.publishChallengeAnswers(answers);
|
||||
setAnswers([]);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const onEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
if (challenges[currentChallengeIndex + 1]) {
|
||||
setCurrentChallengeIndex((prev) => prev + 1);
|
||||
} else {
|
||||
onSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable handle='.challengeHandle'>
|
||||
<div className={styles.container}>
|
||||
<div className={`challengeHandle ${styles.title}`}>
|
||||
Challenge for {publicationType}
|
||||
<button className={styles.closeIcon} onClick={closeModal} title='close' />
|
||||
</div>
|
||||
<div className={styles.publication}>
|
||||
<div className={styles.name}>
|
||||
<input type='text' value={displayName || 'Anonymous'} disabled />
|
||||
</div>
|
||||
{title && (
|
||||
<div className={styles.subject}>
|
||||
<input type='text' value={title} disabled />
|
||||
</div>
|
||||
)}
|
||||
{content && (
|
||||
<div className={styles.content}>
|
||||
<textarea value={content} disabled cols={48} rows={4} wrap='soft' />
|
||||
</div>
|
||||
)}
|
||||
{link && (
|
||||
<div className={styles.link}>
|
||||
<input type='text' value={link} disabled />
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.challengeContainer}>
|
||||
<input
|
||||
className={styles.challengeAnswer}
|
||||
type='text'
|
||||
autoComplete='off'
|
||||
autoCorrect='off'
|
||||
spellCheck='false'
|
||||
placeholder='TYPE THE ANSWER HERE AND PRESS ENTER'
|
||||
onKeyDown={onEnterKey}
|
||||
onChange={onAnswersChange}
|
||||
value={answers[currentChallengeIndex] || ''}
|
||||
autoFocus
|
||||
/>
|
||||
<div className={styles.challengeMediaWrapper}>
|
||||
{isTextChallenge && <div className={styles.challengeMedia}>{challenges[currentChallengeIndex]?.challenge}</div>}
|
||||
{isImageChallenge && (
|
||||
<img alt={t('loading')} className={styles.challengeMedia} src={`data:image/png;base64,${challenges[currentChallengeIndex]?.challenge}`} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.challengeFooter}>
|
||||
<div className={styles.counter}>{t('challenge_counter', { index: currentChallengeIndex + 1, total: challenges?.length })}</div>
|
||||
<span className={styles.buttons}>
|
||||
{!challenges[currentChallengeIndex + 1] && <button onClick={onSubmit}>{t('submit')}</button>}
|
||||
{challenges.length > 1 && (
|
||||
<button disabled={!challenges[currentChallengeIndex - 1]} onClick={() => setCurrentChallengeIndex((prev) => prev - 1)}>
|
||||
{t('previous')}
|
||||
</button>
|
||||
)}
|
||||
{challenges[currentChallengeIndex + 1] && <button onClick={() => setCurrentChallengeIndex((prev) => prev + 1)}>{t('next')}</button>}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
const ChallengeModal = () => {
|
||||
const { challenges, removeChallenge } = useChallenges();
|
||||
const isOpen = !!challenges.length;
|
||||
const closeModal = () => removeChallenge();
|
||||
|
||||
return isOpen && <Challenge challenge={challenges[0]} closeModal={closeModal} />;
|
||||
};
|
||||
|
||||
export default ChallengeModal;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './challenge-modal';
|
||||
@@ -1,11 +1,55 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { useAccount, useComment } from '@plebbit/plebbit-react-hooks';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { PublishCommentOptions, setAccount, useAccount, useAccountComment, useComment, usePublishComment } from '@plebbit/plebbit-react-hooks';
|
||||
import { create } from 'zustand';
|
||||
import styles from './post-form.module.css';
|
||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||
import { getLinkMediaInfo } from '../../lib/utils/media-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isDescriptionView, isPostPageView, isRulesView } from '../../lib/utils/view-utils';
|
||||
import styles from './post-form.module.css';
|
||||
import challengesStore from '../../hooks/use-challenges';
|
||||
|
||||
type SubmitState = {
|
||||
subplebbitAddress: string | undefined;
|
||||
title: string | undefined;
|
||||
content: string | undefined;
|
||||
link: string | undefined;
|
||||
publishCommentOptions: PublishCommentOptions;
|
||||
setSubmitStore: (data: Partial<SubmitState>) => void;
|
||||
resetSubmitStore: () => void;
|
||||
};
|
||||
|
||||
const { addChallenge } = challengesStore.getState();
|
||||
|
||||
const useSubmitStore = create<SubmitState>((set) => ({
|
||||
subplebbitAddress: undefined,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
link: undefined,
|
||||
publishCommentOptions: {},
|
||||
setSubmitStore: ({ subplebbitAddress, title, content, link }) =>
|
||||
set((state) => {
|
||||
const nextState = { ...state };
|
||||
if (subplebbitAddress !== undefined) nextState.subplebbitAddress = subplebbitAddress;
|
||||
if (title !== undefined) nextState.title = title || undefined;
|
||||
if (content !== undefined) nextState.content = content || undefined;
|
||||
if (link !== undefined) nextState.link = link || undefined;
|
||||
|
||||
nextState.publishCommentOptions = {
|
||||
...nextState,
|
||||
onChallenge: (...args: any) => addChallenge(args),
|
||||
onChallengeVerification: alertChallengeVerificationFailed,
|
||||
onError: (error: Error) => {
|
||||
console.error(error);
|
||||
let errorMessage = error.message;
|
||||
alert(errorMessage);
|
||||
},
|
||||
};
|
||||
return nextState;
|
||||
}),
|
||||
resetSubmitStore: () => set({ subplebbitAddress: undefined, title: undefined, content: undefined, link: undefined, publishCommentOptions: {} }),
|
||||
}));
|
||||
|
||||
const LinkTypePreviewer = ({ link }: { link: string }) => {
|
||||
const mediaInfo = getLinkMediaInfo(link);
|
||||
@@ -14,9 +58,44 @@ const LinkTypePreviewer = ({ link }: { link: string }) => {
|
||||
|
||||
const PostFormTable = () => {
|
||||
const account = useAccount();
|
||||
const { displayName } = account || {};
|
||||
const { displayName } = account?.author || {};
|
||||
|
||||
const [link, setLink] = useState('');
|
||||
const [url, setUrl] = useState('');
|
||||
|
||||
const { title, content, link, publishCommentOptions, setSubmitStore, resetSubmitStore } = useSubmitStore();
|
||||
|
||||
const { index, publishComment } = usePublishComment(publishCommentOptions);
|
||||
|
||||
const onPublish = () => {
|
||||
if (!title && !content && !link) {
|
||||
alert(`Cannot post empty comment`);
|
||||
return;
|
||||
}
|
||||
if (link && !isValidURL(link)) {
|
||||
alert(`Invalid link`);
|
||||
return;
|
||||
}
|
||||
|
||||
publishComment();
|
||||
};
|
||||
|
||||
const params = useParams();
|
||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
|
||||
useEffect(() => {
|
||||
if (subplebbitAddress) {
|
||||
setSubmitStore({ subplebbitAddress });
|
||||
}
|
||||
}, [subplebbitAddress, setSubmitStore]);
|
||||
|
||||
// redirect to pending page when pending comment is created
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (typeof index === 'number') {
|
||||
resetSubmitStore();
|
||||
navigate(`/profile/${index}`);
|
||||
}
|
||||
}, [index, resetSubmitStore, navigate]);
|
||||
|
||||
return (
|
||||
<table className={styles.postFormTable}>
|
||||
@@ -24,30 +103,44 @@ const PostFormTable = () => {
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
<input type='text' placeholder={!displayName ? 'Anonymous' : undefined} defaultValue={displayName || undefined} />
|
||||
<input
|
||||
type='text'
|
||||
placeholder={!displayName ? 'Anonymous' : undefined}
|
||||
defaultValue={displayName || undefined}
|
||||
onChange={(e) => setAccount({ ...account, author: { ...account?.author, displayName: e.target.value } })}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subject</td>
|
||||
<td>
|
||||
<input type='text' />
|
||||
<button>Post</button>
|
||||
<input type='text' onChange={(e) => setSubmitStore({ title: e.target.value })} />
|
||||
<button onClick={onPublish}>Post</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Comment</td>
|
||||
<td>
|
||||
<textarea cols={48} rows={4} wrap='soft' />
|
||||
<textarea cols={48} rows={4} wrap='soft' onChange={(e) => setSubmitStore({ content: e.target.value })} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Link</td>
|
||||
<td>
|
||||
<input type='text' onChange={(e) => setLink(e.target.value)} />
|
||||
<input
|
||||
type='text'
|
||||
autoCorrect='off'
|
||||
autoComplete='off'
|
||||
spellCheck='false'
|
||||
onChange={(e) => {
|
||||
setUrl(e.target.value);
|
||||
setSubmitStore({ link: e.target.value });
|
||||
}}
|
||||
/>
|
||||
<span className={styles.linkType}>
|
||||
{link && (
|
||||
{url && (
|
||||
<>
|
||||
(<LinkTypePreviewer link={link} />)
|
||||
(<LinkTypePreviewer link={url} />)
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
@@ -66,9 +159,7 @@ const PostForm = () => {
|
||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
||||
const isInRulesView = isRulesView(location.pathname, params);
|
||||
|
||||
const { subplebbitAddress, commentCid } = params || {};
|
||||
|
||||
const comment = useComment({ commentCid });
|
||||
const comment = useComment({ commentCid: useParams().commentCid });
|
||||
const { deleted, locked, removed } = comment || {};
|
||||
const isThreadClosed = deleted || locked || removed || isInDescriptionView || isInRulesView;
|
||||
|
||||
|
||||
@@ -303,6 +303,11 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.postMobile .postNumLink a {
|
||||
color: unset;
|
||||
text-decoration: unset;
|
||||
}
|
||||
|
||||
.postMobile .postMessage {
|
||||
padding: 10px;
|
||||
font-size: var(--post-mobile-content-font-size);
|
||||
@@ -416,6 +421,11 @@
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.pendingCid {
|
||||
color: red !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.postDesktop {
|
||||
display: none;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Role, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getFormattedDate } from '../../lib/utils/time-utils';
|
||||
import { isPostPageView } from '../../lib/utils/view-utils';
|
||||
import { isPostPageView, isPendingPostView } from '../../lib/utils/view-utils';
|
||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||
import useReplies from '../../hooks/use-replies';
|
||||
import useWindowWidth from '../../hooks/use-window-width';
|
||||
@@ -24,13 +24,16 @@ interface PostProps {
|
||||
|
||||
const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, replyCount, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
|
||||
const { address, displayName, shortAddress } = author || {};
|
||||
const authorRole = roles?.[address]?.role;
|
||||
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
|
||||
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const isInPostPage = isPostPageView(location.pathname, params);
|
||||
const isInPendingPostPage = isPendingPostView(location.pathname, params);
|
||||
|
||||
const displayTitle = title && title.length > 75 ? title?.slice(0, 75) + '...' : title;
|
||||
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content;
|
||||
@@ -120,12 +123,21 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
<span className={styles.postNum}>
|
||||
{!(isDescription || isRules) && (
|
||||
<span className={styles.postNumLink}>
|
||||
<Link to={`/p/${subplebbitAddress}/${cid}`} className={styles.linkToPost} title={t('link_to_post')}>
|
||||
<Link
|
||||
to={`/p/${subplebbitAddress}/${cid}`}
|
||||
className={styles.linkToPost}
|
||||
title={t('link_to_post')}
|
||||
onClick={(e) => isInPendingPostPage && e.preventDefault()}
|
||||
>
|
||||
c/
|
||||
</Link>
|
||||
{isInPendingPostPage ? (
|
||||
<span className={styles.pendingCid}>{state === 'failed' ? 'Failed' : 'Pending'}</span>
|
||||
) : (
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')}>
|
||||
{shortCid}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{pinned && (
|
||||
@@ -305,14 +317,18 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
|
||||
const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
|
||||
const { address, displayName, shortAddress } = author || {};
|
||||
const authorRole = roles?.[address]?.role;
|
||||
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const isInPostPage = isPostPageView(location.pathname, params);
|
||||
const isInPendingPostPage = isPendingPostView(location.pathname, params);
|
||||
|
||||
const linkCount = useCountLinksInReplies(post);
|
||||
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
|
||||
const displayTitle = title && title.length > 30 ? title?.slice(0, 30) + '(...)' : title;
|
||||
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) : content;
|
||||
|
||||
@@ -360,10 +376,23 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
<span className={styles.dateTimePostNum}>
|
||||
{getFormattedDate(timestamp)}{' '}
|
||||
{!(isDescription || isRules) && (
|
||||
<>
|
||||
<span className={styles.linkToPost}>c/</span>
|
||||
<span className={styles.replyToPost}>{shortCid}</span>
|
||||
</>
|
||||
<span className={styles.postNumLink}>
|
||||
<Link
|
||||
to={`/p/${subplebbitAddress}/${cid}`}
|
||||
className={styles.linkToPost}
|
||||
title={t('link_to_post')}
|
||||
onClick={(e) => isInPendingPostPage && e.preventDefault()}
|
||||
>
|
||||
c/
|
||||
</Link>
|
||||
{isInPendingPostPage ? (
|
||||
<span className={styles.pendingCid}>{state === 'failed' ? 'Failed' : 'Pending'}</span>
|
||||
) : (
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')}>
|
||||
{shortCid}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { create } from 'zustand';
|
||||
import { Challenge } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
interface State {
|
||||
challenges: Challenge[];
|
||||
addChallenge: (challenge: Challenge) => void;
|
||||
removeChallenge: () => void;
|
||||
}
|
||||
|
||||
const useChallengesStore = create<State>((set) => ({
|
||||
challenges: [],
|
||||
addChallenge: (challenge: Challenge) => {
|
||||
set((state) => ({ challenges: [...state.challenges, challenge] }));
|
||||
},
|
||||
removeChallenge: () => {
|
||||
set((state) => {
|
||||
const challenges = [...state.challenges];
|
||||
challenges.shift();
|
||||
return { challenges };
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
export default useChallengesStore;
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ChallengeVerification } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
export const alertChallengeVerificationFailed = (challengeVerification: ChallengeVerification, publication: any) => {
|
||||
if (challengeVerification?.challengeSuccess === false) {
|
||||
console.warn(challengeVerification, publication);
|
||||
alert(`p/${publication?.subplebbitAddress} challenge error: ${[...(challengeVerification?.challengeErrors || []), challengeVerification?.reason].join(' ')}`);
|
||||
} else {
|
||||
console.log(challengeVerification, publication);
|
||||
}
|
||||
};
|
||||
|
||||
export const getPublicationType = (publication: any) => {
|
||||
if (!publication) {
|
||||
return;
|
||||
}
|
||||
if (typeof publication.vote === 'number') {
|
||||
return 'vote';
|
||||
}
|
||||
if (publication.parentCid) {
|
||||
return 'reply';
|
||||
}
|
||||
if (publication.commentCid) {
|
||||
return 'edit';
|
||||
}
|
||||
return 'post';
|
||||
};
|
||||
|
||||
export const getVotePreview = (publication: any) => {
|
||||
if (typeof publication?.vote !== 'number') {
|
||||
return '';
|
||||
}
|
||||
let votePreview = '';
|
||||
if (publication.vote === -1) {
|
||||
votePreview += ' -1';
|
||||
} else {
|
||||
votePreview += ` +${publication.vote}`;
|
||||
}
|
||||
return votePreview;
|
||||
};
|
||||
|
||||
export const getPublicationPreview = (publication: any) => {
|
||||
if (!publication) {
|
||||
return '';
|
||||
}
|
||||
let publicationPreview = '';
|
||||
if (publication.title) {
|
||||
publicationPreview += publication.title;
|
||||
}
|
||||
if (publication.content) {
|
||||
if (publicationPreview) {
|
||||
publicationPreview += ': ';
|
||||
}
|
||||
publicationPreview += publication.content;
|
||||
}
|
||||
if (!publicationPreview && publication.link) {
|
||||
publicationPreview += publication.link;
|
||||
}
|
||||
|
||||
if (publicationPreview.length > 50) {
|
||||
publicationPreview = publicationPreview.substring(0, 50) + '...';
|
||||
}
|
||||
return publicationPreview;
|
||||
};
|
||||
Vendored
+2
@@ -6,3 +6,5 @@ declare module '*.module.css' {
|
||||
declare module 'ext-name';
|
||||
|
||||
declare module 'lodash';
|
||||
|
||||
declare module 'react-draggable';
|
||||
|
||||
@@ -30,9 +30,19 @@
|
||||
--catalog-post-menu-btn-opacity: 0.5;
|
||||
--catalog-post-menu-btn-hover-opacity: 1;
|
||||
|
||||
/* challenge modal */
|
||||
--challenge-modal-background-color: #f0e0d6;
|
||||
--challenge-modal-border: 1px solid #d9bfb7;
|
||||
--challenge-modal-title-background-color: #ea8;
|
||||
--challenge-modal-title-text-color: #800;
|
||||
--challenge-modal-title-border: 1px solid #800;
|
||||
--challenge-modal-title-font-size: 10pt;
|
||||
--challenge-modal-title-font-weight: 700;
|
||||
|
||||
/* desktop buttons */
|
||||
--button-desktop-text-color: #00e;
|
||||
--button-desktop-text-color-hover: red;
|
||||
--close-button-background-image: url("/public/assets/buttons/cross-red.png");
|
||||
|
||||
/* mobile buttons */
|
||||
--button-font-size-mobile: 10pt;
|
||||
|
||||
Reference in New Issue
Block a user