diff --git a/package.json b/package.json index 31e69da9..5f237db5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app.tsx b/src/app.tsx index 3b7eb136..c4ef875e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -109,8 +109,8 @@ function App() { } /> } /> } /> - } /> - } /> + } /> + } /> diff --git a/src/components/markdown/markdown.tsx b/src/components/markdown/markdown.tsx index 65f0fc21..d4f81058 100644 --- a/src/components/markdown/markdown.tsx +++ b/src/components/markdown/markdown.tsx @@ -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 ( { const { t } = useTranslation(); - return {t('deleted').toUpperCase()}; + return {t('deleted')}; }; export const FailedLabel = () => { const { t } = useTranslation(); - return {t('failed').toUpperCase()}; + return {t('failed')}; }; export const PendingLabel = () => { const { t } = useTranslation(); - return {t('pending').toUpperCase()}; + return {t('pending')}; }; export const RemovedLabel = () => { const { t } = useTranslation(); - return {t('removed').toUpperCase()}; + return {t('removed')}; }; export const SpoilerLabel = () => { const { t } = useTranslation(); - return {t('spoiler').toUpperCase()}; + return {t('spoiler')}; }; export const RoleLabel = ({ role }: { role: string }) => { const { t } = useTranslation(); - return {t(role).toUpperCase()}; + return {t(role)}; +}; + +export const OfflineLabel = () => { + const { t } = useTranslation(); + + return {t('offline')}; }; diff --git a/src/lib/utils/challenge-utils.ts b/src/lib/utils/challenge-utils.ts index 6fad2311..651b49c1 100644 --- a/src/lib/utils/challenge-utils.ts +++ b/src/lib/utils/challenge-utils.ts @@ -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 [ diff --git a/src/lib/utils/view-utils.ts b/src/lib/utils/view-utils.ts index 1643ceea..30a080c1 100644 --- a/src/lib/utils/view-utils.ts +++ b/src/lib/utils/view-utils.ts @@ -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'; }; diff --git a/src/views/subplebbit/subplebbit-settings/subplebbit-settings.module.css b/src/views/subplebbit/subplebbit-settings/subplebbit-settings.module.css index 1f095954..f45bc034 100644 --- a/src/views/subplebbit/subplebbit-settings/subplebbit-settings.module.css +++ b/src/views/subplebbit/subplebbit-settings/subplebbit-settings.module.css @@ -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 { diff --git a/src/views/subplebbit/subplebbit-settings/subplebbit-settings.tsx b/src/views/subplebbit/subplebbit-settings/subplebbit-settings.tsx index dfc52e1a..b08f1d98 100644 --- a/src/views/subplebbit/subplebbit-settings/subplebbit-settings.tsx +++ b/src/views/subplebbit/subplebbit-settings/subplebbit-settings.tsx @@ -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 (
{getDefaultChallengeDescription(name)}
- {challenge?.optionInputs?.map((inputOption: OptionInput) => ( -
- {inputOption.label} -
{inputOption.description}
+ {challengeSettings.map((setting) => ( +
+
{setting?.label}
+
{setting?.description}
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} />
))}
Exclude from challenge
+
+ Users +
Exclude specific users by their addresses, separated by a comma
+ handleExcludeAddress(e.target.value)} + /> +
+
+ Users with Karma +
Minimum post karma required:
+ handleExcludeChange('postScore', e.target.value)} /> +
Minimum comment karma required:
+ handleExcludeChange('postReply', e.target.value)} /> +
+
+ Users by account age +
Minimum account age in Unix Timestamp (seconds):
+ handleExcludeChange('firstCommentTimestamp', e.target.value)} + /> +
Moderators
Exclude a specific moderator role
-
- -
-
- -
-
- -
+ {rolesToExclude.map((role) => ( +
+ +
+ ))}
Actions
Exclude a specific user action
-
- -
-
- -
-
- -
+ {actionsToExclude.map((action) => ( +
+ +
+ ))} +
+
+ Rate Limit +
Number of free user actions per hour:
+ handleExcludeChange('rateLimit', e.target.value)} /> +
); @@ -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 (
-
full settings data
+
JSON Settings
quickly copy or paste the community settings
-
+