mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-05-20 01:08:36 +00:00
Compare commits
4 commits
419d024792
...
490ef7a2ef
Author | SHA1 | Date | |
---|---|---|---|
490ef7a2ef | |||
578a4e0cf5 | |||
f24ce34c3a | |||
f456bd3401 |
2
go.mod
2
go.mod
|
@ -2,7 +2,7 @@ module github.com/superseriousbusiness/gotosocial
|
|||
|
||||
go 1.22.2
|
||||
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround
|
||||
|
||||
require (
|
||||
codeberg.org/gruf/go-bytes v1.0.2
|
||||
|
|
6
go.sum
6
go.sum
|
@ -422,8 +422,6 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
|
@ -637,8 +635,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround h1:ESobxED9bfE0nOQP/WPv9+tMR8oZoDIWRKlNK2Vs4Ms=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround h1:gFAlklid3jyXIuZBy5Vy0dhG+F6YBgosRy4syT5CDsg=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
|
6
internal/cache/db.go
vendored
6
internal/cache/db.go
vendored
|
@ -148,15 +148,15 @@ type GTSCaches struct {
|
|||
// Tag provides access to the gtsmodel Tag database cache.
|
||||
Tag StructCache[*gtsmodel.Tag]
|
||||
|
||||
// ThreadMute provides access to the gtsmodel ThreadMute database cache.
|
||||
ThreadMute StructCache[*gtsmodel.ThreadMute]
|
||||
|
||||
// Token provides access to the gtsmodel Token database cache.
|
||||
Token StructCache[*gtsmodel.Token]
|
||||
|
||||
// Tombstone provides access to the gtsmodel Tombstone database cache.
|
||||
Tombstone StructCache[*gtsmodel.Tombstone]
|
||||
|
||||
// ThreadMute provides access to the gtsmodel ThreadMute database cache.
|
||||
ThreadMute StructCache[*gtsmodel.ThreadMute]
|
||||
|
||||
// User provides access to the gtsmodel User database cache.
|
||||
User StructCache[*gtsmodel.User]
|
||||
|
||||
|
|
8
internal/cache/size.go
vendored
8
internal/cache/size.go
vendored
|
@ -172,6 +172,8 @@ func totalOfRatios() float64 {
|
|||
return 0 +
|
||||
config.GetCacheAccountMemRatio() +
|
||||
config.GetCacheAccountNoteMemRatio() +
|
||||
config.GetCacheAccountSettingsMemRatio() +
|
||||
config.GetCacheAccountStatsMemRatio() +
|
||||
config.GetCacheApplicationMemRatio() +
|
||||
config.GetCacheBlockMemRatio() +
|
||||
config.GetCacheBlockIDsMemRatio() +
|
||||
|
@ -179,17 +181,21 @@ func totalOfRatios() float64 {
|
|||
config.GetCacheClientMemRatio() +
|
||||
config.GetCacheEmojiMemRatio() +
|
||||
config.GetCacheEmojiCategoryMemRatio() +
|
||||
config.GetCacheFilterMemRatio() +
|
||||
config.GetCacheFilterKeywordMemRatio() +
|
||||
config.GetCacheFilterStatusMemRatio() +
|
||||
config.GetCacheFollowMemRatio() +
|
||||
config.GetCacheFollowIDsMemRatio() +
|
||||
config.GetCacheFollowRequestMemRatio() +
|
||||
config.GetCacheFollowRequestIDsMemRatio() +
|
||||
config.GetCacheInReplyToIDsMemRatio() +
|
||||
config.GetCacheInstanceMemRatio() +
|
||||
config.GetCacheInReplyToIDsMemRatio() +
|
||||
config.GetCacheListMemRatio() +
|
||||
config.GetCacheListEntryMemRatio() +
|
||||
config.GetCacheMarkerMemRatio() +
|
||||
config.GetCacheMediaMemRatio() +
|
||||
config.GetCacheMentionMemRatio() +
|
||||
config.GetCacheMoveMemRatio() +
|
||||
config.GetCacheNotificationMemRatio() +
|
||||
config.GetCachePollMemRatio() +
|
||||
config.GetCachePollVoteMemRatio() +
|
||||
|
|
1
vendor/modernc.org/sqlite/lib/sqlite_darwin_amd64.go
generated
vendored
1
vendor/modernc.org/sqlite/lib/sqlite_darwin_amd64.go
generated
vendored
|
@ -228935,4 +228935,3 @@ type Sqlite3_index_info = sqlite3_index_info
|
|||
type Sqlite3_module = sqlite3_module
|
||||
type Sqlite3_vtab = sqlite3_vtab
|
||||
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor
|
||||
|
||||
|
|
1
vendor/modernc.org/sqlite/lib/sqlite_darwin_arm64.go
generated
vendored
1
vendor/modernc.org/sqlite/lib/sqlite_darwin_arm64.go
generated
vendored
|
@ -228490,4 +228490,3 @@ type Sqlite3_index_info = sqlite3_index_info
|
|||
type Sqlite3_module = sqlite3_module
|
||||
type Sqlite3_vtab = sqlite3_vtab
|
||||
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor
|
||||
|
||||
|
|
6
vendor/modernc.org/sqlite/sqlite.go
generated
vendored
6
vendor/modernc.org/sqlite/sqlite.go
generated
vendored
|
@ -701,7 +701,7 @@ type tx struct {
|
|||
c *conn
|
||||
}
|
||||
|
||||
func newTx(c *conn, opts driver.TxOptions) (*tx, error) {
|
||||
func newTx(ctx context.Context, c *conn, opts driver.TxOptions) (*tx, error) {
|
||||
r := &tx{c: c}
|
||||
|
||||
sql := "begin"
|
||||
|
@ -709,7 +709,7 @@ func newTx(c *conn, opts driver.TxOptions) (*tx, error) {
|
|||
sql = "begin " + c.beginMode
|
||||
}
|
||||
|
||||
if err := r.exec(context.Background(), sql); err != nil {
|
||||
if err := r.exec(ctx, sql); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -1348,7 +1348,7 @@ func (c *conn) Begin() (dt driver.Tx, err error) {
|
|||
}
|
||||
|
||||
func (c *conn) begin(ctx context.Context, opts driver.TxOptions) (t driver.Tx, err error) {
|
||||
return newTx(c, opts)
|
||||
return newTx(ctx, c, opts)
|
||||
}
|
||||
|
||||
// Close invalidates and potentially stops any current prepared statements and
|
||||
|
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
|
@ -1278,7 +1278,7 @@ modernc.org/mathutil
|
|||
# modernc.org/memory v1.8.0
|
||||
## explicit; go 1.18
|
||||
modernc.org/memory
|
||||
# modernc.org/sqlite v1.29.8 => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround
|
||||
# modernc.org/sqlite v1.29.8 => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround
|
||||
## explicit; go 1.20
|
||||
modernc.org/sqlite
|
||||
modernc.org/sqlite/lib
|
||||
|
@ -1291,4 +1291,4 @@ modernc.org/token
|
|||
# mvdan.cc/xurls/v2 v2.5.0
|
||||
## explicit; go 1.19
|
||||
mvdan.cc/xurls/v2
|
||||
# modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround
|
||||
# modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { SerializedError } from "@reduxjs/toolkit";
|
||||
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
function ErrorFallback({ error, resetErrorBoundary }) {
|
||||
return (
|
||||
|
@ -44,39 +46,70 @@ function ErrorFallback({ error, resetErrorBoundary }) {
|
|||
);
|
||||
}
|
||||
|
||||
function Error({ error }) {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.error("Rendering error:", error);
|
||||
let message;
|
||||
interface GtsError {
|
||||
/**
|
||||
* Error message returned from the API.
|
||||
*/
|
||||
error: string;
|
||||
|
||||
if (error.data != undefined) { // RTK Query error with data
|
||||
if (error.status) {
|
||||
message = (<>
|
||||
<b>{error.status}:</b> {error.data.error}
|
||||
{error.data.error_description &&
|
||||
<p>
|
||||
{error.data.error_description}
|
||||
</p>
|
||||
}
|
||||
</>);
|
||||
} else {
|
||||
message = error.data.error;
|
||||
}
|
||||
} else if (error.name != undefined || error.type != undefined) { // JS error
|
||||
message = (<>
|
||||
<b>{error.type && error.name}:</b> {error.message}
|
||||
</>);
|
||||
} else if (error.status && typeof error.error == "string") {
|
||||
message = (<>
|
||||
<b>{error.status}:</b> {error.error}
|
||||
</>);
|
||||
/**
|
||||
* For OAuth errors: description of the error.
|
||||
*/
|
||||
error_description?: string;
|
||||
}
|
||||
|
||||
interface ErrorProps {
|
||||
error: FetchBaseQueryError | SerializedError | Error | undefined;
|
||||
|
||||
/**
|
||||
* Optional function to clear the error.
|
||||
* If provided, rendered error will have
|
||||
* a "dismiss" button.
|
||||
*/
|
||||
reset?: () => void;
|
||||
}
|
||||
|
||||
function Error({ error, reset }: ErrorProps) {
|
||||
if (error === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.error("caught error: ", error);
|
||||
|
||||
let message: ReactNode;
|
||||
if ("status" in error) {
|
||||
// RTK Query error with data.
|
||||
const gtsError = error.data as GtsError;
|
||||
const errMsg = gtsError.error_description ?? gtsError.error;
|
||||
message = <>Code {error.status} {errMsg}</>;
|
||||
} else {
|
||||
message = error.message ?? error;
|
||||
// SerializedError or Error.
|
||||
const errMsg = error.message ?? JSON.stringify(error);
|
||||
message = (
|
||||
<>{error.name && `${error.name}: `}{errMsg}</>
|
||||
);
|
||||
}
|
||||
|
||||
let className = "error";
|
||||
if (reset) {
|
||||
className += " with-dismiss";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="error">
|
||||
{message}
|
||||
<div className={className}>
|
||||
<span>{message}</span>
|
||||
{ reset &&
|
||||
<span
|
||||
className="dismiss"
|
||||
onClick={reset}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<span>Dismiss</span>
|
||||
<i className="fa fa-fw fa-close" aria-hidden="true" />
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import type {
|
|||
RadioFormInputHook,
|
||||
TextFormInputHook,
|
||||
} from "../../lib/form/types";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface TextInputProps extends React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
|
@ -92,22 +93,25 @@ export interface FileInputProps extends React.DetailedHTMLProps<
|
|||
|
||||
export function FileInput({ label, field, ...props }: FileInputProps) {
|
||||
const { onChange, ref, infoComponent } = field;
|
||||
const id = nanoid();
|
||||
|
||||
return (
|
||||
<div className="form-field file">
|
||||
<label>
|
||||
<div className="label">{label}</div>
|
||||
<div className="file-input button">Browse</div>
|
||||
{infoComponent}
|
||||
{/* <a onClick={removeFile("header")}>remove</a> */}
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={onChange}
|
||||
ref={ref ? ref as RefObject<HTMLInputElement> : undefined}
|
||||
{...props}
|
||||
/>
|
||||
<label className="label-label" htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
<label className="label-button" htmlFor={id}>
|
||||
<div className="file-input button">Browse</div>
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={onChange}
|
||||
ref={ref ? ref as RefObject<HTMLInputElement> : undefined}
|
||||
{...props}
|
||||
/>
|
||||
{infoComponent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -51,9 +51,9 @@ export default function MutationButton({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<div className={wrapperClassName ? wrapperClassName : "mutation-button"}>
|
||||
{(showError && targetsThisButton && result.error) &&
|
||||
<Error error={result.error} />
|
||||
<Error error={result.error} reset={result.reset} />
|
||||
}
|
||||
<button
|
||||
type="submit"
|
||||
|
|
|
@ -27,6 +27,7 @@ import type {
|
|||
HookOpts,
|
||||
FileFormInputHook,
|
||||
} from "./types";
|
||||
import { Error as ErrorC } from "../../components/error";
|
||||
|
||||
const _default = undefined;
|
||||
export default function useFileInput(
|
||||
|
@ -41,6 +42,15 @@ export default function useFileInput(
|
|||
const [imageURL, setImageURL] = useState<string>();
|
||||
const [info, setInfo] = useState<React.JSX.Element>();
|
||||
|
||||
function reset() {
|
||||
if (imageURL) {
|
||||
URL.revokeObjectURL(imageURL);
|
||||
}
|
||||
setImageURL(undefined);
|
||||
setFile(undefined);
|
||||
setInfo(undefined);
|
||||
}
|
||||
|
||||
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const files = e.target.files;
|
||||
if (!files) {
|
||||
|
@ -59,25 +69,18 @@ export default function useFileInput(
|
|||
setImageURL(URL.createObjectURL(file));
|
||||
}
|
||||
|
||||
let size = prettierBytes(file.size);
|
||||
const sizePrettier = prettierBytes(file.size);
|
||||
if (maxSize && file.size > maxSize) {
|
||||
size = <span className="error-text">{size}</span>;
|
||||
const maxSizePrettier = prettierBytes(maxSize);
|
||||
setInfo(
|
||||
<ErrorC
|
||||
error={new Error(`file size ${sizePrettier} is larger than max size ${maxSizePrettier}`)}
|
||||
reset={(reset)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
setInfo(<>{file.name} ({sizePrettier})</>);
|
||||
}
|
||||
|
||||
setInfo(
|
||||
<>
|
||||
{file.name} ({size})
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (imageURL) {
|
||||
URL.revokeObjectURL(imageURL);
|
||||
}
|
||||
setImageURL(undefined);
|
||||
setFile(undefined);
|
||||
setInfo(undefined);
|
||||
}
|
||||
|
||||
const infoComponent = (
|
||||
|
|
|
@ -257,33 +257,37 @@ input, select, textarea {
|
|||
overflow: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.with-dismiss {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.dismiss {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mutation-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.messagebutton, .messagebutton > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
div.padded {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
white-space: nowrap;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.messagebutton > div {
|
||||
button, .button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notImplemented {
|
||||
border: 2px solid rgb(70, 79, 88);
|
||||
background: repeating-linear-gradient(
|
||||
|
@ -500,12 +504,29 @@ form {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-field.file label {
|
||||
.form-field.file {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"label-label label-label"
|
||||
"label-button file-info"
|
||||
;
|
||||
|
||||
.label-label {
|
||||
grid-area: label-label;
|
||||
}
|
||||
|
||||
.label {
|
||||
grid-column: 1 / span 2;
|
||||
.label-button {
|
||||
grid-area: label-button;
|
||||
}
|
||||
|
||||
.form-info {
|
||||
grid-area: file-info;
|
||||
.error {
|
||||
padding: 0.1rem;
|
||||
line-height: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useEffect } from "react";
|
||||
import React, { useMemo, useEffect, ReactNode } from "react";
|
||||
import { useFileInput, useComboBoxInput } from "../../../../lib/form";
|
||||
import useShortcode from "./use-shortcode";
|
||||
import useFormSubmit from "../../../../lib/form/submit";
|
||||
|
@ -27,52 +27,74 @@ 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/gts-api";
|
||||
import prettierBytes from "prettier-bytes";
|
||||
|
||||
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 prettierMaxSize = useMemo(() => {
|
||||
return prettierBytes(emojiMaxSize);
|
||||
}, [emojiMaxSize]);
|
||||
|
||||
const category = useComboBoxInput("category");
|
||||
const form = {
|
||||
shortcode: useShortcode(),
|
||||
image: useFileInput("image", {
|
||||
withPreview: true,
|
||||
maxSize: emojiMaxSize
|
||||
}),
|
||||
category: useComboBoxInput("category"),
|
||||
};
|
||||
|
||||
const [submitForm, result] = useFormSubmit({
|
||||
shortcode, image, category
|
||||
}, useAddEmojiMutation());
|
||||
const [submitForm, result] = useFormSubmit(
|
||||
form,
|
||||
useAddEmojiMutation(),
|
||||
{
|
||||
changedOnly: false,
|
||||
// On submission, reset form values
|
||||
// no matter what the result was.
|
||||
onFinish: (_res) => {
|
||||
form.shortcode.reset();
|
||||
form.image.reset();
|
||||
form.category.reset();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (shortcode.value === undefined || shortcode.value.length == 0) {
|
||||
if (image.value != undefined) {
|
||||
let [name, _ext] = image.value.name.split(".");
|
||||
shortcode.setter(name);
|
||||
}
|
||||
// If shortcode has not been entered yet, but an image file
|
||||
// has been submitted, suggest a shortcode based on filename.
|
||||
if (
|
||||
(form.shortcode.value === undefined || form.shortcode.value.length === 0) &&
|
||||
form.image.value !== undefined
|
||||
) {
|
||||
let [name, _ext] = form.image.value.name.split(".");
|
||||
form.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]);
|
||||
// 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
|
||||
}, [form.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}:`;
|
||||
let emojiOrShortcode: ReactNode;
|
||||
if (form.image.previewValue !== undefined) {
|
||||
emojiOrShortcode = (
|
||||
<img
|
||||
className="emoji"
|
||||
src={form.image.previewValue}
|
||||
title={`:${form.shortcode.value}:`}
|
||||
alt={form.shortcode.value}
|
||||
/>
|
||||
);
|
||||
} else if (form.shortcode.value !== undefined && form.shortcode.value.length > 0) {
|
||||
emojiOrShortcode = `:${form.shortcode.value}:`;
|
||||
} else {
|
||||
emojiOrShortcode = `:your_emoji_here:`;
|
||||
}
|
||||
|
@ -87,22 +109,23 @@ export default function NewEmojiForm() {
|
|||
|
||||
<form onSubmit={submitForm} className="form-flex">
|
||||
<FileInput
|
||||
field={image}
|
||||
field={form.image}
|
||||
label={`Image file: png, gif, or static webp; max size ${prettierMaxSize}`}
|
||||
accept="image/png,image/gif,image/webp"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
field={shortcode}
|
||||
field={form.shortcode}
|
||||
label="Shortcode, must be unique among the instance's local emoji"
|
||||
{...{pattern: "^\\w{2,30}$"}}
|
||||
/>
|
||||
|
||||
<CategorySelect
|
||||
field={category}
|
||||
children={[]}
|
||||
field={form.category}
|
||||
/>
|
||||
|
||||
<MutationButton
|
||||
disabled={image.previewValue === undefined}
|
||||
disabled={form.image.previewValue === undefined || form.shortcode.value?.length === 0}
|
||||
label="Upload emoji"
|
||||
result={result}
|
||||
/>
|
||||
|
|
Loading…
Reference in a new issue