summaryrefslogtreecommitdiff
path: root/web/source/settings/admin/emoji
diff options
context:
space:
mode:
Diffstat (limited to 'web/source/settings/admin/emoji')
-rw-r--r--web/source/settings/admin/emoji/category-select.jsx96
-rw-r--r--web/source/settings/admin/emoji/local/detail.js146
-rw-r--r--web/source/settings/admin/emoji/local/index.tsx35
-rw-r--r--web/source/settings/admin/emoji/local/new-emoji.tsx116
-rw-r--r--web/source/settings/admin/emoji/local/overview.js153
-rw-r--r--web/source/settings/admin/emoji/local/use-shortcode.js56
-rw-r--r--web/source/settings/admin/emoji/remote/index.tsx54
-rw-r--r--web/source/settings/admin/emoji/remote/parse-from-toot.tsx235
8 files changed, 0 insertions, 891 deletions
diff --git a/web/source/settings/admin/emoji/category-select.jsx b/web/source/settings/admin/emoji/category-select.jsx
deleted file mode 100644
index e5cf29939..000000000
--- a/web/source/settings/admin/emoji/category-select.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- 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/>.
-*/
-
-const React = require("react");
-const splitFilterN = require("split-filter-n");
-const syncpipe = require('syncpipe');
-const { matchSorter } = require("match-sorter");
-
-const ComboBox = require("../../components/combo-box");
-const { useListEmojiQuery } = require("../../lib/query/admin/custom-emoji");
-
-function useEmojiByCategory(emoji) {
- // split all emoji over an object keyed by the category names (or Unsorted)
- return React.useMemo(() => splitFilterN(
- emoji,
- [],
- (entry) => entry.category ?? "Unsorted"
- ), [emoji]);
-}
-
-function CategorySelect({ field, children }) {
- const { value, setIsNew } = field;
-
- const {
- data: emoji = [],
- isLoading,
- isSuccess,
- error
- } = useListEmojiQuery({ filter: "domain:local" });
-
- const emojiByCategory = useEmojiByCategory(emoji);
-
- const categories = React.useMemo(() => new Set(Object.keys(emojiByCategory)), [emojiByCategory]);
-
- // data used by the ComboBox element to select an emoji category
- const categoryItems = React.useMemo(() => {
- return syncpipe(emojiByCategory, [
- (_) => Object.keys(_), // just emoji category names
- (_) => matchSorter(_, value, { threshold: matchSorter.rankings.NO_MATCH }), // sorted by complex algorithm
- (_) => _.map((categoryName) => [ // map to input value, and selectable element with icon
- categoryName,
- <>
- <img src={emojiByCategory[categoryName][0].static_url} aria-hidden="true"></img>
- {categoryName}
- </>
- ])
- ]);
- }, [emojiByCategory, value]);
-
- React.useEffect(() => {
- if (value != undefined && isSuccess && value.trim().length > 0) {
- setIsNew(!categories.has(value.trim()));
- }
- }, [categories, value, isSuccess, setIsNew]);
-
- if (error) { // fall back to plain text input, but this would almost certainly have caused a bigger error message elsewhere
- return (
- <>
- <input type="text" placeholder="e.g., reactions" onChange={(e) => { field.value = e.target.value; }} />;
- </>
- );
- } else if (isLoading) {
- return <input type="text" value="Loading categories..." disabled={true} />;
- }
-
- return (
- <ComboBox
- field={field}
- items={categoryItems}
- label="Category"
- placeholder="e.g., reactions"
- children={children}
- />
- );
-}
-
-module.exports = {
- useEmojiByCategory,
- CategorySelect
-}; \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/detail.js b/web/source/settings/admin/emoji/local/detail.js
deleted file mode 100644
index a78e3e499..000000000
--- a/web/source/settings/admin/emoji/local/detail.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- 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/>.
-*/
-
-import React, { useEffect } from "react";
-import { useRoute, Link, Redirect } from "wouter";
-
-import { useComboBoxInput, useFileInput, useValue } from "../../../lib/form";
-import { CategorySelect } from "../category-select";
-
-import useFormSubmit from "../../../lib/form/submit";
-import { useBaseUrl } from "../../../lib/navigation/util";
-
-import FakeToot from "../../../components/fake-toot";
-import FormWithData from "../../../lib/form/form-with-data";
-import Loading from "../../../components/loading";
-import { FileInput } from "../../../components/form/inputs";
-import MutationButton from "../../../components/form/mutation-button";
-import { Error } from "../../../components/error";
-
-import { useGetEmojiQuery, useEditEmojiMutation, useDeleteEmojiMutation } from "../../../lib/query/admin/custom-emoji";
-
-export default function EmojiDetailRoute({ }) {
- const baseUrl = useBaseUrl();
- let [_match, params] = useRoute(`${baseUrl}/:emojiId`);
- if (params?.emojiId == undefined) {
- return <Redirect to={baseUrl} />;
- } else {
- return (
- <div className="emoji-detail">
- <Link to={baseUrl}><a>&lt; go back</a></Link>
- <FormWithData dataQuery={useGetEmojiQuery} queryArg={params.emojiId} DataForm={EmojiDetailForm} />
- </div>
- );
- }
-}
-
-function EmojiDetailForm({ data: emoji }) {
- const baseUrl = useBaseUrl();
- const form = {
- id: useValue("id", emoji.id),
- category: useComboBoxInput("category", { source: emoji }),
- image: useFileInput("image", {
- withPreview: true,
- maxSize: 50 * 1024 // TODO: get from instance api
- })
- };
-
- const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation());
-
- // Automatic submitting of category change
- useEffect(() => {
- if (
- form.category.hasChanged() &&
- !form.category.state.open &&
- !form.category.isNew) {
- modifyEmoji();
- }
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [form.category.hasChanged(), form.category.isNew, form.category.state.open]);
-
- const [deleteEmoji, deleteResult] = useDeleteEmojiMutation();
-
- if (deleteResult.isSuccess) {
- return <Redirect to={baseUrl} />;
- }
-
- return (
- <>
- <div className="emoji-header">
- <img src={emoji.url} alt={emoji.shortcode} title={emoji.shortcode} />
- <div>
- <h2>{emoji.shortcode}</h2>
- <MutationButton
- label="Delete"
- type="button"
- onClick={() => deleteEmoji(emoji.id)}
- className="danger"
- showError={false}
- result={deleteResult}
- />
- </div>
- </div>
-
- <form onSubmit={modifyEmoji} className="left-border">
- <h2>Modify this emoji {result.isLoading && <Loading />}</h2>
-
- <div className="update-category">
- <CategorySelect
- field={form.category}
- >
- <MutationButton
- name="create-category"
- label="Create"
- result={result}
- showError={false}
- style={{ visibility: (form.category.isNew ? "initial" : "hidden") }}
- />
- </CategorySelect>
- </div>
-
- <div className="update-image">
- <FileInput
- field={form.image}
- label="Image"
- accept="image/png,image/gif"
- />
-
- <MutationButton
- name="image"
- label="Replace image"
- showError={false}
- result={result}
- />
-
- <FakeToot>
- Look at this new custom emoji <img
- className="emoji"
- src={form.image.previewURL ?? emoji.url}
- title={`:${emoji.shortcode}:`}
- alt={emoji.shortcode}
- /> isn&apos;t it cool?
- </FakeToot>
-
- {result.error && <Error error={result.error} />}
- {deleteResult.error && <Error error={deleteResult.error} />}
- </div>
- </form>
- </>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/index.tsx b/web/source/settings/admin/emoji/local/index.tsx
deleted file mode 100644
index 74a891f3e..000000000
--- a/web/source/settings/admin/emoji/local/index.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- 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/>.
-*/
-
-import React from "react";
-import { Switch, Route } from "wouter";
-
-import EmojiOverview from "./overview";
-import EmojiDetail from "./detail";
-
-export default function CustomEmoji({ baseUrl }) {
- return (
- <Switch>
- <Route path={`${baseUrl}/:emojiId`}>
- <EmojiDetail />
- </Route>
- <EmojiOverview />
- </Switch>
- );
-}
diff --git a/web/source/settings/admin/emoji/local/new-emoji.tsx b/web/source/settings/admin/emoji/local/new-emoji.tsx
deleted file mode 100644
index c6a203765..000000000
--- a/web/source/settings/admin/emoji/local/new-emoji.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- 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/>.
-*/
-
-import React, { useMemo, useEffect } from "react";
-
-import { useFileInput, useComboBoxInput } from "../../../lib/form";
-import useShortcode from "./use-shortcode";
-
-import useFormSubmit from "../../../lib/form/submit";
-
-import { TextInput, FileInput } from "../../../components/form/inputs";
-
-import { CategorySelect } from '../category-select';
-import FakeToot from "../../../components/fake-toot";
-import MutationButton from "../../../components/form/mutation-button";
-import { useAddEmojiMutation } from "../../../lib/query/admin/custom-emoji";
-import { useInstanceV1Query } from "../../../lib/query";
-
-export default function NewEmojiForm() {
- const shortcode = useShortcode();
-
- const { data: instance } = useInstanceV1Query();
- const emojiMaxSize = useMemo(() => {
- return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024;
- }, [instance]);
-
- const image = useFileInput("image", {
- withPreview: true,
- maxSize: emojiMaxSize
- });
-
- const category = useComboBoxInput("category");
-
- const [submitForm, result] = useFormSubmit({
- shortcode, image, category
- }, useAddEmojiMutation());
-
- useEffect(() => {
- if (shortcode.value === undefined || shortcode.value.length == 0) {
- if (image.value != undefined) {
- let [name, _ext] = image.value.name.split(".");
- shortcode.setter(name);
- }
- }
-
- /* We explicitly don't want to have 'shortcode' as a dependency here
- because we only want to change the shortcode to the filename if the field is empty
- at the moment the file is selected, not some time after when the field is emptied
- */
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [image.value]);
-
- let emojiOrShortcode;
-
- if (image.previewValue != undefined) {
- emojiOrShortcode = <img
- className="emoji"
- src={image.previewValue}
- title={`:${shortcode.value}:`}
- alt={shortcode.value}
- />;
- } else if (shortcode.value !== undefined && shortcode.value.length > 0) {
- emojiOrShortcode = `:${shortcode.value}:`;
- } else {
- emojiOrShortcode = `:your_emoji_here:`;
- }
-
- return (
- <div>
- <h2>Add new custom emoji</h2>
-
- <FakeToot>
- Look at this new custom emoji {emojiOrShortcode} isn&apos;t it cool?
- </FakeToot>
-
- <form onSubmit={submitForm} className="form-flex">
- <FileInput
- field={image}
- accept="image/png,image/gif,image/webp"
- />
-
- <TextInput
- field={shortcode}
- label="Shortcode, must be unique among the instance's local emoji"
- />
-
- <CategorySelect
- field={category}
- children={[]}
- />
-
- <MutationButton
- disabled={image.previewValue === undefined}
- label="Upload emoji"
- result={result}
- />
- </form>
- </div>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/overview.js b/web/source/settings/admin/emoji/local/overview.js
deleted file mode 100644
index 45bfd614d..000000000
--- a/web/source/settings/admin/emoji/local/overview.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- 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/>.
-*/
-
-const React = require("react");
-const { Link } = require("wouter");
-const syncpipe = require("syncpipe");
-const { matchSorter } = require("match-sorter");
-
-const NewEmojiForm = require("./new-emoji").default;
-const { useTextInput } = require("../../../lib/form");
-
-const { useEmojiByCategory } = require("../category-select");
-const { useBaseUrl } = require("../../../lib/navigation/util");
-
-const Loading = require("../../../components/loading");
-const { Error } = require("../../../components/error");
-const { TextInput } = require("../../../components/form/inputs");
-const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");
-
-module.exports = function EmojiOverview({ }) {
- const {
- data: emoji = [],
- isLoading,
- isError,
- error
- } = useListEmojiQuery({ filter: "domain:local" });
-
- let content = null;
-
- if (isLoading) {
- content = <Loading />;
- } else if (isError) {
- content = <Error error={error} />;
- } else {
- content = (
- <>
- <EmojiList emoji={emoji} />
- <NewEmojiForm emoji={emoji} />
- </>
- );
- }
-
- return (
- <>
- <h1>Local Custom Emoji</h1>
- <p>
- To use custom emoji in your toots they have to be 'local' to the instance.
- You can either upload them here directly, or copy from those already
- present on other (known) instances through the <Link to={`./remote`}>Remote Emoji</Link> page.
- </p>
- <p>
- <strong>Be warned!</strong> If you upload more than about 300-400 custom emojis in
- total on your instance, this may lead to rate-limiting issues for users and clients
- if they try to load all the emoji images at once (which is what many clients do).
- </p>
- {content}
- </>
- );
-};
-
-function EmojiList({ emoji }) {
- const filterField = useTextInput("filter");
- const filter = filterField.value;
-
- const emojiByCategory = useEmojiByCategory(emoji);
-
- /* Filter emoji based on shortcode match with user input, hiding empty categories */
- const { filteredEmoji, hidden } = React.useMemo(() => {
- let hidden = emoji.length;
- const filteredEmoji = syncpipe(emojiByCategory, [
- (_) => Object.entries(emojiByCategory),
- (_) => _.map(([category, entries]) => {
- let filteredEntries = matchSorter(entries, filter, { keys: ["shortcode"] });
- if (filteredEntries.length == 0) {
- return null;
- } else {
- hidden -= filteredEntries.length;
- return [category, filteredEntries];
- }
- }),
- (_) => _.filter((value) => value !== null)
- ]);
-
- return { filteredEmoji, hidden };
- }, [filter, emojiByCategory, emoji.length]);
-
- return (
- <div>
- <h2>Overview</h2>
- {emoji.length > 0
- ? <span>{emoji.length} custom emoji {hidden > 0 && `(${hidden} filtered)`}</span>
- : <span>No custom emoji yet, you can add one below.</span>
- }
- <div className="list emoji-list">
- <div className="header">
- <TextInput
- field={filterField}
- name="emoji-shortcode"
- placeholder="Search"
- />
- </div>
- <div className="entries scrolling">
- {filteredEmoji.length > 0
- ? (
- <div className="entries scrolling">
- {filteredEmoji.map(([category, entries]) => {
- return <EmojiCategory key={category} category={category} entries={entries} />;
- })}
- </div>
- )
- : <div className="entry">No local emoji matched your filter.</div>
- }
- </div>
- </div>
- </div>
- );
-}
-
-function EmojiCategory({ category, entries }) {
- const baseUrl = useBaseUrl();
- return (
- <div className="entry">
- <b>{category}</b>
- <div className="emoji-group">
- {entries.map((e) => {
- return (
- <Link key={e.id} to={`${baseUrl}/${e.id}`}>
- <a>
- <img src={e.url} alt={e.shortcode} title={`:${e.shortcode}:`} />
- </a>
- </Link>
- );
- })}
- </div>
- </div>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/use-shortcode.js b/web/source/settings/admin/emoji/local/use-shortcode.js
deleted file mode 100644
index 67255860f..000000000
--- a/web/source/settings/admin/emoji/local/use-shortcode.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- 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/>.
-*/
-
-const React = require("react");
-
-const { useTextInput } = require("../../../lib/form");
-const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");
-
-const shortcodeRegex = /^\w{2,30}$/;
-
-module.exports = function useShortcode() {
- const { data: emoji = [] } = useListEmojiQuery({
- filter: "domain:local"
- });
-
- const emojiCodes = React.useMemo(() => {
- return new Set(emoji.map((e) => e.shortcode));
- }, [emoji]);
-
- return useTextInput("shortcode", {
- validator: function validateShortcode(code) {
- // technically invalid, but hacky fix to prevent validation error on page load
- if (code == "") { return ""; }
-
- if (emojiCodes.has(code)) {
- return "Shortcode already in use";
- }
-
- if (code.length < 2 || code.length > 30) {
- return "Shortcode must be between 2 and 30 characters";
- }
-
- if (!shortcodeRegex.test(code)) {
- return "Shortcode must only contain letters, numbers, and underscores";
- }
-
- return "";
- }
- });
-}; \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/remote/index.tsx b/web/source/settings/admin/emoji/remote/index.tsx
deleted file mode 100644
index d9c786be2..000000000
--- a/web/source/settings/admin/emoji/remote/index.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- 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/>.
-*/
-
-import React, { useMemo } from "react";
-
-import ParseFromToot from "./parse-from-toot";
-
-import Loading from "../../../components/loading";
-import { Error } from "../../../components/error";
-import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji";
-
-export default function RemoteEmoji() {
- // local emoji are queried for shortcode collision detection
- const {
- data: emoji = [],
- isLoading,
- error
- } = useListEmojiQuery({ filter: "domain:local" });
-
- const emojiCodes = useMemo(() => {
- return new Set(emoji.map((e) => e.shortcode));
- }, [emoji]);
-
- return (
- <>
- <h1>Custom Emoji (remote)</h1>
- {error &&
- <Error error={error} />
- }
- {isLoading
- ? <Loading />
- : <>
- <ParseFromToot emojiCodes={emojiCodes} />
- </>
- }
- </>
- );
-}
diff --git a/web/source/settings/admin/emoji/remote/parse-from-toot.tsx b/web/source/settings/admin/emoji/remote/parse-from-toot.tsx
deleted file mode 100644
index df1c221ba..000000000
--- a/web/source/settings/admin/emoji/remote/parse-from-toot.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- 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/>.
-*/
-
-import React, { useCallback, useEffect } from "react";
-
-import { useTextInput, useComboBoxInput, useCheckListInput } from "../../../lib/form";
-
-import useFormSubmit from "../../../lib/form/submit";
-
-import CheckList from "../../../components/check-list";
-import { CategorySelect } from '../category-select';
-
-import { TextInput } from "../../../components/form/inputs";
-import MutationButton from "../../../components/form/mutation-button";
-import { Error } from "../../../components/error";
-import { useSearchItemForEmojiMutation, usePatchRemoteEmojisMutation } from "../../../lib/query/admin/custom-emoji";
-
-export default function ParseFromToot({ emojiCodes }) {
- const [searchStatus, result] = useSearchItemForEmojiMutation();
- const urlField = useTextInput("url");
-
- function submitSearch(e) {
- e.preventDefault();
- if (urlField.value !== undefined && urlField.value.trim().length != 0) {
- searchStatus(urlField.value);
- }
- }
-
- return (
- <div className="parse-emoji">
- <h2>Steal this look</h2>
- <form onSubmit={submitSearch}>
- <div className="form-field text">
- <label htmlFor="url">
- Link to a toot:
- </label>
- <div className="row">
- <input
- type="text"
- id="url"
- name="url"
- onChange={urlField.onChange}
- value={urlField.value}
- />
- <button disabled={result.isLoading}>
- <i className={[
- "fa fa-fw",
- (result.isLoading
- ? "fa-refresh fa-spin"
- : "fa-search")
- ].join(" ")} aria-hidden="true" title="Search" />
- <span className="sr-only">Search</span>
- </button>
- </div>
- </div>
- </form>
- <SearchResult result={result} localEmojiCodes={emojiCodes} />
- </div>
- );
-}
-
-function SearchResult({ result, localEmojiCodes }) {
- const { error, data, isSuccess, isError } = result;
-
- if (!(isSuccess || isError)) {
- return null;
- }
-
- if (error == "NONE_FOUND") {
- return "No results found";
- } else if (error == "LOCAL_INSTANCE") {
- return <b>This is a local user/toot, all referenced emoji are already on your instance</b>;
- } else if (error != undefined) {
- return <Error error={result.error} />;
- }
-
- if (data.list.length == 0) {
- return <b>This {data.type == "statuses" ? "toot" : "account"} doesn't use any custom emoji</b>;
- }
-
- return (
- <CopyEmojiForm
- localEmojiCodes={localEmojiCodes}
- type={data.type}
- emojiList={data.list}
- />
- );
-}
-
-function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
- const form = {
- selectedEmoji: useCheckListInput("selectedEmoji", {
- entries: emojiList,
- uniqueKey: "id"
- }),
- category: useComboBoxInput("category")
- };
-
- const [formSubmit, result] = useFormSubmit(
- form,
- usePatchRemoteEmojisMutation(),
- {
- changedOnly: false,
- onFinish: ({ data }) => {
- if (data) {
- // uncheck all successfully processed emoji
- const processed = data.map((emoji) => {
- return [emoji.id, { checked: false }];
- });
- form.selectedEmoji.updateMultiple(processed);
- }
- }
- }
- );
-
- const buttonsInactive = form.selectedEmoji.someSelected
- ? {
- disabled: false,
- title: ""
- }
- : {
- disabled: true,
- title: "No emoji selected, cannot perform any actions"
- };
-
- const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
-
- return (
- <div className="parsed">
- <span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span>
- <form onSubmit={formSubmit}>
- <CheckList
- field={form.selectedEmoji}
- header={<></>}
- EntryComponent={EmojiEntry}
- getExtraProps={checkListExtraProps}
- />
-
- <CategorySelect
- field={form.category}
- children={[]}
- />
-
- <div className="action-buttons row">
- <MutationButton
- name="copy"
- label="Copy to local emoji"
- result={result}
- showError={false}
- {...buttonsInactive}
- />
- <MutationButton
- name="disable"
- label="Disable"
- result={result}
- className="button danger"
- showError={false}
- {...buttonsInactive}
- />
- </div>
- {result.error && (
- Array.isArray(result.error)
- ? <ErrorList errors={result.error} />
- : <Error error={result.error} />
- )}
- </form>
- </div>
- );
-}
-
-function ErrorList({ errors }) {
- return (
- <div className="error">
- One or multiple emoji failed to process:
- {errors.map(([shortcode, err]) => (
- <div key={shortcode}>
- <b>{shortcode}:</b> {err}
- </div>
- ))}
- </div>
- );
-}
-
-function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } }) {
- const shortcodeField = useTextInput("shortcode", {
- defaultValue: emoji.shortcode,
- validator: function validateShortcode(code) {
- return (emoji.checked && localEmojiCodes.has(code))
- ? "Shortcode already in use"
- : "";
- }
- });
-
- useEffect(() => {
- if (emoji.valid != shortcodeField.valid) {
- onChange({ valid: shortcodeField.valid });
- }
- }, [onChange, emoji.valid, shortcodeField.valid]);
-
- useEffect(() => {
- shortcodeField.validate();
- // only need this update if it's the emoji.checked that updated, not shortcodeField
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [emoji.checked]);
-
- return (
- <>
- <img className="emoji" src={emoji.url} title={emoji.shortcode} />
-
- <TextInput
- field={shortcodeField}
- onChange={(e) => {
- shortcodeField.onChange(e);
- onChange({ shortcode: e.target.value, checked: true });
- }}
- />
- </>
- );
-} \ No newline at end of file