[frontend] Better autocapitalize/spellcheck settings on forms (#3077)

This commit is contained in:
tobi 2024-07-08 09:38:27 +02:00 committed by GitHub
parent 5769722c58
commit bbbf6ebe37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 205 additions and 38 deletions

View file

@ -22,9 +22,33 @@ import { TextInput } from "../../../../components/form/inputs";
import MutationButton from "../../../../components/form/mutation-button"; import MutationButton from "../../../../components/form/mutation-button";
import { useTextInput } from "../../../../lib/form"; import { useTextInput } from "../../../../lib/form";
import { useInstanceKeysExpireMutation } from "../../../../lib/query/admin/actions"; import { useInstanceKeysExpireMutation } from "../../../../lib/query/admin/actions";
import isValidDomain from "is-valid-domain";
export default function ExpireRemote({}) { export default function ExpireRemote({}) {
const domainField = useTextInput("domain"); const domainField = useTextInput("domain", {
validator: (v: string) => {
if (v.length === 0) {
return "";
}
if (v[v.length-1] === ".") {
return "invalid domain";
}
const valid = isValidDomain(v, {
subdomain: true,
wildcard: false,
allowUnicode: true,
topLevel: false,
});
if (valid) {
return "";
}
return "invalid domain";
}
});
const [expire, expireResult] = useInstanceKeysExpireMutation(); const [expire, expireResult] = useInstanceKeysExpireMutation();
@ -52,11 +76,13 @@ export default function ExpireRemote({}) {
<TextInput <TextInput
field={domainField} field={domainField}
label="Domain" label="Domain"
type="string" type="text"
autoCapitalize="none"
spellCheck="false"
placeholder="example.org" placeholder="example.org"
/> />
<MutationButton <MutationButton
disabled={!domainField.value} disabled={!domainField.value || !domainField.valid}
label="Expire keys" label="Expire keys"
result={expireResult} result={expireResult}
/> />

View file

@ -53,6 +53,9 @@ export default function ApURL() {
<TextInput <TextInput
field={urlField} field={urlField}
label="URL" label="URL"
type="url"
pattern="(http|https):\/\/.+"
placeholder="https://example.org/users/someone"
/> />
<MutationButton <MutationButton
disabled={!urlField.value} disabled={!urlField.value}

View file

@ -126,6 +126,8 @@ export function CategorySelect({ field, children }: PropsWithChildren<CategorySe
items={categoryItems} items={categoryItems}
label="Category" label="Category"
placeholder="e.g., reactions" placeholder="e.g., reactions"
autoCapitalize="none"
spellCheck="false"
> >
{children} {children}
</ComboBox> </ComboBox>

View file

@ -117,6 +117,8 @@ export default function NewEmojiForm() {
<TextInput <TextInput
field={form.shortcode} field={form.shortcode}
label="Shortcode, must be unique among the instance's local emoji" label="Shortcode, must be unique among the instance's local emoji"
autoCapitalize="none"
spellCheck="false"
{...{pattern: "^\\w{2,30}$"}} {...{pattern: "^\\w{2,30}$"}}
/> />

View file

@ -116,6 +116,8 @@ function EmojiList({ emoji }: EmojiListParams) {
field={filterField} field={filterField}
name="emoji-shortcode" name="emoji-shortcode"
placeholder="Search" placeholder="Search"
autoCapitalize="none"
spellCheck="false"
/> />
</div> </div>
<div className="entries scrolling"> <div className="entries scrolling">

View file

@ -52,9 +52,10 @@ export default function StealThisLook({ emojiCodes }) {
</label> </label>
<div className="row"> <div className="row">
<input <input
type="text"
id="url" id="url"
name="url" name="url"
type="url"
pattern="(http|https):\/\/.+"
onChange={urlField.onChange} onChange={urlField.onChange}
value={urlField.value} value={urlField.value}
/> />

View file

@ -100,7 +100,7 @@ export default function HeaderPermCreateForm({ permType }: { permType: PermType
field={form.header} field={form.header}
label={ label={
<> <>
HTTP Header Name&nbsp; Header Name&nbsp;
<a <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers"
target="_blank" target="_blank"
@ -112,12 +112,15 @@ export default function HeaderPermCreateForm({ permType }: { permType: PermType
</> </>
} }
placeholder={"User-Agent"} placeholder={"User-Agent"}
autoCapitalize="none"
spellCheck="false"
{...{className: "monospace"}}
/> />
<TextInput <TextInput
field={form.regex} field={form.regex}
label={ label={
<> <>
HTTP Header Value RE2 Regex&nbsp; Value Regex&nbsp;
<a <a
href="https://github.com/google/re2/wiki/Syntax" href="https://github.com/google/re2/wiki/Syntax"
target="_blank" target="_blank"
@ -129,6 +132,8 @@ export default function HeaderPermCreateForm({ permType }: { permType: PermType
</> </>
} }
placeholder={"^.*Some-User-Agent.*$"} placeholder={"^.*Some-User-Agent.*$"}
autoCapitalize="none"
spellCheck="false"
{...{className: "monospace"}} {...{className: "monospace"}}
/> />
<MutationButton <MutationButton

View file

@ -164,7 +164,7 @@ function PermDeets({
<dl className="info-list"> <dl className="info-list">
<div className="info-list-entry"> <div className="info-list-entry">
<dt>ID</dt> <dt>ID</dt>
<dd>{perm.id}</dd> <dd className="monospace">{perm.id}</dd>
</div> </div>
<div className="info-list-entry"> <div className="info-list-entry">
<dt>Created</dt> <dt>Created</dt>
@ -176,10 +176,10 @@ function PermDeets({
</div> </div>
<div className="info-list-entry"> <div className="info-list-entry">
<dt>Header Name</dt> <dt>Header Name</dt>
<dd>{perm.header}</dd> <dd className="monospace">{perm.header}</dd>
</div> </div>
<div className="info-list-entry"> <div className="info-list-entry">
<dt>Header Value Regex</dt> <dt>Value Regex</dt>
<dd className="monospace">{perm.regex}</dd> <dd className="monospace">{perm.regex}</dd>
</div> </div>
<div className="info-list-entry"> <div className="info-list-entry">

View file

@ -79,7 +79,10 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation()); const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation());
return ( return (
<form onSubmit={submitForm}> <form
onSubmit={submitForm}
autoComplete="none"
>
<h1>Instance Settings</h1> <h1>Instance Settings</h1>
<div className="form-section-docs"> <div className="form-section-docs">
@ -97,7 +100,8 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
<TextInput <TextInput
field={form.title} field={form.title}
label={`Instance title (max ${titleLimit} characters)`} label={`Instance title (max ${titleLimit} characters)`}
placeholder="My GoToSocial instance" autoCapitalize="words"
placeholder="My GoToSocial Instance"
/> />
<div className="file-upload" aria-labelledby="avatar"> <div className="file-upload" aria-labelledby="avatar">
@ -117,6 +121,7 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
field={form.thumbnailDesc} field={form.thumbnailDesc}
label="Avatar image description" label="Avatar image description"
placeholder="A cute drawing of a smiling sloth." placeholder="A cute drawing of a smiling sloth."
autoCapitalize="sentences"
/> />
</div> </div>
</div> </div>
@ -139,6 +144,7 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
field={form.shortDesc} field={form.shortDesc}
label={`Short description (markdown accepted, max ${shortDescLimit} characters)`} label={`Short description (markdown accepted, max ${shortDescLimit} characters)`}
placeholder="A small testing instance for the GoToSocial alpha software." placeholder="A small testing instance for the GoToSocial alpha software."
autoCapitalize="sentences"
rows={6} rows={6}
/> />
@ -146,6 +152,7 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
field={form.description} field={form.description}
label={`Full description (markdown accepted, max ${descLimit} characters)`} label={`Full description (markdown accepted, max ${descLimit} characters)`}
placeholder="A small testing instance for the GoToSocial alpha software. Just trying it out, my main instance is https://example.com" placeholder="A small testing instance for the GoToSocial alpha software. Just trying it out, my main instance is https://example.com"
autoCapitalize="sentences"
rows={6} rows={6}
/> />
@ -153,6 +160,7 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
field={form.terms} field={form.terms}
label={`Terms & Conditions (markdown accepted, max ${termsLimit} characters)`} label={`Terms & Conditions (markdown accepted, max ${termsLimit} characters)`}
placeholder="Terms and conditions of using this instance, data policy, imprint, GDPR stuff, yadda yadda." placeholder="Terms and conditions of using this instance, data policy, imprint, GDPR stuff, yadda yadda."
autoCapitalize="sentences"
rows={6} rows={6}
/> />
@ -172,12 +180,15 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
field={form.contactUser} field={form.contactUser}
label="Contact user (local account username)" label="Contact user (local account username)"
placeholder="admin" placeholder="admin"
autoCapitalize="none"
spellCheck="false"
/> />
<TextInput <TextInput
field={form.contactEmail} field={form.contactEmail}
label="Contact email" label="Contact email"
placeholder="admin@example.com" placeholder="admin@example.com"
type="email"
/> />
<MutationButton label="Save" result={result} disabled={false} /> <MutationButton label="Save" result={result} disabled={false} />

View file

@ -78,14 +78,21 @@ function ModerateAccount({ account }: { account: AdminAccount }) {
> >
<h3 id="account-moderation-actions">Account Moderation Actions</h3> <h3 id="account-moderation-actions">Account Moderation Actions</h3>
<div> <div>
Currently only the "suspend" action is implemented.<br/> Currently only the "suspend" action is implemented.
Suspending an account will delete it from your server, and remove all of its media, posts, relationships, etc.<br/> <br/>
If the suspended account is local, suspending will also send out a "delete" message to other servers, requesting them to remove its data from their instance as well.<br/> Suspending an account will delete it from your server,
and remove all of its media, posts, relationships, etc.
<br/>
If the suspended account is local, suspending will also
send out a "delete" message to other servers, requesting
them to remove its data from their instance as well.
<br/>
<b>Account suspension cannot be reversed.</b> <b>Account suspension cannot be reversed.</b>
</div> </div>
<TextInput <TextInput
field={form.reason} field={form.reason}
placeholder="Reason for this action" placeholder="Reason for this action"
autoCapitalize="sentences"
/> />
<div className="action-buttons"> <div className="action-buttons">
<MutationButton <MutationButton

View file

@ -23,12 +23,16 @@ import { AccountSearchForm } from "./search";
export default function AccountsSearch({ }) { export default function AccountsSearch({ }) {
return ( return (
<div className="accounts-view"> <div className="accounts-view">
<h1>Accounts Search</h1> <div className="form-section-docs">
<span> <h1>Accounts Search</h1>
You can perform actions on an account by clicking <p>
its name in a report, or by searching for the account You can perform actions on an account by clicking
using the form below and clicking on its name. its name in a report, or by searching for the account
</span> using the form below and clicking on its name.
<br/>
All fields in the below form are optional.
</p>
</div>
<AccountSearchForm /> <AccountSearchForm />
</div> </div>
); );

View file

@ -44,7 +44,23 @@ export default function AccountsPending() {
return ( return (
<div className="accounts-view"> <div className="accounts-view">
<h1>Pending Accounts</h1> <div className="form-section-docs">
<h1>Pending Accounts</h1>
<p>
You can see a list of pending account sign-ups below.
<br/>
To approve or reject a sign-up, click on the account's name in the
list, and use the controls at the bottom of the account detail view.
</p>
<a
href="https://docs.gotosocial.org/en/latest/admin/signups/"
target="_blank"
className="docslink"
rel="noreferrer"
>
Learn more about account sign-ups (opens in a new tab)
</a>
</div>
<PageableList <PageableList
isLoading={searchRes.isLoading} isLoading={searchRes.isLoading}
isFetching={searchRes.isFetching} isFetching={searchRes.isFetching}

View file

@ -27,6 +27,7 @@ import MutationButton from "../../../../components/form/mutation-button";
import { useLocation, useSearch } from "wouter"; import { useLocation, useSearch } from "wouter";
import { AdminAccount } from "../../../../lib/types/account"; import { AdminAccount } from "../../../../lib/types/account";
import Username from "../../../../components/username"; import Username from "../../../../components/username";
import isValidDomain from "is-valid-domain";
export function AccountSearchForm() { export function AccountSearchForm() {
const [ location, setLocation ] = useLocation(); const [ location, setLocation ] = useLocation();
@ -42,7 +43,31 @@ export function AccountSearchForm() {
permissions: useTextInput("permissions", { defaultValue: urlQueryParams.get("permissions") ?? ""}), permissions: useTextInput("permissions", { defaultValue: urlQueryParams.get("permissions") ?? ""}),
username: useTextInput("username", { defaultValue: urlQueryParams.get("username") ?? ""}), username: useTextInput("username", { defaultValue: urlQueryParams.get("username") ?? ""}),
display_name: useTextInput("display_name", { defaultValue: urlQueryParams.get("display_name") ?? ""}), display_name: useTextInput("display_name", { defaultValue: urlQueryParams.get("display_name") ?? ""}),
by_domain: useTextInput("by_domain", { defaultValue: urlQueryParams.get("by_domain") ?? ""}), by_domain: useTextInput("by_domain", {
defaultValue: urlQueryParams.get("by_domain") ?? "",
validator: (v: string) => {
if (v.length === 0) {
return "";
}
if (v[v.length-1] === ".") {
return "invalid domain";
}
const valid = isValidDomain(v, {
subdomain: true,
wildcard: false,
allowUnicode: true,
topLevel: false,
});
if (valid) {
return "";
}
return "invalid domain";
}
}),
email: useTextInput("email", { defaultValue: urlQueryParams.get("email") ?? ""}), email: useTextInput("email", { defaultValue: urlQueryParams.get("email") ?? ""}),
ip: useTextInput("ip", { defaultValue: urlQueryParams.get("ip") ?? ""}), ip: useTextInput("ip", { defaultValue: urlQueryParams.get("ip") ?? ""}),
limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "50"}) limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "50"})
@ -109,13 +134,17 @@ export function AccountSearchForm() {
> >
<TextInput <TextInput
field={form.username} field={form.username}
label={"(Optional) username (without leading '@' symbol)"} label={`Username (without "@" prefix) - case sensitive`}
placeholder="someone" placeholder="someone"
autoCapitalize="none"
spellCheck="false"
/> />
<TextInput <TextInput
field={form.by_domain} field={form.by_domain}
label={"(Optional) domain"} label={`Domain (without "https://" prefix)`}
placeholder="example.org" placeholder="example.org"
autoCapitalize="none"
spellCheck="false"
/> />
<Select <Select
field={form.origin} field={form.origin}
@ -130,15 +159,18 @@ export function AccountSearchForm() {
></Select> ></Select>
<TextInput <TextInput
field={form.email} field={form.email}
label={"(Optional) email address (local accounts only)"} label={"Email address (local accounts only)"}
placeholder={"someone@example.org"} placeholder={"someone@example.org"}
// Get email validation for free. // Get email validation for free.
{...{type: "email"}} type="email"
/> />
<TextInput <TextInput
field={form.ip} field={form.ip}
label={"(Optional) IP address (local accounts only)"} label={"IP address (local accounts only)"}
placeholder={"198.51.100.0"} placeholder={"198.51.100.0"}
autoCapitalize="none"
spellCheck="false"
className="monospace"
/> />
<Select <Select
field={form.status} field={form.status}

View file

@ -39,6 +39,7 @@ import { NoArg } from "../../../lib/types/query";
import { Error } from "../../../components/error"; import { Error } from "../../../components/error";
import { useBaseUrl } from "../../../lib/navigation/util"; import { useBaseUrl } from "../../../lib/navigation/util";
import { PermType } from "../../../lib/types/perm"; import { PermType } from "../../../lib/types/perm";
import isValidDomain from "is-valid-domain";
export default function DomainPermDetail() { export default function DomainPermDetail() {
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
@ -139,7 +140,32 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
}; };
const form = { const form = {
domain: useTextInput("domain", { source: perm, defaultValue: defaultDomain }), domain: useTextInput("domain", {
source: perm,
defaultValue: defaultDomain,
validator: (v: string) => {
if (v.length === 0) {
return "";
}
if (v[v.length-1] === ".") {
return "invalid domain";
}
const valid = isValidDomain(v, {
subdomain: true,
wildcard: false,
allowUnicode: true,
topLevel: false,
});
if (valid) {
return "";
}
return "invalid domain";
}
}),
obfuscate: useBoolInput("obfuscate", { source: perm }), obfuscate: useBoolInput("obfuscate", { source: perm }),
commentPrivate: useTextInput("private_comment", { source: perm }), commentPrivate: useTextInput("private_comment", { source: perm }),
commentPublic: useTextInput("public_comment", { source: perm }) commentPublic: useTextInput("public_comment", { source: perm })
@ -208,6 +234,8 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
field={form.domain} field={form.domain}
label="Domain" label="Domain"
placeholder="example.com" placeholder="example.com"
autoCapitalize="none"
spellCheck="false"
{...disabledForm} {...disabledForm}
/> />
@ -220,6 +248,7 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
<TextArea <TextArea
field={form.commentPrivate} field={form.commentPrivate}
label="Private comment" label="Private comment"
autoCapitalize="sentences"
rows={3} rows={3}
{...disabledForm} {...disabledForm}
/> />
@ -227,6 +256,7 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
<TextArea <TextArea
field={form.commentPublic} field={form.commentPublic}
label="Public comment" label="Public comment"
autoCapitalize="sentences"
rows={3} rows={3}
{...disabledForm} {...disabledForm}
/> />

View file

@ -80,9 +80,12 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp
<div className="import-export"> <div className="import-export">
<TextArea <TextArea
field={form.domains} field={form.domains}
label="Domains" label="Domains (newline-separated)"
placeholder={`google.com\nfacebook.com`} placeholder={`google.com\nfacebook.com`}
rows={8} rows={8}
autoCapitalize="none"
spellCheck="false"
className={"monospace"}
/> />
<RadioGroup <RadioGroup

View file

@ -66,9 +66,11 @@ export default function DomainPermissionsOverview() {
} }
return ( return (
<> <div className={`domain-${permType}`}>
<h1>Domain {permTypeUpper}s</h1> <div className="form-section-docs">
{ permType == "block" ? <BlockHelperText/> : <AllowHelperText/> } <h1>Domain {permTypeUpper}s</h1>
{ permType == "block" ? <BlockHelperText/> : <AllowHelperText/> }
</div>
<DomainPermsList <DomainPermsList
data={data} data={data}
permType={permType} permType={permType}
@ -77,7 +79,7 @@ export default function DomainPermissionsOverview() {
<Link to={`~${baseUrl}/import-export`}> <Link to={`~${baseUrl}/import-export`}>
Or use the bulk import/export interface Or use the bulk import/export interface
</Link> </Link>
</> </div>
); );
} }

View file

@ -213,14 +213,18 @@ function ReportActionForm({ report }) {
This is useful for providing an explanation about what action was This is useful for providing an explanation about what action was
taken (if any) before the report was marked as resolved. taken (if any) before the report was marked as resolved.
<br /> <br />
<b> <div className="info">
Any comment made here will be visible <i className="fa fa-fw fa-exclamation-triangle" aria-hidden="true"></i>
to the user that created the report! <b>
</b> If the report was created by a local account, then any
comment made here will be emailed to that account's user!
</b>
</div>
</> </>
<TextArea <TextArea
field={form.comment} field={form.comment}
label="Comment" label="Comment"
autoCapitalize="sentences"
/> />
<MutationButton <MutationButton
disabled={false} disabled={false}

View file

@ -135,6 +135,8 @@ function AlsoKnownAsURI({ index, data }) {
label={`Alias #${index+1}`} label={`Alias #${index+1}`}
field={form.alsoKnownAsURI} field={form.alsoKnownAsURI}
placeholder={`https://example.org/users/my_other_account_${index+1}`} placeholder={`https://example.org/users/my_other_account_${index+1}`}
type="url"
pattern="(http|https):\/\/.+"
/> />
); );
} }
@ -190,10 +192,13 @@ function MoveForm({ data: profile }) {
field={form.movedToURI} field={form.movedToURI}
label="Move target URI" label="Move target URI"
placeholder="https://example.org/users/my_new_account" placeholder="https://example.org/users/my_new_account"
type="url"
pattern="(http|https):\/\/.+"
/> />
<TextInput <TextInput
disabled={false} disabled={false}
type="password" type="password"
autoComplete="current-password"
name="password" name="password"
field={form.password} field={form.password}
label="Current account password" label="Current account password"

View file

@ -175,12 +175,15 @@ function UserProfileForm({ data: profile }) {
<TextInput <TextInput
field={form.displayName} field={form.displayName}
label="Display name" label="Display name"
placeholder="A GoToSocial user" placeholder="A GoToSocial User"
autoCapitalize="words"
spellCheck="false"
/> />
<TextArea <TextArea
field={form.note} field={form.note}
label="Bio" label="Bio"
placeholder="Just trying out GoToSocial, my pronouns are they/them and I like sloths." placeholder="Just trying out GoToSocial, my pronouns are they/them and I like sloths."
autoCapitalize="sentences"
rows={8} rows={8}
/> />
<b>Profile fields</b> <b>Profile fields</b>
@ -233,6 +236,8 @@ function UserProfileForm({ data: profile }) {
className="monospace" className="monospace"
rows={8} rows={8}
disabled={!instanceConfig.allowCustomCSS} disabled={!instanceConfig.allowCustomCSS}
autoCapitalize="none"
spellCheck="false"
/> />
<MutationButton <MutationButton
disabled={false} disabled={false}
@ -270,10 +275,14 @@ function Field({ index, data }) {
<TextInput <TextInput
field={form.name} field={form.name}
placeholder="Name" placeholder="Name"
autoCapitalize="none"
spellCheck="false"
/> />
<TextInput <TextInput
field={form.value} field={form.value}
placeholder="Value" placeholder="Value"
autoCapitalize="none"
spellCheck="false"
/> />
</div> </div>
); );

View file

@ -58,6 +58,8 @@
required required
placeholder="Please enter your desired username" placeholder="Please enter your desired username"
pattern="^[a-z0-9_]{1,64}$" pattern="^[a-z0-9_]{1,64}$"
autocapitalize="off"
spellcheck="false"
title="lowercase a-z, numbers, and underscores; max 64 characters" title="lowercase a-z, numbers, and underscores; max 64 characters"
> >
</div> </div>
@ -75,6 +77,7 @@
rows="8" rows="8"
minlength="40" minlength="40"
maxlength="500" maxlength="500"
autocapitalize="sentences"
title="40-500 characters" title="40-500 characters"
></textarea> ></textarea>
</div> </div>