diff --git a/web/source/css/base.css b/web/source/css/base.css
index 576c0829f..ed8d55d05 100644
--- a/web/source/css/base.css
+++ b/web/source/css/base.css
@@ -261,12 +261,16 @@ section.login {
}
section.error {
+ word-break: break-word;
display: flex;
flex-direction: row;
align-items: center;
+ margin-bottom: 0.5rem;
+
span {
font-size: 2em;
}
+
pre {
border: 1px solid #ff000080;
margin-left: 1em;
diff --git a/web/source/settings/admin/emoji/new-emoji.js b/web/source/settings/admin/emoji/new-emoji.js
new file mode 100644
index 000000000..4aaaa8994
--- /dev/null
+++ b/web/source/settings/admin/emoji/new-emoji.js
@@ -0,0 +1,133 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 .
+*/
+
+"use strict";
+
+const Promise = require('bluebird');
+const React = require("react");
+
+const FakeToot = require("../../components/fake-toot");
+const MutateButton = require("../../components/mutation-button");
+
+const {
+ useTextInput,
+ useFileInput
+} = require("../../components/form");
+
+const query = require("../../lib/query");
+
+module.exports = function NewEmojiForm({emoji}) {
+ const emojiCodes = React.useMemo(() => {
+ return new Set(emoji.map((e) => e.shortcode));
+ }, [emoji]);
+
+ const [addEmoji, result] = query.useAddEmojiMutation();
+
+ const [onFileChange, resetFile, {image, imageURL, imageInfo}] = useFileInput("image", {
+ withPreview: true,
+ maxSize: 50 * 1000
+ });
+
+ const [onShortcodeChange, resetShortcode, {shortcode, setShortcode, shortcodeRef}] = useTextInput("shortcode", {
+ validator: function validateShortcode(code) {
+ return emojiCodes.has(code)
+ ? "Shortcode already in use"
+ : "";
+ }
+ });
+
+ React.useEffect(() => {
+ if (shortcode.length == 0) {
+ if (image != undefined) {
+ let [name, _ext] = image.name.split(".");
+ setShortcode(name);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [image]);
+
+ function uploadEmoji(e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ Promise.try(() => {
+ return addEmoji({
+ image,
+ shortcode
+ });
+ }).then(() => {
+ resetFile();
+ resetShortcode();
+ });
+ }
+
+ let emojiOrShortcode = `:${shortcode}:`;
+
+ if (imageURL != undefined) {
+ emojiOrShortcode = ;
+ }
+
+ return (
+
+
Add new custom emoji
+
+
+ Look at this new custom emoji {emojiOrShortcode} isn't it cool?
+
+
+
+
-
-
- Look at this new custom emoji {emojiOrShortcode} isn't it cool?
-
-
-
-
- );
}
\ No newline at end of file
diff --git a/web/source/settings/components/form-fields.jsx b/web/source/settings/components/form-fields.jsx
index cb402c3b2..6393b1d5c 100644
--- a/web/source/settings/components/form-fields.jsx
+++ b/web/source/settings/components/form-fields.jsx
@@ -119,7 +119,7 @@ module.exports = {
field = (
<>
-
+
{file ? file.name : "no file selected"} {size}
{/* remove */}
diff --git a/web/source/settings/components/form/file.jsx b/web/source/settings/components/form/file.jsx
new file mode 100644
index 000000000..7c34f3393
--- /dev/null
+++ b/web/source/settings/components/form/file.jsx
@@ -0,0 +1,78 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 .
+*/
+
+"use strict";
+
+const React = require("react");
+const prettierBytes = require("prettier-bytes");
+
+module.exports = function useFileInput({name, Name}, {
+ withPreview,
+ maxSize,
+ initialInfo = "no file selected"
+}) {
+ const [file, setFile] = React.useState();
+ const [imageURL, setImageURL] = React.useState();
+ const [info, setInfo] = React.useState();
+
+ function onChange(e) {
+ let file = e.target.files[0];
+ setFile(file);
+
+ URL.revokeObjectURL(imageURL);
+
+ if (file != undefined) {
+ if (withPreview) {
+ setImageURL(URL.createObjectURL(file));
+ }
+
+ let size = prettierBytes(file.size);
+ if (maxSize && file.size > maxSize) {
+ size = {size};
+ }
+
+ setInfo(<>
+ {file.name} ({size})
+ >);
+ } else {
+ setInfo();
+ }
+ }
+
+ function reset() {
+ URL.revokeObjectURL(imageURL);
+ setImageURL();
+ setFile();
+ setInfo();
+ }
+
+ return [
+ onChange,
+ reset,
+ {
+ [name]: file,
+ [`${name}URL`]: imageURL,
+ [`${name}Info`]:
+ {info
+ ? info
+ : initialInfo
+ }
+
+ }
+ ];
+};
\ No newline at end of file
diff --git a/web/source/settings/components/form/index.js b/web/source/settings/components/form/index.js
new file mode 100644
index 000000000..5edc52364
--- /dev/null
+++ b/web/source/settings/components/form/index.js
@@ -0,0 +1,36 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 .
+*/
+
+"use strict";
+
+function capitalizeFirst(str) {
+ return str.slice(0,1).toUpperCase()+str.slice(1);
+}
+
+function makeHook(func) {
+ return (name, ...args) => func({
+ name,
+ Name: capitalizeFirst(name)
+ },
+ ...args);
+}
+
+module.exports = {
+ useTextInput: makeHook(require("./text")),
+ useFileInput: makeHook(require("./file"))
+};
\ No newline at end of file
diff --git a/web/source/settings/components/form/text.jsx b/web/source/settings/components/form/text.jsx
new file mode 100644
index 000000000..a392239dc
--- /dev/null
+++ b/web/source/settings/components/form/text.jsx
@@ -0,0 +1,56 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 .
+*/
+
+"use strict";
+
+const React = require("react");
+
+module.exports = function useTextInput({name, Name}, {validator} = {}) {
+ const [text, setText] = React.useState("");
+ const textRef = React.useRef(null);
+
+ function onChange(e) {
+ let input = e.target.value;
+ setText(input);
+
+ if (validator) {
+ validator(input);
+ }
+ }
+
+ function reset() {
+ setText("");
+ }
+
+ React.useEffect(() => {
+ if (validator) {
+ textRef.current.setCustomValidity(validator(text));
+ textRef.current.reportValidity();
+ }
+ }, [text, textRef, validator]);
+
+ return [
+ onChange,
+ reset,
+ {
+ [name]: text,
+ [`${name}Ref`]: textRef,
+ [`set${Name}`]: setText
+ }
+ ];
+};
\ No newline at end of file
diff --git a/web/source/settings/components/mutation-button.jsx b/web/source/settings/components/mutation-button.jsx
index f79bab2d2..9a8c9d089 100644
--- a/web/source/settings/components/mutation-button.jsx
+++ b/web/source/settings/components/mutation-button.jsx
@@ -29,7 +29,7 @@ module.exports = function MutateButton({text, result}) {
return (