summaryrefslogtreecommitdiff
path: root/web/source
diff options
context:
space:
mode:
Diffstat (limited to 'web/source')
-rw-r--r--web/source/css/base.css51
-rw-r--r--web/source/settings/admin/federation/detail.js35
-rw-r--r--web/source/settings/admin/settings/index.jsx (renamed from web/source/settings/admin/settings.js)12
-rw-r--r--web/source/settings/admin/settings/rules.jsx169
-rw-r--r--web/source/settings/index.js5
-rw-r--r--web/source/settings/lib/query/admin/index.js48
-rw-r--r--web/source/settings/lib/query/base.js2
-rw-r--r--web/source/settings/lib/query/lib.js7
8 files changed, 306 insertions, 23 deletions
diff --git a/web/source/css/base.css b/web/source/css/base.css
index 87d2fcca7..5cd2cd047 100644
--- a/web/source/css/base.css
+++ b/web/source/css/base.css
@@ -542,6 +542,57 @@ label {
}
}
+.instance-rules {
+ list-style-position: inside;
+ margin: 0;
+ padding: 0;
+
+ a.rule {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+ color: $fg;
+ text-decoration: none;
+ background: $toot-bg;
+ padding: 1rem;
+ margin: 0.5rem 0;
+ border-radius: $br;
+ line-height: 2rem;
+ position: relative;
+
+ &:hover {
+ color: $fg-accent;
+
+ .edit-icon {
+ display: inline;
+ }
+ }
+
+ .edit-icon {
+ display: none;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ }
+
+ li {
+ font-size: 1.75rem;
+ padding: 0;
+ margin: 0;
+
+ h2 {
+ margin: 0;
+ margin-top: 0 !important;
+ display: inline-block;
+ font-size: 1.5rem;
+ }
+ }
+
+ span {
+ color: $fg-reduced;
+ }
+ }
+}
+
@media screen and (max-width: 30rem) {
.domain-blocklist .entry {
grid-template-columns: 1fr;
diff --git a/web/source/settings/admin/federation/detail.js b/web/source/settings/admin/federation/detail.js
index 344b9f9b6..a3bbfcac1 100644
--- a/web/source/settings/admin/federation/detail.js
+++ b/web/source/settings/admin/federation/detail.js
@@ -141,22 +141,29 @@ function DomainBlockForm({ defaultDomain, block = {}, baseUrl }) {
{...disabledForm}
/>
- <MutationButton
- label="Suspend"
- result={addResult}
- {...disabledForm}
- />
-
- {
- isExistingBlock &&
+ <div className="action-buttons row">
<MutationButton
- type="button"
- onClick={() => removeBlock(block.id)}
- label="Remove"
- result={removeResult}
- className="button danger"
+ label="Suspend"
+ result={addResult}
+ showError={false}
+ {...disabledForm}
/>
- }
+
+ {
+ isExistingBlock &&
+ <MutationButton
+ type="button"
+ onClick={() => removeBlock(block.id)}
+ label="Remove"
+ result={removeResult}
+ className="button danger"
+ showError={false}
+ />
+ }
+ </div>
+
+ {addResult.error && <Error error={addResult.error} />}
+ {removeResult.error && <Error error={removeResult.error} />}
</form>
);
diff --git a/web/source/settings/admin/settings.js b/web/source/settings/admin/settings/index.jsx
index ec986a6c4..dab476433 100644
--- a/web/source/settings/admin/settings.js
+++ b/web/source/settings/admin/settings/index.jsx
@@ -21,23 +21,23 @@
const React = require("react");
-const query = require("../lib/query");
+const query = require("../../lib/query");
const {
useTextInput,
useFileInput
-} = require("../lib/form");
+} = require("../../lib/form");
-const useFormSubmit = require("../lib/form/submit");
+const useFormSubmit = require("../../lib/form/submit");
const {
TextInput,
TextArea,
FileInput
-} = require("../components/form/inputs");
+} = require("../../components/form/inputs");
-const FormWithData = require("../lib/form/form-with-data");
-const MutationButton = require("../components/form/mutation-button");
+const FormWithData = require("../../lib/form/form-with-data");
+const MutationButton = require("../../components/form/mutation-button");
module.exports = function AdminSettings() {
return (
diff --git a/web/source/settings/admin/settings/rules.jsx b/web/source/settings/admin/settings/rules.jsx
new file mode 100644
index 000000000..330bc07fd
--- /dev/null
+++ b/web/source/settings/admin/settings/rules.jsx
@@ -0,0 +1,169 @@
+/*
+ GoToSocial
+ Copyright (C) GoToSocial Authors admin@gotosocial.org
+ SPDX-License-Identifier: AGPL-3.0-or-later
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+"use strict";
+
+const React = require("react");
+const { Switch, Route, Link, Redirect, useRoute } = require("wouter");
+
+const query = require("../../lib/query");
+const FormWithData = require("../../lib/form/form-with-data");
+const { useBaseUrl } = require("../../lib/navigation/util");
+
+const { useValue, useTextInput } = require("../../lib/form");
+const useFormSubmit = require("../../lib/form/submit");
+
+const { TextArea } = require("../../components/form/inputs");
+const MutationButton = require("../../components/form/mutation-button");
+
+module.exports = function InstanceRulesData({ baseUrl }) {
+ return (
+ <FormWithData
+ dataQuery={query.useInstanceRulesQuery}
+ DataForm={InstanceRules}
+ baseUrl={baseUrl}
+ />
+ );
+};
+
+function InstanceRules({ baseUrl, data: rules }) {
+ return (
+ <Switch>
+ <Route path={`${baseUrl}/:ruleId`}>
+ <InstanceRuleDetail rules={rules} />
+ </Route>
+ <Route>
+ <div>
+ <h1>Instance Rules</h1>
+ <div>
+ <p>
+ The rules for your instance are listed on the about page, and can be selected when submitting reports.
+ </p>
+ </div>
+ <InstanceRuleList rules={rules} />
+ </div>
+ </Route>
+ </Switch>
+ );
+}
+
+function InstanceRuleList({ rules }) {
+ const newRule = useTextInput("text", {});
+
+ const [submitForm, result] = useFormSubmit({ newRule }, query.useAddInstanceRuleMutation(), {
+ onFinish: () => newRule.reset()
+ });
+
+ return (
+ <>
+ <form onSubmit={submitForm} className="new-rule">
+ <ol className="instance-rules">
+ {Object.values(rules).map((rule) => (
+ <InstanceRule key={rule.id} rule={rule} />
+ ))}
+ </ol>
+ <TextArea
+ field={newRule}
+ label="New instance rule"
+ />
+ <MutationButton label="Add rule" result={result} />
+ </form>
+ </>
+ );
+}
+
+function InstanceRule({ rule }) {
+ const baseUrl = useBaseUrl();
+
+ return (
+ <Link to={`${baseUrl}/${rule.id}`}>
+ <a className="rule">
+ <li>
+ <h2>{rule.text} <i className="fa fa-pencil edit-icon" /></h2>
+ </li>
+ <span>{new Date(rule.created_at).toLocaleString()}</span>
+ </a>
+ </Link>
+ );
+}
+
+function InstanceRuleDetail({ rules }) {
+ const baseUrl = useBaseUrl();
+ let [_match, params] = useRoute(`${baseUrl}/:ruleId`);
+
+ if (params?.ruleId == undefined || rules[params.ruleId] == undefined) {
+ return <Redirect to={baseUrl} />;
+ } else {
+ return (
+ <>
+ <Link to={baseUrl}><a>&lt; go back</a></Link>
+ <InstanceRuleForm rule={rules[params.ruleId]} />
+ </>
+ );
+ }
+}
+
+function InstanceRuleForm({ rule }) {
+ const baseUrl = useBaseUrl();
+ const form = {
+ id: useValue("id", rule.id),
+ rule: useTextInput("text", { defaultValue: rule.text })
+ };
+
+ const [submitForm, result] = useFormSubmit(form, query.useUpdateInstanceRuleMutation());
+
+ const [deleteRule, deleteResult] = query.useDeleteInstanceRuleMutation({ fixedCacheKey: rule.id });
+
+ if (result.isSuccess || deleteResult.isSuccess) {
+ return (
+ <Redirect to={baseUrl} />
+ );
+ }
+
+ return (
+ <div className="rule-detail">
+ <form onSubmit={submitForm}>
+ <TextArea
+ field={form.rule}
+ />
+
+ <div className="action-buttons row">
+ <MutationButton
+ label="Save"
+ showError={false}
+ result={result}
+ disabled={!form.rule.hasChanged()}
+ />
+
+ <MutationButton
+ type="button"
+ onClick={() => deleteRule(rule.id)}
+ label="Delete"
+ className="button danger"
+ showError={false}
+ result={deleteResult}
+ />
+ </div>
+
+ {result.error && <Error error={result.error} />}
+ {deleteResult.error && <Error error={deleteResult.error} />}
+ </form>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/web/source/settings/index.js b/web/source/settings/index.js
index 8eb11e0aa..398bca0f6 100644
--- a/web/source/settings/index.js
+++ b/web/source/settings/index.js
@@ -60,7 +60,10 @@ const { Sidebar, ViewRouter } = createNavigation("/settings", [
Item("Local", { icon: "fa-home", wildcard: true }, require("./admin/emoji/local")),
Item("Remote", { icon: "fa-cloud" }, require("./admin/emoji/remote"))
]),
- Item("Settings", { icon: "fa-sliders" }, require("./admin/settings"))
+ Menu("Settings", { icon: "fa-sliders" }, [
+ Item("Settings", { icon: "fa-sliders", url: "" }, require("./admin/settings")),
+ Item("Rules", { icon: "fa-dot-circle-o", wildcard: true }, require("./admin/settings/rules"))
+ ])
])
]);
diff --git a/web/source/settings/lib/query/admin/index.js b/web/source/settings/lib/query/admin/index.js
index dd4a61b51..515d8edcf 100644
--- a/web/source/settings/lib/query/admin/index.js
+++ b/web/source/settings/lib/query/admin/index.js
@@ -22,7 +22,8 @@
const {
replaceCacheOnMutation,
removeFromCacheOnMutation,
- domainListToObject
+ domainListToObject,
+ idListToObject
} = require("../lib");
const base = require("../base");
@@ -104,6 +105,51 @@ const endpoints = (build) => ({
return res.accounts ?? [];
}
}),
+ instanceRules: build.query({
+ query: () => ({
+ url: `/api/v1/admin/instance/rules`
+ }),
+ transformResponse: idListToObject
+ }),
+ addInstanceRule: build.mutation({
+ query: (formData) => ({
+ method: "POST",
+ url: `/api/v1/admin/instance/rules`,
+ asForm: true,
+ body: formData,
+ discardEmpty: true
+ }),
+ transformResponse: (data) => {
+ return {
+ [data.id]: data
+ };
+ },
+ ...replaceCacheOnMutation("instanceRules")
+ }),
+ updateInstanceRule: build.mutation({
+ query: ({ id, ...edit }) => ({
+ method: "PATCH",
+ url: `/api/v1/admin/instance/rules/${id}`,
+ asForm: true,
+ body: edit,
+ discardEmpty: true
+ }),
+ transformResponse: (data) => {
+ return {
+ [data.id]: data
+ };
+ },
+ ...replaceCacheOnMutation("instanceRules")
+ }),
+ deleteInstanceRule: build.mutation({
+ query: (id) => ({
+ method: "DELETE",
+ url: `/api/v1/admin/instance/rules/${id}`
+ }),
+ ...removeFromCacheOnMutation("instanceRules", {
+ findKey: (_draft, rule) => rule.id
+ })
+ }),
...require("./import-export")(build),
...require("./custom-emoji")(build),
...require("./reports")(build)
diff --git a/web/source/settings/lib/query/base.js b/web/source/settings/lib/query/base.js
index 653fc449b..ba02d4e07 100644
--- a/web/source/settings/lib/query/base.js
+++ b/web/source/settings/lib/query/base.js
@@ -59,7 +59,7 @@ function instanceBasedQuery(args, api, extraOptions) {
module.exports = createApi({
reducerPath: "api",
baseQuery: instanceBasedQuery,
- tagTypes: ["Auth", "Emoji", "Reports", "Account"],
+ tagTypes: ["Auth", "Emoji", "Reports", "Account", "InstanceRules"],
endpoints: (build) => ({
instance: build.query({
query: () => ({
diff --git a/web/source/settings/lib/query/lib.js b/web/source/settings/lib/query/lib.js
index 78a9291b7..56ce05478 100644
--- a/web/source/settings/lib/query/lib.js
+++ b/web/source/settings/lib/query/lib.js
@@ -37,6 +37,13 @@ module.exports = {
(_) => Object.fromEntries(_)
]);
},
+ idListToObject: (data) => {
+ // Turn flat Array into Object keyed by entry id field
+ return syncpipe(data, [
+ (_) => _.map((entry) => [entry.id, entry]),
+ (_) => Object.fromEntries(_)
+ ]);
+ },
replaceCacheOnMutation: makeCacheMutation((draft, newData) => {
Object.assign(draft, newData);
}),