diff --git a/public/translations/en/default.json b/public/translations/en/default.json
index b1395741..2bdd7662 100644
--- a/public/translations/en/default.json
+++ b/public/translations/en/default.json
@@ -224,7 +224,7 @@
"add_moderator": "add a moderator",
"add_rule": "add a rule",
"json_settings": "JSON settings",
- "json_settings_info": "quickly copy or paste the community settings",
+ "json_settings_info": "quickly copy and paste the community settings",
"address_setting_info": "Set a readable community address using a crypto domain",
"enter_crypto_address": "Please enter a valid crypto address.",
"check_for_updates": "<1>Check1> for updates",
diff --git a/src/app.tsx b/src/app.tsx
index 87f8fa66..6c59b10c 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -15,6 +15,7 @@ import PostPage from './views/post-page';
import Profile from './views/profile';
import Settings from './views/settings';
import AccountDataEditor from './views/settings/account-data-editor';
+import SubplebbitDataEditor from './views/subplebbit-settings/subplebbit-data-editor';
import SubmitPage from './views/submit-page';
import Subplebbit from './views/subplebbit';
import SubplebbitSettings from './views/subplebbit-settings';
@@ -97,6 +98,7 @@ const App = () => {
} />
} />
+ } />
} />
} />
} />
diff --git a/src/lib/utils/view-utils.ts b/src/lib/utils/view-utils.ts
index 1bbce1d3..9906a683 100644
--- a/src/lib/utils/view-utils.ts
+++ b/src/lib/utils/view-utils.ts
@@ -176,7 +176,7 @@ export const isSubplebbitAboutView = (pathname: string, params: ParamsType): boo
};
export const isSubplebbitSettingsView = (pathname: string, params: ParamsType): boolean => {
- return params.subplebbitAddress ? pathname === `/p/${params.subplebbitAddress}/settings` : false;
+ return params.subplebbitAddress ? pathname === `/p/${params.subplebbitAddress}/settings` || pathname === `/p/${params.subplebbitAddress}/settings/editor` : false;
};
export const isSubplebbitSubmitView = (pathname: string, params: ParamsType): boolean => {
diff --git a/src/views/subplebbit-settings/subplebbit-data-editor/index.ts b/src/views/subplebbit-settings/subplebbit-data-editor/index.ts
new file mode 100644
index 00000000..5edffcdf
--- /dev/null
+++ b/src/views/subplebbit-settings/subplebbit-data-editor/index.ts
@@ -0,0 +1 @@
+export { default } from './subplebbit-data-editor';
diff --git a/src/views/subplebbit-settings/subplebbit-data-editor/subplebbit-data-editor.tsx b/src/views/subplebbit-settings/subplebbit-data-editor/subplebbit-data-editor.tsx
new file mode 100644
index 00000000..87e90c18
--- /dev/null
+++ b/src/views/subplebbit-settings/subplebbit-data-editor/subplebbit-data-editor.tsx
@@ -0,0 +1,211 @@
+import React, { useEffect, useMemo, useState, lazy, Suspense, Component } from 'react';
+import { Trans, useTranslation } from 'react-i18next';
+import { usePublishSubplebbitEdit, useSubplebbit } from '@plebbit/plebbit-react-hooks';
+import useTheme from '../../../stores/use-theme-store';
+import styles from '../../settings/account-data-editor/account-data-editor.module.css';
+import useIsMobile from '../../../hooks/use-is-mobile';
+import LoadingEllipsis from '../../../components/loading-ellipsis';
+import useSubplebbitSettingsStore from '../../../stores/use-subplebbit-settings-store';
+import { useParams } from 'react-router-dom';
+import ErrorDisplay from '../../../components/error-display';
+import useStateString from '../../../hooks/use-state-string';
+
+class EditorErrorBoundary extends Component<{ children: React.ReactNode; fallback: React.ReactNode }> {
+ constructor(props: { children: React.ReactNode; fallback: React.ReactNode }) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError() {
+ return { hasError: true };
+ }
+
+ componentDidCatch(error: any, errorInfo: any) {
+ console.error('Ace Editor failed to load:', error, errorInfo);
+ }
+
+ render() {
+ if ((this.state as any).hasError) {
+ return this.props.fallback;
+ }
+ return this.props.children;
+ }
+}
+
+const LazyAceEditor = lazy(async () => {
+ const ReactAceModule = await import('react-ace');
+ await import('ace-builds/src-noconflict/mode-json');
+ await import('ace-builds/src-noconflict/theme-github');
+ await import('ace-builds/src-noconflict/theme-tomorrow_night');
+ return ReactAceModule;
+});
+
+const FallbackEditor = ({ value, onChange, height, disabled }: { value: string; onChange: (value: string) => void; height: string; disabled?: boolean }) => {
+ const { t } = useTranslation();
+
+ return (
+
+
{t('editor_fallback_warning', 'Advanced editor failed to load. Using basic text editor as fallback.')}
+
+ );
+};
+
+const SubplebbitDataEditor = () => {
+ const { t } = useTranslation();
+ const isMobile = useIsMobile();
+ const theme = useTheme((state) => state.theme);
+ const [text, setText] = useState('');
+
+ const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
+ const subplebbit = useSubplebbit({ subplebbitAddress });
+ const { address, challenges, createdAt, description, error, rules, settings, suggested, roles, title } = subplebbit || {};
+ const hasLoaded = !!createdAt;
+
+ const { publishSubplebbitEditOptions, setSubplebbitSettingsStore, resetSubplebbitSettingsStore } = useSubplebbitSettingsStore();
+
+ const { error: publishSubplebbitEditError, publishSubplebbitEdit } = usePublishSubplebbitEdit(publishSubplebbitEditOptions);
+
+ const subplebbitSettings = useMemo(
+ () => JSON.stringify({ title, description, address, suggested, rules, roles, settings, challenges, subplebbitAddress }, null, 2),
+ [title, description, address, suggested, rules, roles, settings, challenges, subplebbitAddress],
+ );
+
+ useEffect(() => {
+ setText(subplebbitSettings);
+ }, [subplebbitSettings]);
+
+ const [showSaving, setShowSaving] = useState(false);
+ const [currentError, setCurrentError] = useState(undefined);
+
+ const saveSubplebbitSettings = async () => {
+ try {
+ setShowSaving(true);
+ setCurrentError(undefined);
+ console.log('Saving subplebbit with options:', publishSubplebbitEditOptions);
+ await publishSubplebbitEdit();
+ setShowSaving(false);
+ if (publishSubplebbitEditError) {
+ setCurrentError(publishSubplebbitEditError);
+ alert(publishSubplebbitEditError.message || 'Error: ' + publishSubplebbitEditError);
+ } else {
+ alert(t('settings_saved', { subplebbitAddress }));
+ }
+ } catch (e) {
+ if (e instanceof Error) {
+ console.warn(e);
+ setCurrentError(e);
+ alert(`failed editing subplebbit: ${e.message}`);
+ } else {
+ console.error('An unknown error occurred:', e);
+ }
+ }
+ };
+
+ // Set store for loaded subplebbit settings when editing
+ useEffect(() => {
+ if (hasLoaded) {
+ resetSubplebbitSettingsStore();
+ setSubplebbitSettingsStore({
+ title: title ?? '',
+ description: description ?? '',
+ address,
+ suggested: suggested ?? {},
+ rules: rules ?? [],
+ roles: roles ?? {},
+ settings: settings ?? {},
+ challenges: challenges ?? [],
+ subplebbitAddress,
+ });
+ }
+ }, [
+ hasLoaded,
+ resetSubplebbitSettingsStore,
+ setSubplebbitSettingsStore,
+ title,
+ description,
+ address,
+ suggested,
+ rules,
+ roles,
+ settings,
+ challenges,
+ subplebbitAddress,
+ ]);
+
+ const loadingStateString = useStateString(subplebbit);
+
+ if (!hasLoaded) {
+ return (
+ <>
+ {error?.message && (
+
+
+
+ )}
+
+
+
+ >
+ );
+ }
+
+ return (
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ {currentError && error: {currentError.message || 'unknown error'}
}
+ {showSaving ? (
+
+
+
+ ) : (
+
+ ,
+ 2:
+ )}
+
+ );
+};
+
+export default SubplebbitDataEditor;
diff --git a/src/views/subplebbit-settings/subplebbit-settings.tsx b/src/views/subplebbit-settings/subplebbit-settings.tsx
index ead796b9..6c7b71ce 100644
--- a/src/views/subplebbit-settings/subplebbit-settings.tsx
+++ b/src/views/subplebbit-settings/subplebbit-settings.tsx
@@ -323,30 +323,15 @@ const Moderators = ({ isReadOnly = false }: { isReadOnly?: boolean }) => {
const JSONSettings = ({ isReadOnly = false }: { isReadOnly?: boolean }) => {
const { t } = useTranslation();
- const { challenges, title, description, address, suggested, rules, roles, settings, subplebbitAddress, setSubplebbitSettingsStore } = useSubplebbitSettingsStore();
- const [text, setText] = useState('');
-
- useEffect(() => {
- const JSONSettings = JSON.stringify({ title, description, address, suggested, rules, roles, settings, challenges, subplebbitAddress }, null, 2);
- setText(JSONSettings);
- }, [challenges, title, description, address, suggested, rules, roles, settings, subplebbitAddress]);
-
- const handleChange = (newText: string) => {
- setText(newText);
- try {
- const newSettings = JSON.parse(newText);
- setSubplebbitSettingsStore(newSettings);
- } catch (e) {
- console.error('Invalid JSON format');
- }
- };
+ const navigate = useNavigate();
+ const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
return (
{t('json_settings')}
{t('json_settings_info')}
-
);