Ask for confirmation on leaving pages with incomplete forms. Fixes #529

This commit is contained in:
Dessalines 2020-03-06 14:57:52 -05:00
parent 0708a6d665
commit a67a69f95e
6 changed files with 266 additions and 217 deletions

View file

@ -1,4 +1,5 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
import { import {
CommentNode as CommentNodeI, CommentNode as CommentNodeI,
CommentForm as CommentFormI, CommentForm as CommentFormI,
@ -87,6 +88,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
render() { render() {
return ( return (
<div class="mb-3"> <div class="mb-3">
<Prompt
when={this.state.commentForm.content}
message={i18n.t('block_leaving')}
/>
<form onSubmit={linkEvent(this, this.handleCommentSubmit)}> <form onSubmit={linkEvent(this, this.handleCommentSubmit)}>
<div class="form-group row"> <div class="form-group row">
<div className={`col-sm-12`}> <div className={`col-sm-12`}>

View file

@ -1,4 +1,5 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { import {
@ -105,120 +106,131 @@ export class CommunityForm extends Component<
render() { render() {
return ( return (
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}> <>
<div class="form-group row"> <Prompt
<label class="col-12 col-form-label" htmlFor="community-name"> when={
{i18n.t('name')} !this.state.loading &&
</label> (this.state.communityForm.name ||
<div class="col-12"> this.state.communityForm.title ||
<input this.state.communityForm.description)
type="text" }
id="community-name" message={i18n.t('block_leaving')}
class="form-control" />
value={this.state.communityForm.name} <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
onInput={linkEvent(this, this.handleCommunityNameChange)}
required
minLength={3}
maxLength={20}
pattern="[a-z0-9_]+"
title={i18n.t('community_reqs')}
/>
</div>
</div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor="community-title">
{i18n.t('title')}
</label>
<div class="col-12">
<input
type="text"
id="community-title"
value={this.state.communityForm.title}
onInput={linkEvent(this, this.handleCommunityTitleChange)}
class="form-control"
required
minLength={3}
maxLength={100}
/>
</div>
</div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor={this.id}>
{i18n.t('sidebar')}
</label>
<div class="col-12">
<textarea
id={this.id}
value={this.state.communityForm.description}
onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
class="form-control"
rows={3}
maxLength={10000}
/>
</div>
</div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor="community-category">
{i18n.t('category')}
</label>
<div class="col-12">
<select
class="form-control"
id="community-category"
value={this.state.communityForm.category_id}
onInput={linkEvent(this, this.handleCommunityCategoryChange)}
>
{this.state.categories.map(category => (
<option value={category.id}>{category.name}</option>
))}
</select>
</div>
</div>
{this.state.enable_nsfw && (
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label" htmlFor="community-name">
{i18n.t('name')}
</label>
<div class="col-12"> <div class="col-12">
<div class="form-check"> <input
<input type="text"
class="form-check-input" id="community-name"
id="community-nsfw" class="form-control"
type="checkbox" value={this.state.communityForm.name}
checked={this.state.communityForm.nsfw} onInput={linkEvent(this, this.handleCommunityNameChange)}
onChange={linkEvent(this, this.handleCommunityNsfwChange)} required
/> minLength={3}
<label class="form-check-label" htmlFor="community-nsfw"> maxLength={20}
{i18n.t('nsfw')} pattern="[a-z0-9_]+"
</label> title={i18n.t('community_reqs')}
</div> />
</div> </div>
</div> </div>
)}
<div class="form-group row"> <div class="form-group row">
<div class="col-12"> <label class="col-12 col-form-label" htmlFor="community-title">
<button type="submit" class="btn btn-secondary mr-2"> {i18n.t('title')}
{this.state.loading ? ( </label>
<svg class="icon icon-spinner spin"> <div class="col-12">
<use xlinkHref="#icon-spinner"></use> <input
</svg> type="text"
) : this.props.community ? ( id="community-title"
capitalizeFirstLetter(i18n.t('save')) value={this.state.communityForm.title}
) : ( onInput={linkEvent(this, this.handleCommunityTitleChange)}
capitalizeFirstLetter(i18n.t('create')) class="form-control"
)} required
</button> minLength={3}
{this.props.community && ( maxLength={100}
<button />
type="button" </div>
class="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)}
>
{i18n.t('cancel')}
</button>
)}
</div> </div>
</div> <div class="form-group row">
</form> <label class="col-12 col-form-label" htmlFor={this.id}>
{i18n.t('sidebar')}
</label>
<div class="col-12">
<textarea
id={this.id}
value={this.state.communityForm.description}
onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
class="form-control"
rows={3}
maxLength={10000}
/>
</div>
</div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor="community-category">
{i18n.t('category')}
</label>
<div class="col-12">
<select
class="form-control"
id="community-category"
value={this.state.communityForm.category_id}
onInput={linkEvent(this, this.handleCommunityCategoryChange)}
>
{this.state.categories.map(category => (
<option value={category.id}>{category.name}</option>
))}
</select>
</div>
</div>
{this.state.enable_nsfw && (
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input
class="form-check-input"
id="community-nsfw"
type="checkbox"
checked={this.state.communityForm.nsfw}
onChange={linkEvent(this, this.handleCommunityNsfwChange)}
/>
<label class="form-check-label" htmlFor="community-nsfw">
{i18n.t('nsfw')}
</label>
</div>
</div>
</div>
)}
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-secondary mr-2">
{this.state.loading ? (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.community ? (
capitalizeFirstLetter(i18n.t('save'))
) : (
capitalizeFirstLetter(i18n.t('create'))
)}
</button>
{this.props.community && (
<button
type="button"
class="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)}
>
{i18n.t('cancel')}
</button>
)}
</div>
</div>
</form>
</>
); );
} }

View file

@ -1,4 +1,5 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
@ -153,6 +154,15 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
render() { render() {
return ( return (
<div> <div>
<Prompt
when={
!this.state.loading &&
(this.state.postForm.name ||
this.state.postForm.url ||
this.state.postForm.body)
}
message={i18n.t('block_leaving')}
/>
<form onSubmit={linkEvent(this, this.handlePostSubmit)}> <form onSubmit={linkEvent(this, this.handlePostSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label" htmlFor="post-url"> <label class="col-sm-2 col-form-label" htmlFor="post-url">

View file

@ -1,4 +1,5 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
@ -116,6 +117,10 @@ export class PrivateMessageForm extends Component<
render() { render() {
return ( return (
<div> <div>
<Prompt
when={!this.state.loading && this.state.privateMessageForm.content}
message={i18n.t('block_leaving')}
/>
<form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}> <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
{!this.props.privateMessage && ( {!this.props.privateMessage && (
<div class="form-group row"> <div class="form-group row">

View file

@ -1,4 +1,5 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
import { Site, SiteForm as SiteFormI } from '../interfaces'; import { Site, SiteForm as SiteFormI } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { capitalizeFirstLetter, randomStr, setupTribute } from '../utils'; import { capitalizeFirstLetter, randomStr, setupTribute } from '../utils';
@ -59,123 +60,138 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
render() { render() {
return ( return (
<form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}> <>
<h5>{`${ <Prompt
this.props.site when={
? capitalizeFirstLetter(i18n.t('edit')) !this.state.loading &&
: capitalizeFirstLetter(i18n.t('name')) (this.state.siteForm.name || this.state.siteForm.description)
} ${i18n.t('your_site')}`}</h5> }
<div class="form-group row"> message={i18n.t('block_leaving')}
<label class="col-12 col-form-label" htmlFor="create-site-name"> />
{i18n.t('name')} <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
</label> <h5>{`${
<div class="col-12"> this.props.site
<input ? capitalizeFirstLetter(i18n.t('edit'))
type="text" : capitalizeFirstLetter(i18n.t('name'))
id="create-site-name" } ${i18n.t('your_site')}`}</h5>
class="form-control" <div class="form-group row">
value={this.state.siteForm.name} <label class="col-12 col-form-label" htmlFor="create-site-name">
onInput={linkEvent(this, this.handleSiteNameChange)} {i18n.t('name')}
required </label>
minLength={3} <div class="col-12">
maxLength={20}
/>
</div>
</div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor={this.id}>
{i18n.t('sidebar')}
</label>
<div class="col-12">
<textarea
id={this.id}
value={this.state.siteForm.description}
onInput={linkEvent(this, this.handleSiteDescriptionChange)}
class="form-control"
rows={3}
maxLength={10000}
/>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input <input
class="form-check-input" type="text"
id="create-site-downvotes" id="create-site-name"
type="checkbox" class="form-control"
checked={this.state.siteForm.enable_downvotes} value={this.state.siteForm.name}
onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)} onInput={linkEvent(this, this.handleSiteNameChange)}
required
minLength={3}
maxLength={20}
/> />
<label class="form-check-label" htmlFor="create-site-downvotes">
{i18n.t('enable_downvotes')}
</label>
</div> </div>
</div> </div>
</div> <div class="form-group row">
<div class="form-group row"> <label class="col-12 col-form-label" htmlFor={this.id}>
<div class="col-12"> {i18n.t('sidebar')}
<div class="form-check"> </label>
<input <div class="col-12">
class="form-check-input" <textarea
id="create-site-enable-nsfw" id={this.id}
type="checkbox" value={this.state.siteForm.description}
checked={this.state.siteForm.enable_nsfw} onInput={linkEvent(this, this.handleSiteDescriptionChange)}
onChange={linkEvent(this, this.handleSiteEnableNsfwChange)} class="form-control"
rows={3}
maxLength={10000}
/> />
<label class="form-check-label" htmlFor="create-site-enable-nsfw">
{i18n.t('enable_nsfw')}
</label>
</div> </div>
</div> </div>
</div> <div class="form-group row">
<div class="form-group row"> <div class="col-12">
<div class="col-12"> <div class="form-check">
<div class="form-check"> <input
<input class="form-check-input"
class="form-check-input" id="create-site-downvotes"
id="create-site-open-registration" type="checkbox"
type="checkbox" checked={this.state.siteForm.enable_downvotes}
checked={this.state.siteForm.open_registration} onChange={linkEvent(
onChange={linkEvent( this,
this, this.handleSiteEnableDownvotesChange
this.handleSiteOpenRegistrationChange )}
/>
<label class="form-check-label" htmlFor="create-site-downvotes">
{i18n.t('enable_downvotes')}
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input
class="form-check-input"
id="create-site-enable-nsfw"
type="checkbox"
checked={this.state.siteForm.enable_nsfw}
onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
/>
<label
class="form-check-label"
htmlFor="create-site-enable-nsfw"
>
{i18n.t('enable_nsfw')}
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input
class="form-check-input"
id="create-site-open-registration"
type="checkbox"
checked={this.state.siteForm.open_registration}
onChange={linkEvent(
this,
this.handleSiteOpenRegistrationChange
)}
/>
<label
class="form-check-label"
htmlFor="create-site-open-registration"
>
{i18n.t('open_registration')}
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-secondary mr-2">
{this.state.loading ? (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.site ? (
capitalizeFirstLetter(i18n.t('save'))
) : (
capitalizeFirstLetter(i18n.t('create'))
)} )}
/> </button>
<label {this.props.site && (
class="form-check-label" <button
htmlFor="create-site-open-registration" type="button"
> class="btn btn-secondary"
{i18n.t('open_registration')} onClick={linkEvent(this, this.handleCancel)}
</label> >
{i18n.t('cancel')}
</button>
)}
</div> </div>
</div> </div>
</div> </form>
<div class="form-group row"> </>
<div class="col-12">
<button type="submit" class="btn btn-secondary mr-2">
{this.state.loading ? (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.site ? (
capitalizeFirstLetter(i18n.t('save'))
) : (
capitalizeFirstLetter(i18n.t('create'))
)}
</button>
{this.props.site && (
<button
type="button"
class="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)}
>
{i18n.t('cancel')}
</button>
)}
</div>
</div>
</form>
); );
} }

View file

@ -245,5 +245,6 @@
"no_private_message_edit_allowed": "Not allowed to edit private message.", "no_private_message_edit_allowed": "Not allowed to edit private message.",
"couldnt_update_private_message": "Couldn't update private message.", "couldnt_update_private_message": "Couldn't update private message.",
"time": "Time", "time": "Time",
"action": "Action" "action": "Action",
"block_leaving": "Are you sure you want to leave?"
} }