mirror of
https://github.com/plebbit/seedit.git
synced 2026-02-15 08:21:19 -05:00
@@ -6,7 +6,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.26.1",
|
||||
"@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#80f19b488013cf386f4926fefb5790c79a3b9b96",
|
||||
"@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#6b614e0bf6c304de7b184564198a0ce7b98bca1d",
|
||||
"@testing-library/jest-dom": "5.14.1",
|
||||
"@testing-library/react": "13.0.0",
|
||||
"@testing-library/user-event": "13.2.1",
|
||||
|
||||
@@ -109,8 +109,8 @@ function App() {
|
||||
<Route path='/communities/admin' element={<Subplebbits />} />
|
||||
<Route path='/communities/owner' element={<Subplebbits />} />
|
||||
<Route path='/communities/vote' element={<Subplebbits />} />
|
||||
<Route path='/communities/vote/passed' element={<Subplebbits />} />
|
||||
<Route path='/communities/vote/rejected' element={<Subplebbits />} />
|
||||
<Route path='/communities/vote/passing' element={<Subplebbits />} />
|
||||
<Route path='/communities/vote/rejecting' element={<Subplebbits />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -6,12 +6,6 @@ import breaks from 'remark-breaks';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
const Markdown = ({ content }: { content: string }) => {
|
||||
// replace \n with \n\n when it follows a sentence starting with '>'
|
||||
let preserveNewlineAfterQuote = content?.replace(/(^|\n)(>[^>].*?)(\n)/gm, '$1\n$2\n\n');
|
||||
|
||||
// replace \n\n with \n for list items separated by two newlines
|
||||
let adjustListNewlines = preserveNewlineAfterQuote?.replace(/(\n\n)([*-]|[0-9]+\.) (.+?)(?=\n\n([*-]|[0-9]+\.) )/gms, '\n$2 $3');
|
||||
|
||||
const customSchema = useMemo(
|
||||
() => ({
|
||||
...defaultSchema,
|
||||
@@ -41,7 +35,7 @@ const Markdown = ({ content }: { content: string }) => {
|
||||
return (
|
||||
<span className={styles.markdown}>
|
||||
<ReactMarkdown
|
||||
children={adjustListNewlines}
|
||||
children={content}
|
||||
remarkPlugins={[excludeBlockquote, [remarkGfm, { singleTilde: false }], breaks]}
|
||||
rehypePlugins={[[rehypeSanitize, customSchema]]}
|
||||
components={{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
line-height: 14px;
|
||||
padding: 0 4px;
|
||||
margin: 0 2px 2px 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.black {
|
||||
|
||||
@@ -4,35 +4,41 @@ import styles from './label.module.css';
|
||||
export const DeletedLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('deleted').toUpperCase()}</span>;
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('deleted')}</span>;
|
||||
};
|
||||
|
||||
export const FailedLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('failed').toUpperCase()}</span>;
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('failed')}</span>;
|
||||
};
|
||||
|
||||
export const PendingLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.yellow}`}>{t('pending').toUpperCase()}</span>;
|
||||
return <span className={`${styles.stamp} ${styles.yellow}`}>{t('pending')}</span>;
|
||||
};
|
||||
|
||||
export const RemovedLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('removed').toUpperCase()}</span>;
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('removed')}</span>;
|
||||
};
|
||||
|
||||
export const SpoilerLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.black}`}>{t('spoiler').toUpperCase()}</span>;
|
||||
return <span className={`${styles.stamp} ${styles.black}`}>{t('spoiler')}</span>;
|
||||
};
|
||||
|
||||
export const RoleLabel = ({ role }: { role: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.green}`}>{t(role).toUpperCase()}</span>;
|
||||
return <span className={`${styles.stamp} ${styles.green}`}>{t(role)}</span>;
|
||||
};
|
||||
|
||||
export const OfflineLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span className={`${styles.stamp} ${styles.red}`}>{t('offline')}</span>;
|
||||
};
|
||||
|
||||
@@ -92,7 +92,70 @@ export const getDefaultChallengeDescription = (challengeType: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getDefaultOptionInputs = (challengeType: string) => {
|
||||
export const getDefaultChallengeOptions = (challengeType: string) => {
|
||||
switch (challengeType) {
|
||||
case 'text-math':
|
||||
return {
|
||||
difficulty: '1',
|
||||
};
|
||||
case 'captcha-canvas-v3':
|
||||
return {
|
||||
characters: '',
|
||||
height: '',
|
||||
width: '',
|
||||
color: '',
|
||||
};
|
||||
case 'fail':
|
||||
return {
|
||||
error: "You're not allowed to publish.",
|
||||
};
|
||||
case 'blacklist':
|
||||
return {
|
||||
blacklist: '',
|
||||
error: "You're blacklisted.",
|
||||
};
|
||||
case 'question':
|
||||
return {
|
||||
question: '',
|
||||
answer: '',
|
||||
};
|
||||
case 'evm-contract-call':
|
||||
return {
|
||||
chainTicker: 'eth',
|
||||
address: '',
|
||||
abi: '',
|
||||
condition: '',
|
||||
error: "Contract call response doesn't pass condition.",
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export type OptionInput = {
|
||||
option: string;
|
||||
label: string;
|
||||
default?: string;
|
||||
description: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
export type Exclude = {
|
||||
postScore?: number;
|
||||
postReply?: number;
|
||||
firstCommentTimestamp?: number;
|
||||
challenges?: number[];
|
||||
post?: boolean;
|
||||
reply?: boolean;
|
||||
vote?: boolean;
|
||||
role?: string[];
|
||||
address?: string[];
|
||||
rateLimit?: number;
|
||||
rateLimitChallengeSuccess?: boolean;
|
||||
};
|
||||
|
||||
export const getDefaultChallengeSettings = (challengeType: string) => {
|
||||
switch (challengeType) {
|
||||
case 'text-math':
|
||||
return [
|
||||
|
||||
@@ -148,10 +148,10 @@ export const isSubplebbitsVoteView = (pathname: string): boolean => {
|
||||
return pathname === '/communities/vote';
|
||||
};
|
||||
|
||||
export const isSubplebbitsVotePassedView = (pathname: string): boolean => {
|
||||
return pathname === '/communities/vote/passed';
|
||||
export const isSubplebbitsVotePassingView = (pathname: string): boolean => {
|
||||
return pathname === '/communities/vote/passing';
|
||||
};
|
||||
|
||||
export const isSubplebbitsVoteRejectedView = (pathname: string): boolean => {
|
||||
return pathname === '/communities/vote/rejected';
|
||||
export const isSubplebbitsVoteRejectingView = (pathname: string): boolean => {
|
||||
return pathname === '/communities/vote/rejecting';
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
.fullSettings textarea {
|
||||
.JSONSettings textarea {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
@@ -114,13 +114,13 @@
|
||||
}
|
||||
|
||||
.challengeOption {
|
||||
font-size: 15px;
|
||||
margin-top: 10px !important;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.challengeDescription {
|
||||
margin: 15px 0 15px 0;
|
||||
font-size: 15px;
|
||||
margin: 20px 0 0 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.challengeOptionDescription {
|
||||
@@ -137,6 +137,7 @@
|
||||
width: auto !important;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.challengesArray {
|
||||
|
||||
@@ -6,7 +6,14 @@ import { useTranslation } from 'react-i18next';
|
||||
import { create } from 'zustand';
|
||||
import styles from './subplebbit-settings.module.css';
|
||||
import { isValidURL } from '../../../lib/utils/url-utils';
|
||||
import { getDefaultChallengeDescription, getDefaultExclude, getDefaultOptionInputs } from '../../../lib/utils/challenge-utils';
|
||||
import {
|
||||
OptionInput,
|
||||
Exclude,
|
||||
getDefaultChallengeDescription,
|
||||
getDefaultExclude,
|
||||
getDefaultChallengeOptions,
|
||||
getDefaultChallengeSettings,
|
||||
} from '../../../lib/utils/challenge-utils';
|
||||
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
||||
import Sidebar from '../../../components/sidebar';
|
||||
|
||||
@@ -298,109 +305,110 @@ interface ChallengeSettingsProps {
|
||||
showSettings: boolean;
|
||||
}
|
||||
|
||||
type OptionInput = {
|
||||
option: string;
|
||||
value?: string;
|
||||
label: string;
|
||||
default?: string;
|
||||
description: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
const rolesToExclude = ['moderator', 'admin', 'owner'];
|
||||
const actionsToExclude: Array<'post' | 'reply' | 'vote'> = ['post', 'reply', 'vote'];
|
||||
|
||||
const ChallengeSettings = ({ challenge, index, setSubmitStore, settings, showSettings }: ChallengeSettingsProps) => {
|
||||
const { exclude, name, optionInputs } = challenge || {};
|
||||
const { exclude, name, options } = challenge || {};
|
||||
const challengeSettings: OptionInput[] = getDefaultChallengeSettings(name);
|
||||
|
||||
const handleOptionChange = (optionName: string, newValue: string) => {
|
||||
const updatedOptionInputs = optionInputs.map((input: any) => (input.option === optionName ? { ...input, value: newValue } : input));
|
||||
|
||||
const updatedChallenges = settings.challenges.map((ch: any, idx: number) => (idx === index ? { ...ch, optionInputs: updatedOptionInputs } : ch));
|
||||
|
||||
const updatedOptions = { ...options, [optionName]: newValue };
|
||||
const updatedChallenges = settings.challenges.map((ch: any, idx: number) => (idx === index ? { ...ch, options: updatedOptions } : ch));
|
||||
setSubmitStore({ settings: { ...settings, challenges: updatedChallenges } });
|
||||
};
|
||||
|
||||
const handleExcludeChange = (type: 'role' | 'post' | 'reply' | 'vote', value: string | boolean) => {
|
||||
const updatedExclude = { ...exclude[0] }; // Clone the first exclude object
|
||||
|
||||
if (type === 'role') {
|
||||
if (typeof value === 'string') {
|
||||
const roleIndex = updatedExclude.role.indexOf(value);
|
||||
if (roleIndex > -1) {
|
||||
updatedExclude.role.splice(roleIndex, 1); // Remove role
|
||||
} else {
|
||||
updatedExclude.role.push(value); // Add role
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle post, reply, vote
|
||||
updatedExclude[type] = value;
|
||||
}
|
||||
|
||||
const updatedChallenges = [...settings.challenges];
|
||||
updatedChallenges[index] = { ...updatedChallenges[index], exclude: [updatedExclude] };
|
||||
const handleExcludeChange = (type: keyof Exclude, value: string | boolean | number | string[] | number[]) => {
|
||||
const updatedExclude = { ...exclude[0], [type]: value };
|
||||
const updatedChallenges = settings.challenges.map((ch: any, idx: number) => (idx === index ? { ...ch, exclude: [updatedExclude] } : ch));
|
||||
setSubmitStore({ settings: { ...settings, challenges: updatedChallenges } });
|
||||
};
|
||||
|
||||
const handleExcludeAddress = (value: string) => {
|
||||
// Split the input by commas, trim spaces, and filter out empty strings
|
||||
const addresses = value
|
||||
.split(',')
|
||||
.map((addr) => addr.trim())
|
||||
.filter((addr) => addr !== '');
|
||||
handleExcludeChange('address', addresses);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={showSettings ? styles.visible : styles.hidden}>
|
||||
<div className={styles.challengeDescription}>{getDefaultChallengeDescription(name)}</div>
|
||||
{challenge?.optionInputs?.map((inputOption: OptionInput) => (
|
||||
<div key={inputOption.option} className={styles.challengeOption}>
|
||||
{inputOption.label}
|
||||
<div className={styles.challengeOptionDescription}>{inputOption.description}</div>
|
||||
{challengeSettings.map((setting) => (
|
||||
<div key={setting?.option} className={styles.challengeOption}>
|
||||
<div className={styles.challengeOptionLabel}>{setting?.label}</div>
|
||||
<div className={styles.challengeOptionDescription}>{setting?.description}</div>
|
||||
<input
|
||||
type='text'
|
||||
value={inputOption.value || inputOption.default || ''}
|
||||
placeholder={inputOption.placeholder || ''}
|
||||
onChange={(e) => handleOptionChange(inputOption.option, e.target.value)}
|
||||
required={inputOption.required || false}
|
||||
value={options && (options[setting?.option] || setting?.default || '')}
|
||||
placeholder={setting?.placeholder || ''}
|
||||
onChange={(e) => handleOptionChange(setting?.option, e.target.value)}
|
||||
required={setting?.required || false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className={styles.challengeDescription}>Exclude from challenge</div>
|
||||
<div className={styles.challengeOption}>
|
||||
Users
|
||||
<div className={styles.challengeOptionDescription}>Exclude specific users by their addresses, separated by a comma</div>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='address1.eth, address2.eth, address3.eth'
|
||||
value={exclude?.address?.join(', ')}
|
||||
onChange={(e) => handleExcludeAddress(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.challengeOption}>
|
||||
Users with Karma
|
||||
<div className={styles.challengeOptionDescription}>Minimum post karma required:</div>
|
||||
<input type='number' placeholder='3' value={exclude?.postScore || undefined} onChange={(e) => handleExcludeChange('postScore', e.target.value)} />
|
||||
<div className={styles.challengeOptionDescription}>Minimum comment karma required:</div>
|
||||
<input type='number' placeholder='3' value={exclude?.postReply || undefined} onChange={(e) => handleExcludeChange('postReply', e.target.value)} />
|
||||
</div>
|
||||
<div className={styles.challengeOption}>
|
||||
Users by account age
|
||||
<div className={styles.challengeOptionDescription}>Minimum account age in Unix Timestamp (seconds):</div>
|
||||
<input
|
||||
type='number'
|
||||
placeholder='604800'
|
||||
value={exclude?.firstCommentTimestamp || undefined}
|
||||
onChange={(e) => handleExcludeChange('firstCommentTimestamp', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.challengeOption}>
|
||||
Moderators
|
||||
<div className={styles.challengeOptionDescription}>Exclude a specific moderator role</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[0]?.role.includes('moderator')} onChange={() => handleExcludeChange('role', 'moderator')} />
|
||||
exclude moderators
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[0]?.role.includes('admin')} onChange={() => handleExcludeChange('role', 'admin')} />
|
||||
exclude admins
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[0]?.role.includes('owner')} onChange={() => handleExcludeChange('role', 'owner')} />
|
||||
exclude owners
|
||||
</label>
|
||||
</div>
|
||||
{rolesToExclude.map((role) => (
|
||||
<div key={role}>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[0]?.role.includes(role)} onChange={() => handleExcludeChange('role', role)} />
|
||||
exclude {role}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.challengeOption}>
|
||||
Actions
|
||||
<div className={styles.challengeOptionDescription}>Exclude a specific user action</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[0]?.post} onChange={(e) => handleExcludeChange('post', e.target.checked)} />
|
||||
exclude posts
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[1]?.post} onChange={(e) => handleExcludeChange('reply', e.target.checked)} />
|
||||
exclude replies
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[2]?.post} onChange={(e) => handleExcludeChange('vote', e.target.checked)} />
|
||||
exclude votes
|
||||
</label>
|
||||
</div>
|
||||
{actionsToExclude.map((action) => (
|
||||
<div key={action}>
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude[0]?.[action]} onChange={(e) => handleExcludeChange(action, e.target.checked)} />
|
||||
exclude {action}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.challengeOption}>
|
||||
Rate Limit
|
||||
<div className={styles.challengeOptionDescription}>Number of free user actions per hour:</div>
|
||||
<input type='number' placeholder='2' value={exclude?.rateLimit || undefined} onChange={(e) => handleExcludeChange('rateLimit', e.target.value)} />
|
||||
<label>
|
||||
<input type='checkbox' checked={exclude?.rateLimitChallengeSuccess} onChange={(e) => handleExcludeChange('rateLimitChallengeSuccess', e.target.checked)} />
|
||||
only rate limit after a challenge is successfully completed
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -419,9 +427,11 @@ const Challenges = () => {
|
||||
};
|
||||
|
||||
const handleAddChallenge = () => {
|
||||
const defaultChallenge = 'captcha-canvas-v3';
|
||||
const options = getDefaultChallengeOptions(defaultChallenge);
|
||||
const newChallenge = {
|
||||
name: 'captcha-canvas-v3',
|
||||
optionInputs: getDefaultOptionInputs('captcha-canvas-v3'),
|
||||
name: defaultChallenge,
|
||||
options,
|
||||
exclude: getDefaultExclude(),
|
||||
};
|
||||
const updatedChallenges = [...(settings?.challenges || []), newChallenge];
|
||||
@@ -437,7 +447,7 @@ const Challenges = () => {
|
||||
|
||||
const handleChallengeTypeChange = (index: number, newType: string) => {
|
||||
const updatedChallenges = [...challenges];
|
||||
updatedChallenges[index] = { ...updatedChallenges[index], name: newType, optionInputs: getDefaultOptionInputs(newType) };
|
||||
updatedChallenges[index] = { ...updatedChallenges[index], name: newType, options: getDefaultChallengeOptions(newType) };
|
||||
setSubmitStore({ settings: { ...settings, challenges: updatedChallenges } });
|
||||
};
|
||||
|
||||
@@ -473,13 +483,13 @@ const Challenges = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const FullSettings = () => {
|
||||
const JSONSettings = () => {
|
||||
const { title, description, address, suggested, rules, roles, settings, subplebbitAddress, setSubmitStore } = useSubplebbitSettingsStore();
|
||||
const [text, setText] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fullSettings = JSON.stringify({ title, description, address, suggested, rules, roles, settings, subplebbitAddress }, null, 2);
|
||||
setText(fullSettings);
|
||||
const JSONSettings = JSON.stringify({ title, description, address, suggested, rules, roles, settings, subplebbitAddress }, null, 2);
|
||||
setText(JSONSettings);
|
||||
}, [title, description, address, suggested, rules, roles, settings, subplebbitAddress]);
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
@@ -494,9 +504,9 @@ const FullSettings = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.box}>
|
||||
<div className={styles.boxTitle}>full settings data</div>
|
||||
<div className={styles.boxTitle}>JSON Settings</div>
|
||||
<div className={styles.boxSubtitle}>quickly copy or paste the community settings</div>
|
||||
<div className={`${styles.boxInput} ${styles.fullSettings}`}>
|
||||
<div className={`${styles.boxInput} ${styles.JSONSettings}`}>
|
||||
<textarea onChange={(e) => handleChange(e.target.value)} autoCorrect='off' autoComplete='off' spellCheck='false' value={text} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -570,7 +580,7 @@ const SubplebbitSettings = () => {
|
||||
<Moderators />
|
||||
{/* subplebbit.settings is private, only shows to the sub owner */}
|
||||
{settings?.challenges && <Challenges />}
|
||||
<FullSettings />
|
||||
<JSONSettings />
|
||||
<div className={styles.saveOptions}>
|
||||
<button disabled={!isElectron || !isAdmin || showLoading} onClick={saveSubplebbit}>
|
||||
{t('save_options')}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
.subplebbit {
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 1px;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
.arrowWrapper {
|
||||
padding-left: 11px;
|
||||
padding-top: 2px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.arrowCommon {
|
||||
@@ -167,14 +167,14 @@
|
||||
.subplebbitPreferences {
|
||||
padding: 0 1px;
|
||||
line-height: 1.6em;
|
||||
text-transform: lowercase;
|
||||
margin-top: 1px;
|
||||
margin-left: 28px;
|
||||
}
|
||||
|
||||
.subplebbitPreferences a {
|
||||
color: var(--gray-contrast);
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.subplebbitPreferences a:hover {
|
||||
@@ -182,20 +182,8 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.onlineStatus {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.roleLabel {
|
||||
padding-right: 2px;
|
||||
.label {
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.subplebbitsTabs {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Subplebbit as SubplebbitType, useAccount, useAccountSubplebbits, useSub
|
||||
import styles from './subplebbits.module.css';
|
||||
import Sidebar from '../../components/sidebar';
|
||||
import SubscribeButton from '../../components/subscribe-button';
|
||||
import { getFormattedTimeDuration, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { getFormattedTimeDuration } from '../../lib/utils/time-utils';
|
||||
import {
|
||||
isSubplebbitsView,
|
||||
isSubplebbitsSubscriberView,
|
||||
@@ -13,12 +13,11 @@ import {
|
||||
isSubplebbitsAdminView,
|
||||
isSubplebbitsOwnerView,
|
||||
isSubplebbitsVoteView,
|
||||
isSubplebbitsVotePassedView,
|
||||
isSubplebbitsVoteRejectedView,
|
||||
isSubplebbitsVotePassingView,
|
||||
isSubplebbitsVoteRejectingView,
|
||||
} from '../../lib/utils/view-utils';
|
||||
import { useDefaultSubplebbitAddresses } from '../../lib/utils/addresses-utils';
|
||||
import { RoleLabel } from '../../components/post/label/label';
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
import { OfflineLabel, RoleLabel } from '../../components/post/label/label';
|
||||
|
||||
interface SubplebbitProps {
|
||||
index?: number;
|
||||
@@ -58,8 +57,8 @@ const VoteTabs = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const isInSubplebbitsVoteView = isSubplebbitsVoteView(location.pathname);
|
||||
const isInSubplebbitsVotePassedView = isSubplebbitsVotePassedView(location.pathname);
|
||||
const isInSubplebbitsVoteRejectedView = isSubplebbitsVoteRejectedView(location.pathname);
|
||||
const isInSubplebbitsVotePassingView = isSubplebbitsVotePassingView(location.pathname);
|
||||
const isInSubplebbitsVoteRejectingView = isSubplebbitsVoteRejectingView(location.pathname);
|
||||
|
||||
return (
|
||||
<div className={styles.subplebbitsTabs}>
|
||||
@@ -67,12 +66,12 @@ const VoteTabs = () => {
|
||||
{t('all')}
|
||||
</Link>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Link to='/communities/vote/passed' className={isInSubplebbitsVotePassedView ? styles.selected : styles.choice} onClick={(e) => e.preventDefault()}>
|
||||
{t('passed')}
|
||||
<Link to='/communities/vote/passing' className={isInSubplebbitsVotePassingView ? styles.selected : styles.choice} onClick={(e) => e.preventDefault()}>
|
||||
{t('passing')}
|
||||
</Link>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Link to='/communities/vote/rejected' className={isInSubplebbitsVoteRejectedView ? styles.selected : styles.choice} onClick={(e) => e.preventDefault()}>
|
||||
{t('rejected')}
|
||||
<Link to='/communities/vote/rejecting' className={isInSubplebbitsVoteRejectingView ? styles.selected : styles.choice} onClick={(e) => e.preventDefault()}>
|
||||
{t('rejecting')}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
@@ -106,7 +105,7 @@ const Subplebbit = ({ subplebbit }: SubplebbitProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { address, createdAt, description, roles, shortAddress, settings, suggested, title, updatedAt } = subplebbit || {};
|
||||
|
||||
const [showDescription, setShowDescription] = useState(isMobile ? false : true);
|
||||
const [showDescription, setShowDescription] = useState(false);
|
||||
const buttonType = showDescription ? 'closeButton' : 'textButton';
|
||||
const toggleExpanded = () => setShowDescription(!showDescription);
|
||||
|
||||
@@ -122,11 +121,8 @@ const Subplebbit = ({ subplebbit }: SubplebbitProps) => {
|
||||
const downvoteCount = 0;
|
||||
|
||||
const postScore = upvoteCount === 0 && downvoteCount === 0 ? '•' : upvoteCount - downvoteCount || '•';
|
||||
const { allActiveUserCount } = useSubplebbitStats({ subplebbitAddress: address });
|
||||
const isOnline = updatedAt && updatedAt > Date.now() / 1000 - 60 * 30;
|
||||
const { allActiveUserCount, hourActiveUserCount } = useSubplebbitStats({ subplebbitAddress: address });
|
||||
const onlineNotice = t('users_online', { count: hourActiveUserCount });
|
||||
const offlineNotice = updatedAt && t('posts_last_synced', { dateAgo: getFormattedTimeAgo(updatedAt) });
|
||||
const onlineStatus = isOnline ? onlineNotice : offlineNotice;
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${t('communities')} - seedit`;
|
||||
@@ -169,15 +165,13 @@ const Subplebbit = ({ subplebbit }: SubplebbitProps) => {
|
||||
<span>
|
||||
{t('members_count', { count: allActiveUserCount })}, {t('community_for', { date: getFormattedTimeDuration(createdAt) })}
|
||||
<div className={styles.subplebbitPreferences}>
|
||||
{updatedAt && !isOnline && <OfflineLabel />}
|
||||
{(userRole || isUserOwner) && (
|
||||
<span className={styles.roleLabel}>
|
||||
<span className={styles.label}>
|
||||
<RoleLabel role={userRole || 'owner'} />
|
||||
</span>
|
||||
)}
|
||||
<Link to={`/p/${address}/settings`}>{t('settings')}</Link>
|
||||
<span className={styles.onlineLine}>
|
||||
<span>{onlineStatus}</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
60
yarn.lock
60
yarn.lock
@@ -2598,58 +2598,6 @@
|
||||
uuid "9.0.0"
|
||||
viem "1.5.2"
|
||||
|
||||
"@plebbit/plebbit-js@https://github.com/plebbit/plebbit-js.git#d081a9f4f1a086b5fccee5e9f332e113f61a90f1":
|
||||
version "0.0.3"
|
||||
resolved "https://github.com/plebbit/plebbit-js.git#d081a9f4f1a086b5fccee5e9f332e113f61a90f1"
|
||||
dependencies:
|
||||
"@ensdomains/eth-ens-namehash" "2.0.15"
|
||||
"@keyv/sqlite" "3.6.2"
|
||||
"@plebbit/plebbit-logger" "github:plebbit/plebbit-logger#9525ca9539479918931c3b334e8d490d1c87e507"
|
||||
"@plebbit/proper-lockfile" "github:plebbit/node-proper-lockfile#7fd6332117340c1d3d98dd0afee2d31cc06f72b8"
|
||||
"@types/node-fetch" "2.6.2"
|
||||
"@types/proper-lockfile" "4.1.2"
|
||||
"@types/uuid" "8.3.4"
|
||||
assert "2.0.0"
|
||||
async-wait-until "2.0.12"
|
||||
better-sqlite3 "9.2.0"
|
||||
buffer "6.0.3"
|
||||
captcha-canvas "3.2.1"
|
||||
cbor "9.0.1"
|
||||
debounce "1.2.1"
|
||||
err-code "3.0.1"
|
||||
ethers "6.7.0"
|
||||
file-type "16.5.4"
|
||||
form-data "4.0.0"
|
||||
hpagent "1.2.0"
|
||||
ipfs-http-client "56.0.3"
|
||||
ipfs-only-hash "4.0.0"
|
||||
is-ipfs "6.0.2"
|
||||
jose "4.11.0"
|
||||
js-sha256 "0.9.0"
|
||||
keyv "4.5.4"
|
||||
knex "3.0.1"
|
||||
libp2p-crypto "0.21.2"
|
||||
limiter "2.1.0"
|
||||
localforage "1.10.0"
|
||||
lodash-es "4.17.21"
|
||||
lru-cache "7.18.3"
|
||||
open-graph-scraper "4.11.1"
|
||||
p-limit "3.1.0"
|
||||
p-timeout "4.1.0"
|
||||
peer-id "0.16.0"
|
||||
probe-image-size "^7.2.3"
|
||||
retry "0.13.1"
|
||||
rpc-websockets "7.6.0"
|
||||
safe-stable-stringify "2.4.1"
|
||||
sha1-uint8array "0.10.3"
|
||||
skia-canvas "1.0.0"
|
||||
sqlite3 "5.1.6"
|
||||
tiny-typed-emitter "2.1.0"
|
||||
tinycache "1.1.2"
|
||||
ts-custom-error "3.3.1"
|
||||
uuid "9.0.0"
|
||||
viem "1.5.2"
|
||||
|
||||
"@plebbit/plebbit-logger@github:plebbit/plebbit-logger#9525ca9539479918931c3b334e8d490d1c87e507":
|
||||
version "0.0.1"
|
||||
uid "9525ca9539479918931c3b334e8d490d1c87e507"
|
||||
@@ -2663,11 +2611,11 @@
|
||||
dependencies:
|
||||
debug "4.3.3"
|
||||
|
||||
"@plebbit/plebbit-react-hooks@https://github.com/plebbit/plebbit-react-hooks.git#57f8891697134e8aa65f286ffd8bdbda9a6bd512":
|
||||
"@plebbit/plebbit-react-hooks@https://github.com/plebbit/plebbit-react-hooks.git#6b614e0bf6c304de7b184564198a0ce7b98bca1d":
|
||||
version "0.0.1"
|
||||
resolved "https://github.com/plebbit/plebbit-react-hooks.git#57f8891697134e8aa65f286ffd8bdbda9a6bd512"
|
||||
resolved "https://github.com/plebbit/plebbit-react-hooks.git#6b614e0bf6c304de7b184564198a0ce7b98bca1d"
|
||||
dependencies:
|
||||
"@plebbit/plebbit-js" "https://github.com/plebbit/plebbit-js.git#d081a9f4f1a086b5fccee5e9f332e113f61a90f1"
|
||||
"@plebbit/plebbit-js" "https://github.com/plebbit/plebbit-js.git#4a7ced7497fcbf863e9457b7e83a7bb2889bd518"
|
||||
"@plebbit/plebbit-logger" "https://github.com/plebbit/plebbit-logger.git"
|
||||
assert "2.0.0"
|
||||
ethers "5.6.9"
|
||||
@@ -13045,7 +12993,7 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
probe-image-size@7.2.3, probe-image-size@^7.2.3:
|
||||
probe-image-size@7.2.3:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.3.tgz#d49c64be540ec8edea538f6f585f65a9b3ab4309"
|
||||
integrity sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==
|
||||
|
||||
Reference in New Issue
Block a user