mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Migrated <GhValidationStatusContainer>
to {{validation-status}}
modifier
no issue - moved logic from `<GhValidationStatusContainer>` to a new `validation-status` modifier - removes a usage of the `ValidationState` mixin - migrated uses of the component to a mixin - paves the way for full removal of the `ValidationState` mixin in later refactors (mixins are deprecated) - migrated `<GhFormGroup>` to a glimmer component - swapped the extend of `GhValidationStatusContainer` to usage of the `validation-status` modifier with a template-only component - updated all `<GhFormGroup>` to use the standard `class=""` instead of `@classNames=""` and `@class=""` - allows `data-test-*` attributes to be added to uses of `<FormGroup>` to help when complex components are grouped as a form input
This commit is contained in:
parent
34d99c92e0
commit
9fd87f565d
26 changed files with 233 additions and 194 deletions
|
@ -6,7 +6,7 @@
|
|||
|
||||
<div class="modal-body {{if this.authenticationError 'error'}}">
|
||||
<form id="login" class="login-form" method="post" novalidate="novalidate" {{on "submit" (perform this.reauthenticateTask)}}>
|
||||
<GhValidationStatusContainer @class="password-wrap" @errors={{this.signup.errors}} @property="password" @hasValidated={{this.signup.hasValidated}}>
|
||||
<div class="password-wrap" {{validation-status errors=this.signup.errors property="password" hasValidated=this.signup.hasValidated}}>
|
||||
<input
|
||||
type="password"
|
||||
class="gh-input password"
|
||||
|
@ -16,7 +16,7 @@
|
|||
aria-label="Your password"
|
||||
{{on "input" this.setPassword}}
|
||||
/>
|
||||
</GhValidationStatusContainer>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<GhTaskButton
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
|
||||
<div class="gh-blognav-line {{unless this.name "placeholder"}}">
|
||||
{{svg-jar "check-2"}}
|
||||
<GhValidationStatusContainer
|
||||
@tagName="span"
|
||||
@class="gh-blognav-label"
|
||||
@errors={{this.benefitItem.errors}}
|
||||
@property="name"
|
||||
@hasValidated={{this.benefitItem.hasValidated}}
|
||||
<span
|
||||
class="gh-blognav-label"
|
||||
{{validation-status errors=this.benefitItem.errors property="name" hasValidated=this.benefitItem.hasValidated}}
|
||||
>
|
||||
<GhTrimFocusInput
|
||||
@shouldFocus={{this.benefitItem.last}}
|
||||
|
@ -28,7 +25,7 @@
|
|||
@errors={{this.benefitItem.errors}}
|
||||
@property="name"
|
||||
data-test-error="benefit-label" />
|
||||
</GhValidationStatusContainer>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{#if this.benefitItem.isNew}}
|
||||
|
|
7
ghost/admin/app/components/gh-form-group.hbs
Normal file
7
ghost/admin/app/components/gh-form-group.hbs
Normal file
|
@ -0,0 +1,7 @@
|
|||
<div
|
||||
class="form-group"
|
||||
{{validation-status errors=@errors property=@property hasValidated=@hasValidated}}
|
||||
...attributes
|
||||
>
|
||||
{{yield}}
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
import ValidationStatusContainer from 'ghost-admin/components/gh-validation-status-container';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {classNames} from '@ember-decorators/component';
|
||||
|
||||
@classic
|
||||
@classNames('form-group')
|
||||
export default class GhFormGroup extends ValidationStatusContainer {}
|
|
@ -6,7 +6,7 @@
|
|||
<div class="gh-main-section-content grey">
|
||||
<div>
|
||||
<div class="gh-cp-member-email-name">
|
||||
<GhFormGroup @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="name" @classNames="max-width">
|
||||
<GhFormGroup class="max-width" @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="name">
|
||||
<label for="member-name">Name</label>
|
||||
<GhTextInput
|
||||
@id="member-name"
|
||||
|
@ -20,7 +20,7 @@
|
|||
<GhErrorMessage @errors={{this.member.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="email" @classNames="max-width">
|
||||
<GhFormGroup class="max-width" @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="email">
|
||||
<label for="member-email">Email</label>
|
||||
<GhTextInput
|
||||
@value={{this.scratchMember.email}}
|
||||
|
@ -37,7 +37,7 @@
|
|||
</GhFormGroup>
|
||||
</div>
|
||||
|
||||
<GhFormGroup @classNames="gh-member-labels">
|
||||
<GhFormGroup class="gh-member-labels">
|
||||
<label for="label-input">Labels</label>
|
||||
<GhMemberLabelInput
|
||||
@onChange={{this.setLabels}}
|
||||
|
@ -49,7 +49,7 @@
|
|||
/>
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="note" @classNames="mb0 gh-member-note">
|
||||
<GhFormGroup @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="note" class="mb0 gh-member-note">
|
||||
<label for="member-note">Note <span class="midgrey-d1 fw4">(not visible to member)</span></label>
|
||||
<GhTextarea
|
||||
@id="member-note"
|
||||
|
@ -65,7 +65,7 @@
|
|||
{{gh-count-down-characters this.scratchMember.note 500}}</p>
|
||||
</GhFormGroup>
|
||||
{{#if this.canShowSingleNewsletter}}
|
||||
<GhFormGroup @classNames="gh-members-subscribed-checkbox mb0">
|
||||
<GhFormGroup class="gh-members-subscribed-checkbox mb0">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h4 class="gh-setting-title m">Subscribed to newsletter</h4>
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
</GhFormGroup>
|
||||
|
||||
{{#if (eq this.post.visibility "tiers")}}
|
||||
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="tiers" @class="nt3">
|
||||
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="tiers" class="nt3">
|
||||
<GhPostSettingsMenu::VisibilitySegmentSelect
|
||||
@tiers={{this.post.tiers}}
|
||||
@onChange={{action "setVisibility"}}
|
||||
|
@ -109,7 +109,7 @@
|
|||
</GhFormGroup>
|
||||
|
||||
{{#unless this.session.user.isAuthorOrContributor}}
|
||||
<GhFormGroup @class="for-select" @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="authors" data-test-input="authors">
|
||||
<GhFormGroup class="for-select" @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="authors" data-test-input="authors">
|
||||
<label for="author-list">Authors</label>
|
||||
<GhPsmAuthorsInput @selectedAuthors={{this.post.authors}} @updateAuthors={{action "changeAuthors"}} @triggerId="author-list" />
|
||||
<GhErrorMessage @errors={{this.post.errors}} @property="authors" data-test-error="authors" />
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import Component from '@ember/component';
|
||||
import ValidationStateMixin from 'ghost-admin/mixins/validation-state';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {classNameBindings} from '@ember-decorators/component';
|
||||
import {computed} from '@ember/object';
|
||||
|
||||
/**
|
||||
* Handles the CSS necessary to show a specific property state. When passed a
|
||||
* DS.Errors object and a property name, if the DS.Errors object has errors for
|
||||
* the specified property, it will change the CSS to reflect the error state
|
||||
* @param {DS.Errors} errors The DS.Errors object
|
||||
* @param {string} property Name of the property
|
||||
*/
|
||||
@classic
|
||||
@classNameBindings('errorClass')
|
||||
export default class GhValidationStatusContainer extends Component.extend(ValidationStateMixin) {
|
||||
@computed('property', 'hasError', 'hasValidated.[]')
|
||||
get errorClass() {
|
||||
let hasValidated = this.hasValidated;
|
||||
let property = this.property;
|
||||
|
||||
if (hasValidated && hasValidated.includes(property)) {
|
||||
return this.hasError ? 'error' : 'success';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
{{#unless this.membersUtils.isStripeEnabled}}
|
||||
<button class="gh-btn gh-btn-link {{unless this.session.user.isAdmin "disabled"}}" type="button" {{on "click" (action "openStripeConnect")}}>Connect to Stripe</button>
|
||||
{{/unless}}
|
||||
<GhFormGroup @classNames="gh-members-subscribed-checkbox gh-portal-setting-first mb0">
|
||||
<GhFormGroup class="gh-members-subscribed-checkbox gh-portal-setting-first mb0">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="mr3">
|
||||
<h4 class="gh-portal-setting-title">Display name in signup form</h4>
|
||||
|
@ -153,7 +153,7 @@
|
|||
</button>
|
||||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded" onclick={{action "switchPreviewPage" "signup"}}>
|
||||
<GhFormGroup @classNames="gh-members-subscribed-checkbox gh-portal-setting-first mb0 b--whitegrey">
|
||||
<GhFormGroup class="gh-members-subscribed-checkbox gh-portal-setting-first mb0 b--whitegrey">
|
||||
<div class="flex justify-between items-center">
|
||||
<h4 class="gh-portal-setting-title">Show Portal button</h4>
|
||||
<div class="for-switch small">
|
||||
|
@ -175,7 +175,7 @@
|
|||
</GhFormGroup>
|
||||
{{#if this.settings.portalButton}}
|
||||
<div class="mt5">
|
||||
<GhFormGroup @classNames="space-l">
|
||||
<GhFormGroup class="space-l">
|
||||
<h4 class="gh-portal-setting-title mb1">Portal button style</h4>
|
||||
<span
|
||||
class="gh-select mt2"
|
||||
|
@ -195,7 +195,7 @@
|
|||
</span>
|
||||
</GhFormGroup>
|
||||
{{#if this.showIconSetting}}
|
||||
<GhFormGroup @classNames="space-l">
|
||||
<GhFormGroup class="space-l">
|
||||
<h4 class="gh-portal-setting-title">Icon</h4>
|
||||
<GhUploader
|
||||
@extensions={{this.iconExtensions}}
|
||||
|
@ -251,7 +251,7 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
{{#if this.showButtonTextSetting}}
|
||||
<GhFormGroup @classNames="space-l">
|
||||
<GhFormGroup class="space-l">
|
||||
<h4 class="gh-portal-setting-title">Signup button text</h4>
|
||||
|
||||
<div class="flex items-center mt2">
|
||||
|
@ -275,7 +275,7 @@
|
|||
</button>
|
||||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded" onclick={{action "switchPreviewPage" "accountHome"}}>
|
||||
<GhFormGroup @classNames="space-l mt5" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="members_support_address">
|
||||
<GhFormGroup class="space-l mt5" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="members_support_address">
|
||||
<h4 class="gh-portal-setting-title">Support email address</h4>
|
||||
<div class="mt2">
|
||||
<GhTextInput
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded">
|
||||
<div class="gh-stack">
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting">
|
||||
<GhFormGroup class="gh-stack-item gh-setting">
|
||||
<GhUploader
|
||||
@extensions={{this.imageExtensions}}
|
||||
@paramsHash={{hash purpose="image"}}
|
||||
|
@ -58,7 +58,7 @@
|
|||
</GhFormGroup>
|
||||
|
||||
{{#if this.settings.icon}}
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting">
|
||||
<GhFormGroup class="gh-stack-item gh-setting">
|
||||
<label class="modal-fullsettings-title {{unless this.settings.icon "disabled"}}">Publication icon</label>
|
||||
<div class="for-switch small {{unless this.settings.icon "disabled"}}">
|
||||
<label class="switch" for="show-header">
|
||||
|
@ -76,7 +76,7 @@
|
|||
</GhFormGroup>
|
||||
{{/if}}
|
||||
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting">
|
||||
<GhFormGroup class="gh-stack-item gh-setting">
|
||||
<label class="modal-fullsettings-title">Publication title</label>
|
||||
<div class="for-switch small">
|
||||
<label class="switch" for="show-title" data-test-toggle="showHeaderTitle">
|
||||
|
@ -91,7 +91,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting">
|
||||
<GhFormGroup class="gh-stack-item gh-setting">
|
||||
<label class="modal-fullsettings-title">Newsletter name</label>
|
||||
<div class="for-switch small">
|
||||
<label class="switch" for="show-header-name" data-test-toggle="showHeaderName">
|
||||
|
@ -119,7 +119,7 @@
|
|||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded">
|
||||
<div class="gh-stack">
|
||||
<GhFormGroup @classNames="gh-stack-item">
|
||||
<GhFormGroup class="gh-stack-item">
|
||||
<label class="modal-fullsettings-title">Newsletter title style</label>
|
||||
<div class="gh-email-design-typography-wrapper header">
|
||||
<div class="modal-fullsettings-radiogroup gh-email-design-typography" data-test-input="titleFontCategory">
|
||||
|
@ -134,7 +134,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @classNames="gh-stack-item">
|
||||
<GhFormGroup class="gh-stack-item">
|
||||
<label class="modal-fullsettings-title">Body style</label>
|
||||
<div class="gh-email-design-typography-wrapper">
|
||||
<div class="modal-fullsettings-radiogroup gh-email-design-typography" data-test-input="bodyFontCategory">
|
||||
|
@ -145,7 +145,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting">
|
||||
<GhFormGroup class="gh-stack-item gh-setting">
|
||||
<label class="modal-fullsettings-title">Feature image</label>
|
||||
<div class="for-switch small">
|
||||
<label class="switch" for="show-feature-image">
|
||||
|
@ -175,7 +175,7 @@
|
|||
<div class="gh-stack">
|
||||
|
||||
{{#if (feature "audienceFeedback")}}
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting gh-setting-extra">
|
||||
<GhFormGroup class="gh-stack-item gh-setting gh-setting-extra">
|
||||
<label for="capture-feedback" class="modal-fullsettings-title" data-test-toggle="feedbackEnabled">Ask your readers for feedback</label>
|
||||
<div class="for-switch small">
|
||||
<div class="container">
|
||||
|
@ -191,7 +191,7 @@
|
|||
</GhFormGroup>
|
||||
{{/if}}
|
||||
|
||||
<GhFormGroup @classNames="gh-stack-item">
|
||||
<GhFormGroup class="gh-stack-item">
|
||||
<label class="modal-fullsettings-title">Email footer</label>
|
||||
<KoenigBasicHtmlInput
|
||||
@name="footer"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded">
|
||||
<div class="gh-stack">
|
||||
<GhFormGroup @classNames="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="name">
|
||||
<GhFormGroup class="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="name">
|
||||
<label for="newsletter-title" class="modal-fullsettings-title">Name</label>
|
||||
<input
|
||||
id="newsletter-title"
|
||||
|
@ -23,7 +23,7 @@
|
|||
<GhErrorMessage @errors={{@newsletter.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @classNames="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="description">
|
||||
<GhFormGroup class="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="description">
|
||||
<label for="newsletter-description" class="modal-fullsettings-title">Description</label>
|
||||
<textarea
|
||||
id="newsletter-description"
|
||||
|
@ -45,7 +45,7 @@
|
|||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded">
|
||||
<div class="gh-stack">
|
||||
<GhFormGroup @classNames="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderName">
|
||||
<GhFormGroup class="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderName">
|
||||
<label for="newsletter-sender-name" class="modal-fullsettings-title">Sender name</label>
|
||||
<input
|
||||
id="newsletter-sender-name"
|
||||
|
@ -58,7 +58,7 @@
|
|||
<GhErrorMessage @errors={{@newsletter.errors}} @property="senderName" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @classNames="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderEmail">
|
||||
<GhFormGroup class="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderEmail">
|
||||
<span class="flex items-center justify-between">
|
||||
<label for="newsletter-sender-email" class="modal-fullsettings-title ml2">Sender email address</label>
|
||||
<span class="tooltip-top-left" data-tooltip="Defaults to {{full-email-address "noreply"}} if empty">{{svg-jar "info" class="w4 h4"}}</span>
|
||||
|
@ -74,7 +74,7 @@
|
|||
<GhErrorMessage @errors={{@newsletter.errors}} @property="senderEmail" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @classNames="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderReplyTo">
|
||||
<GhFormGroup class="gh-stack-item" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderReplyTo">
|
||||
<label for="newsletter-reply-to" class="modal-fullsettings-title">Reply-to email</label>
|
||||
<Inputs::Select
|
||||
id="newsletter-reply-to"
|
||||
|
@ -100,7 +100,7 @@
|
|||
{{#liquid-if isOpen}}
|
||||
<div class="modal-fullsettings-tab-expanded">
|
||||
<div class="gh-stack">
|
||||
<GhFormGroup @classNames="gh-stack-item gh-setting">
|
||||
<GhFormGroup class="gh-stack-item gh-setting">
|
||||
<label for="subscribe-on-signup" class="modal-fullsettings-title" data-test-toggle="subscribeOnSignup">Subscribe new members on signup</label>
|
||||
<div class="for-switch small">
|
||||
<div class="container">
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<GhErrorMessage @errors={{@data.newsletter.errors}} @property="description" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @classNames="flex justify-between items-start mb2">
|
||||
<GhFormGroup class="flex justify-between items-start mb2">
|
||||
<div class="mr3">
|
||||
<label for="opt-in-existing" class="modal-fullsettings-title">Opt-in existing subscribers</label>
|
||||
<p>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<label class="gh-setting-title" for="site-description">Site description</label>
|
||||
<div class="gh-setting-desc mb3">Used in your theme, meta data and search results</div>
|
||||
<div class="gh-setting-action" data-test-setting="description">
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="description" @class="description-container">
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="description" class="description-container">
|
||||
<input
|
||||
id="site-description"
|
||||
type="text"
|
||||
|
|
|
@ -6,13 +6,7 @@
|
|||
{{/unless}}
|
||||
|
||||
<div class="gh-blognav-line">
|
||||
<GhValidationStatusContainer
|
||||
@tagName="span"
|
||||
@class="gh-blognav-label"
|
||||
@errors={{this.navItem.errors}}
|
||||
@property="label"
|
||||
@hasValidated={{this.navItem.hasValidated}}
|
||||
>
|
||||
<span class="gh-blognav-label" {{validation-status errors=this.navItem.errors property="label" hasValidated=this.navItem.hasValidated}}>
|
||||
<GhTrimFocusInput
|
||||
@shouldFocus={{this.navItem.last}}
|
||||
@placeholder="Label"
|
||||
|
@ -23,14 +17,8 @@
|
|||
<GhErrorMessage
|
||||
@errors={{this.navItem.errors}}
|
||||
@property="label" data-test-error="label" />
|
||||
</GhValidationStatusContainer>
|
||||
<GhValidationStatusContainer
|
||||
@tagName="span"
|
||||
@class="gh-blognav-url"
|
||||
@errors={{this.navItem.errors}}
|
||||
@property="url"
|
||||
@hasValidated={{this.navItem.hasValidated}}
|
||||
>
|
||||
</span>
|
||||
<span class="gh-blognav-url" {{validation-status errors=this.navItem.errors property="url" hasValidated=this.navItem.hasValidated}}>
|
||||
<Settings::Navigation::NavItemUrlInput
|
||||
@baseUrl={{this.baseUrl}}
|
||||
@isNew={{this.navItem.isNew}}
|
||||
|
@ -40,7 +28,7 @@
|
|||
<GhErrorMessage
|
||||
@errors={{this.navItem.errors}}
|
||||
@property="url" data-test-error="url" />
|
||||
</GhValidationStatusContainer>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{#if this.navItem.isNew}}
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<div class="flex flex-column flex">
|
||||
<GhFormGroup>
|
||||
<div class="flex items-center">
|
||||
<GhFormGroup @class="gh-mailgun-region no-margin">
|
||||
<GhFormGroup class="gh-mailgun-region no-margin">
|
||||
<label class="fw6 f8">Mailgun region</label>
|
||||
<div class="mt1">
|
||||
<PowerSelect
|
||||
|
@ -104,7 +104,7 @@
|
|||
</PowerSelect>
|
||||
</div>
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @class="no-margin">
|
||||
<GhFormGroup class="no-margin">
|
||||
<label class="fw6 f8" for="mailgun-domain">Mailgun domain</label>
|
||||
<input
|
||||
id="mailgun-domain"
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<GhErrorMessage @errors={{@tag.errors}} @property="slug" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @class="no-margin" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="description">
|
||||
<GhFormGroup class="no-margin" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="description">
|
||||
<label for="tag-description">Description</label>
|
||||
<textarea
|
||||
id="tag-description"
|
||||
|
@ -91,7 +91,7 @@
|
|||
<p>Maximum: <b>500</b> characters. You’ve used {{gh-count-down-characters @tag.description 500}}</p>
|
||||
</GhFormGroup>
|
||||
</div>
|
||||
<GhFormGroup @class="gh-tag-image-uploader no-margin" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="featureImage">
|
||||
<GhFormGroup class="gh-tag-image-uploader no-margin" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="featureImage">
|
||||
<label for="tag-image">Tag image</label>
|
||||
<GhImageUploaderWithPreview
|
||||
@image={{@tag.featureImage}}
|
||||
|
@ -197,7 +197,7 @@
|
|||
<div class="gh-setting-content-extended">
|
||||
<div class="gh-twitter-settings">
|
||||
<div class="gh-twitter-settings-left flex-basis-1-2-m flex-basis-2-3-l">
|
||||
<GhFormGroup @class="gh-tag-image-uploader" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="twitterImage">
|
||||
<GhFormGroup class="gh-tag-image-uploader" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="twitterImage">
|
||||
<label for="twitter-image">Twitter image</label>
|
||||
<GhImageUploaderWithPreview
|
||||
@image={{@tag.twitterImage}}
|
||||
|
@ -294,7 +294,7 @@
|
|||
<div class="gh-setting-content-extended">
|
||||
<div class="gh-og-settings">
|
||||
<div class="gh-og-settings-left flex-basis-1-2-m flex-basis-2-3-l">
|
||||
<GhFormGroup @class="gh-tag-image-uploader" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="ogImage">
|
||||
<GhFormGroup class="gh-tag-image-uploader" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="ogImage">
|
||||
<label for="og-image">Facebook image</label>
|
||||
<GhImageUploaderWithPreview
|
||||
@image={{@tag.ogImage}}
|
||||
|
@ -390,7 +390,7 @@
|
|||
<div class="gh-expandable-content">
|
||||
{{#liquid-if this.codeInjectionOpen}}
|
||||
<div class="gh-main-section">
|
||||
<GhFormGroup @class="gh-main-section-block settings-code" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="codeinjectionHead">
|
||||
<GhFormGroup class="gh-main-section-block settings-code" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="codeinjectionHead">
|
||||
<label for="codeinjection-head" class="gh-tag-setting-codeheader">Tag header <code class="fw4 ml1">\{{ghost_head}}</code></label>
|
||||
<GhCmEditor
|
||||
@value={{@tag.codeinjectionHead}}
|
||||
|
@ -404,7 +404,7 @@
|
|||
<GhErrorMessage @errors={{@tag.errors}} @property="codeinjectionHead"/>
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @class="gh-main-section-block settings-code" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="codeinjectionFoot">
|
||||
<GhFormGroup class="gh-main-section-block settings-code" @errors={{@tag.errors}} @hasValidated={{@tag.hasValidated}} @property="codeinjectionFoot">
|
||||
<label for="codeinjection-foot"class="gh-tag-setting-codeheader">Tag footer <code class="fw4 ml1">\{{ghost_foot}}</code></label>
|
||||
<GhCmEditor @value={{@tag.codeinjectionFoot}}
|
||||
@id="tag-setting-codeinjection-foot"
|
||||
|
|
|
@ -6,6 +6,11 @@ import {observer} from '@ember/object';
|
|||
import {on} from '@ember/object/evented';
|
||||
import {run} from '@ember/runloop';
|
||||
|
||||
/**
|
||||
* Adds `success` or `error` classes to the element based on the passed
|
||||
* in `DS.Errors` object, the `property` to inspect, and an array of
|
||||
* validated property names in `hasValidated`
|
||||
*/
|
||||
export default Mixin.create({
|
||||
|
||||
errors: null,
|
||||
|
|
46
ghost/admin/app/modifiers/validation-status.js
Normal file
46
ghost/admin/app/modifiers/validation-status.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import Modifier from 'ember-modifier';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
|
||||
const errorClass = 'error';
|
||||
const successClass = 'success';
|
||||
|
||||
export default class ValidationStatusModifier extends Modifier {
|
||||
modify(element, positional, {errors, property, hasValidated}) {
|
||||
const validationClass = this.errorClass(errors, property, hasValidated);
|
||||
|
||||
element.classList.remove(errorClass);
|
||||
element.classList.remove(successClass);
|
||||
|
||||
if (validationClass) {
|
||||
element.classList.add(validationClass);
|
||||
}
|
||||
}
|
||||
|
||||
errorClass(errors, property, hasValidated) {
|
||||
const hasError = this.hasError(errors, property, hasValidated);
|
||||
|
||||
if (hasValidated && hasValidated.includes(property)) {
|
||||
return hasError ? errorClass : successClass;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
hasError(errors, property, hasValidated) {
|
||||
// if we aren't looking at a specific property we always want an error class
|
||||
if (!property && errors && !errors.get('isEmpty')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we haven't yet validated this field, there is no validation class needed
|
||||
if (!hasValidated || !hasValidated.includes(property)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errors && !isEmpty(errors.errorsFor(property))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -271,7 +271,7 @@
|
|||
<GhErrorMessage @errors={{this.offer.errors}} @property="displayDescription" />
|
||||
</span>
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @errors={{this.errors}} @property="url" @class="gh-offer-url" class="no-margin">
|
||||
<GhFormGroup @errors={{this.errors}} @property="url" class="gh-offer-url no-margin">
|
||||
<label for="url" class="fw6">URL</label>
|
||||
<div class="gh-input-group">
|
||||
<GhTextInput
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<p>The name of your site</p>
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="description" @class="description-container">
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="description" class="description-container">
|
||||
<GhTextInput
|
||||
@value={{readonly this.settings.description}}
|
||||
@input={{action (mut this.settings.description) value="target.value"}}
|
||||
|
@ -419,7 +419,7 @@
|
|||
A private RSS feed is available at
|
||||
<a href="{{this.privateRSSUrl}}" target="_blank" rel="noopener noreferrer">{{this.privateRSSUrl}}</a>
|
||||
</span>
|
||||
<GhFormGroup @class="no-margin pt2" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="password">
|
||||
<GhFormGroup class="no-margin pt2" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="password">
|
||||
<GhTextInput
|
||||
@value={{readonly this.settings.password}}
|
||||
@name="general[password]"
|
||||
|
|
|
@ -64,11 +64,13 @@
|
|||
</figure>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<GhValidationStatusContainer
|
||||
@class="flex flex-column w-100 mr3"
|
||||
@errors={{this.integration.errors}}
|
||||
@hasValidated={{this.integration.hasValidated}}
|
||||
@property="name"
|
||||
<div
|
||||
class="flex flex-column w-100 mr3"
|
||||
{{validation-status
|
||||
errors=this.integration.errors
|
||||
hasValidated=this.integration.hasValidated
|
||||
property="name"
|
||||
}}
|
||||
>
|
||||
<label for="integration_name">Name</label>
|
||||
<GhTextInput
|
||||
|
@ -81,13 +83,15 @@
|
|||
data-test-input="name"
|
||||
/>
|
||||
<GhErrorMessage @errors={{this.integration.errors}} @property="name" data-test-error="name" class="ma0" />
|
||||
</GhValidationStatusContainer>
|
||||
</div>
|
||||
|
||||
<GhValidationStatusContainer
|
||||
@class="flex flex-column w-100 mr3"
|
||||
@errors={{this.integration.errors}}
|
||||
@hasValidated={{this.integration.hasValidated}}
|
||||
@property="description"
|
||||
<div
|
||||
class="flex flex-column w-100 mr3"
|
||||
{{validation-status
|
||||
errors=this.integration.errors
|
||||
hasValidated=this.integration.hasValidated
|
||||
property="description"
|
||||
}}
|
||||
>
|
||||
<label for="integration_description" class="mt3">Description</label>
|
||||
<GhTextInput
|
||||
|
@ -100,7 +104,7 @@
|
|||
data-test-input="description"
|
||||
/>
|
||||
<GhErrorMessage @errors={{this.integration.errors}} @property="description" data-test-error="description" class="ma0" />
|
||||
</GhValidationStatusContainer>
|
||||
</div>
|
||||
|
||||
<table class="app-custom-api-table list" style="table-layout: fixed">
|
||||
<tbody>
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<div class="gh-setting-title">Google Analytics Tracking ID</div>
|
||||
<div class="gh-setting-desc">Tracks AMP traffic in Google Analytics, find your ID <a href="https://ghost.org/help/how-to-find-your-google-analytics-tracking-id/">here</a></div>
|
||||
<div class="gh-setting-content-extended">
|
||||
<GhFormGroup @class="no-margin" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="ampGtagId">
|
||||
<GhFormGroup class="no-margin" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="ampGtagId">
|
||||
<GhTextInput
|
||||
@placeholder="UA-XXXXXXX-X"
|
||||
@name="amp_gtag_id"
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<div class="gh-setting-title">FirstPromoter Account ID</div>
|
||||
<div class="gh-setting-desc"> Affiliate and referral tracking, find your ID <a href="https://ghost.org/help/firstpromoter-id/">here</a></div>
|
||||
<div class="gh-setting-content-extended">
|
||||
<GhFormGroup @class="no-margin" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="firstpromoterId">
|
||||
<GhFormGroup class="no-margin" @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="firstpromoterId">
|
||||
<GhTextInput
|
||||
@placeholder="XXXXXXXX"
|
||||
@name="firstpromoter_id"
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
<div class="pa5">
|
||||
<fieldset class="user-details-bottom">
|
||||
|
||||
<GhFormGroup @errors={{this.user.errors}} @hasValidated={{this.user.hasValidated}} @property="name" @class="first-form-group">
|
||||
<GhFormGroup @errors={{this.user.errors}} @hasValidated={{this.user.hasValidated}} @property="name" class="first-form-group">
|
||||
<label for="user-name">Full name</label>
|
||||
<input
|
||||
type="text"
|
||||
|
@ -226,7 +226,7 @@
|
|||
<p>URL of your personal Twitter profile</p>
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @errors={{this.user.errors}} @hasValidated={{this.user.hasValidated}} @property="bio" @class="bio-container">
|
||||
<GhFormGroup @errors={{this.user.errors}} @hasValidated={{this.user.hasValidated}} @property="bio" class="bio-container">
|
||||
<label for="user-bio">Bio</label>
|
||||
<textarea
|
||||
id="user-bio"
|
||||
|
|
100
ghost/admin/tests/integration/components/gh-form-group-test.js
Normal file
100
ghost/admin/tests/integration/components/gh-form-group-test.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
import DS from 'ember-data'; // eslint-disable-line
|
||||
import EmberObject from '@ember/object';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import {expect} from 'chai';
|
||||
import {find, render} from '@ember/test-helpers';
|
||||
import {settled} from '@ember/test-helpers';
|
||||
import {setupRenderingTest} from 'ember-mocha';
|
||||
|
||||
const {Errors} = DS;
|
||||
|
||||
describe('Integration: Component: gh-form-group', function () {
|
||||
setupRenderingTest();
|
||||
|
||||
beforeEach(function () {
|
||||
let testObject = EmberObject.create();
|
||||
testObject.name = 'Test';
|
||||
testObject.hasValidated = [];
|
||||
testObject.errors = Errors.create();
|
||||
|
||||
this.set('testObject', testObject);
|
||||
});
|
||||
|
||||
// NOTE: primarily testing the validation-status modifier
|
||||
|
||||
it('has no success/error class by default', async function () {
|
||||
await render(hbs`
|
||||
<GhFormGroup
|
||||
@property="test"
|
||||
@errors={{this.testObject.errors}}
|
||||
@hasValidated={{this.testObject.hasValidated}}
|
||||
>Testing</GhFormGroup>
|
||||
`);
|
||||
|
||||
expect(find('.form-group')).to.exist;
|
||||
expect(find('.form-group')).to.not.have.class('success');
|
||||
expect(find('.form-group')).to.not.have.class('error');
|
||||
});
|
||||
|
||||
it('has success class when valid', async function () {
|
||||
await render(hbs`
|
||||
<GhFormGroup
|
||||
@property="name"
|
||||
@errors={{this.testObject.errors}}
|
||||
@hasValidated={{this.testObject.hasValidated}}
|
||||
>Testing</GhFormGroup>
|
||||
`);
|
||||
|
||||
this.testObject.hasValidated.pushObject('name'); // pushObject vs push because this is an EmberArray and we're testing tracking
|
||||
await settled();
|
||||
|
||||
expect(find('.form-group')).to.have.class('success');
|
||||
expect(find('.form-group')).to.not.have.class('error');
|
||||
});
|
||||
|
||||
it('has error class when invalid', async function () {
|
||||
await render(hbs`
|
||||
<GhFormGroup
|
||||
@property="name"
|
||||
@errors={{this.testObject.errors}}
|
||||
@hasValidated={{this.testObject.hasValidated}}
|
||||
>Testing</GhFormGroup>
|
||||
`);
|
||||
|
||||
this.testObject.hasValidated.pushObject('name'); // pushObject vs push because this is an EmberArray and we're testing tracking
|
||||
this.testObject.errors.add('name', 'has error');
|
||||
await settled();
|
||||
|
||||
expect(find('.form-group')).to.have.class('error');
|
||||
expect(find('.form-group')).to.not.have.class('success');
|
||||
});
|
||||
|
||||
it('still renders if hasValidated is undefined', async function () {
|
||||
delete this.testObject.hasValidated;
|
||||
|
||||
await render(hbs`
|
||||
<GhFormGroup
|
||||
@property="name"
|
||||
@errors={{this.testObject.errors}}
|
||||
@hasValidated={{this.testObject.hasValidated}}
|
||||
>Testing</GhFormGroup>
|
||||
`);
|
||||
|
||||
expect(find('.form-group')).to.exist;
|
||||
});
|
||||
|
||||
it('passes element attributes through', async function () {
|
||||
await render(hbs`
|
||||
<GhFormGroup
|
||||
class="custom"
|
||||
@property="name"
|
||||
@errors={{this.testObject.errors}}
|
||||
@hasValidated={{this.testObject.hasValidated}}
|
||||
data-test-exists="true"
|
||||
>Testing</GhFormGroup>
|
||||
`);
|
||||
|
||||
expect(find('.form-group')).to.have.class('custom');
|
||||
expect(find('[data-test-exists="true"]')).to.exist;
|
||||
});
|
||||
});
|
|
@ -1,73 +0,0 @@
|
|||
// TODO: remove usage of Ember Data's private `Errors` class when refactoring validations
|
||||
// eslint-disable-next-line
|
||||
import DS from 'ember-data';
|
||||
import EmberObject from '@ember/object';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import {describe, it} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import {find, render} from '@ember/test-helpers';
|
||||
import {setupRenderingTest} from 'ember-mocha';
|
||||
|
||||
const {Errors} = DS;
|
||||
|
||||
describe('Integration: Component: gh-validation-status-container', function () {
|
||||
setupRenderingTest();
|
||||
|
||||
beforeEach(function () {
|
||||
let testObject = EmberObject.create();
|
||||
testObject.set('name', 'Test');
|
||||
testObject.set('hasValidated', []);
|
||||
testObject.set('errors', Errors.create());
|
||||
|
||||
this.set('testObject', testObject);
|
||||
});
|
||||
|
||||
it('has no success/error class by default', async function () {
|
||||
await render(hbs`
|
||||
{{#gh-validation-status-container class="gh-test" property="name" errors=testObject.errors hasValidated=testObject.hasValidated}}
|
||||
{{/gh-validation-status-container}}
|
||||
`);
|
||||
|
||||
expect(find('.gh-test')).to.exist;
|
||||
expect(find('.gh-test')).to.not.have.class('success');
|
||||
expect(find('.gh-test')).to.not.have.class('error');
|
||||
});
|
||||
|
||||
it('has success class when valid', async function () {
|
||||
this.get('testObject.hasValidated').push('name');
|
||||
|
||||
await render(hbs`
|
||||
{{#gh-validation-status-container class="gh-test" property="name" errors=testObject.errors hasValidated=testObject.hasValidated}}
|
||||
{{/gh-validation-status-container}}
|
||||
`);
|
||||
|
||||
expect(find('.gh-test')).to.exist;
|
||||
expect(find('.gh-test')).to.have.class('success');
|
||||
expect(find('.gh-test')).to.not.have.class('error');
|
||||
});
|
||||
|
||||
it('has error class when invalid', async function () {
|
||||
this.get('testObject.hasValidated').push('name');
|
||||
this.get('testObject.errors').add('name', 'has error');
|
||||
|
||||
await render(hbs`
|
||||
{{#gh-validation-status-container class="gh-test" property="name" errors=testObject.errors hasValidated=testObject.hasValidated}}
|
||||
{{/gh-validation-status-container}}
|
||||
`);
|
||||
|
||||
expect(find('.gh-test')).to.exist;
|
||||
expect(find('.gh-test')).to.not.have.class('success');
|
||||
expect(find('.gh-test')).to.have.class('error');
|
||||
});
|
||||
|
||||
it('still renders if hasValidated is undefined', async function () {
|
||||
this.set('testObject.hasValidated', undefined);
|
||||
|
||||
await render(hbs`
|
||||
{{#gh-validation-status-container class="gh-test" property="name" errors=testObject.errors hasValidated=testObject.hasValidated}}
|
||||
{{/gh-validation-status-container}}
|
||||
`);
|
||||
|
||||
expect(find('.gh-test')).to.exist;
|
||||
});
|
||||
});
|
|
@ -18,10 +18,10 @@ describe('Integration: Component: settings/navigation/nav-item', function () {
|
|||
await render(hbs`<Settings::Navigation::NavItem @navItem={{this.navItem}} @baseUrl={{this.baseUrl}} />`);
|
||||
let item = find('.gh-blognav-item');
|
||||
|
||||
expect(item.querySelector('.gh-blognav-grab')).to.exist;
|
||||
expect(item.querySelector('.gh-blognav-label')).to.exist;
|
||||
expect(item.querySelector('.gh-blognav-url')).to.exist;
|
||||
expect(item.querySelector('.gh-blognav-delete')).to.exist;
|
||||
expect(item.querySelector('.gh-blognav-grab'), 'grab').to.exist;
|
||||
expect(item.querySelector('.gh-blognav-label'), 'label').to.exist;
|
||||
expect(item.querySelector('.gh-blognav-url'), 'url').to.exist;
|
||||
expect(item.querySelector('.gh-blognav-delete'), 'delete').to.exist;
|
||||
|
||||
// doesn't show any errors
|
||||
expect(find('.gh-blognav-item--error')).to.not.exist;
|
||||
|
|
Loading…
Add table
Reference in a new issue