From 93bd3dd8a8f587dc860c72653364d853c27bc6fd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 21 Oct 2016 13:49:09 +0200 Subject: [PATCH 1/9] Upgrade gl_field_errors to support more use cases. --- app/assets/javascripts/gl_field_errors.js.es6 | 54 ++++++++++++++++--- .../javascripts/username_validator.js.es6 | 1 - app/assets/stylesheets/framework/forms.scss | 32 +++++++++++ app/assets/stylesheets/pages/login.scss | 30 +---------- app/views/devise/shared/_signup_box.html.haml | 2 +- .../fixtures/gl_field_errors.html.haml | 2 +- spec/javascripts/gl_field_errors_spec.js.es6 | 2 +- 7 files changed, 84 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index be6c3ec274f..fa3e131c43b 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -4,18 +4,56 @@ * This class overrides the browser's validation error bubbles, displaying custom * error messages for invalid fields instead. To begin validating any form, add the * class `show-gl-field-errors` to the form element, and ensure error messages are - * declared in each inputs' title attribute. + * declared in each inputs' `title` attribute. If no title is declared for an invalid + * field the user attempts to submit, "This field is required." will be shown by default. * - * Example: + * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input. + * + * Set a custom error anchor for error message to be injected after with the class `gl-field-error-anchor` + * + * Examples: + * + * Basic: * *
* - *
+ * * + * Ignore specific inputs (e.g. UsernameValidator): + * + *
+ *
+ * + *
+ *
+ * + * Custom Error Anchor (allows error message to be injected after specified element): + * + *
+ *
+ * + * // Error message typically injected here + *
+ * // Error message now injected here + *
+ * + * */ + + /* + * Regex Patterns in use: + * + * Only alphanumeric: : "[a-zA-Z0-9]+" + * No special characters : "[a-zA-Z0-9-_]+", + * * */ const errorMessageClass = 'gl-field-error'; const inputErrorClass = 'gl-field-error-outline'; + const errorAnchorSelector = '.gl-field-error-anchor'; + const ignoreInputSelector = '.gl-field-error-ignore'; class GlFieldError { constructor({ input, formErrors }) { @@ -34,16 +72,18 @@ } initFieldValidation() { + const customErrorAnchor = this.inputElement.parents(errorAnchorSelector); + const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement; + // hidden when injected into DOM - this.inputElement.after(this.fieldErrorElement); + errorAnchor.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this)); this.scopedSiblings = this.safelySelectSiblings(); } safelySelectSiblings() { // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity - const ignoreSelector = '.validation-ignore'; - const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`); + const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`); const parentContainer = this.inputElement.parent('.form-group'); // Only select siblings when they're scoped within a form-group with one input @@ -125,7 +165,7 @@ } } - const customValidationFlag = 'no-gl-field-errors'; + const customValidationFlag = 'gl-field-error-ignore'; class GlFieldErrors { constructor(form) { diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index c4dde575c6e..023ec0838dc 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -25,7 +25,6 @@ this.inputElement.on('keyup.username_check', () => { const username = this.inputElement.val(); - this.state.valid = this.inputDomElement.validity.valid; this.state.empty = !username.length; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 761c07384f4..514d0868ba1 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -136,3 +136,35 @@ label { color: $red-normal; } +.show-gl-field-errors { + .gl-field-success-outline { + border: 1px solid $green-normal; + + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } + + .gl-field-error-outline { + border: 1px solid $red-normal; + + &:focus { + box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); + border: 0 none; + } + } + + .gl-field-success-message { + color: $green-normal; + } + + .gl-field-error-message { + color: $red-normal; + } + + .gl-field-hint { + color: $gl-text-color; + } +} + diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a2f5c6c6bd3..10f67b47998 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -75,43 +75,17 @@ .login-body { font-size: 13px; - input + p { margin-top: 5px; } - .gl-field-success-outline { - border: 1px solid $green-normal; - - &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; - border: 0 none; - } - } - - .gl-field-error-outline { - border: 1px solid $red-normal; - - &:focus { - box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); - border: 0 none; - } - } - - .username .validation-success, - .gl-field-success-message { + .username .validation-success { color: $green-normal; } - .username .validation-error, - .gl-field-error-message { + .username .validation-error { color: $red-normal; } - - .gl-field-hint { - color: $gl-text-color; - } - } } diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index d0bbcf3115e..99cde88fb08 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,7 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' + = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. %p.validation-pending.hide Checking username availability... diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml index 2526e5e33a5..3026af8856d 100644 --- a/spec/javascripts/fixtures/gl_field_errors.html.haml +++ b/spec/javascripts/fixtures/gl_field_errors.html.haml @@ -10,6 +10,6 @@ .form-group %input.hidden{ type:'hidden' } .form-group - %input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate + %input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate .form-group %input.submit{type: 'submit'} Submit diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 index 4bdd72800ea..220e8c32447 100644 --- a/spec/javascripts/gl_field_errors_spec.js.es6 +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -21,7 +21,7 @@ }); it('should ignore elements with custom error handling', function() { - const customErrorFlag = 'no-gl-field-errors'; + const customErrorFlag = 'gl-field-error-ignore'; const customErrorElem = $(`.${customErrorFlag}`); expect(customErrorElem.length).toBe(1); From a15e278e4ad8066bd99bb323b4f6d58803f9a2aa Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 21 Oct 2016 13:54:24 +0200 Subject: [PATCH 2/9] Add gl field errors to group name edit form. --- CHANGELOG.md | 45 ++++++++++++++++++++++++++ app/views/groups/edit.html.haml | 2 +- app/views/shared/_group_form.html.haml | 6 ++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba9a38c04a8..87635004c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,51 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix and improve `Sortable.highest_label_priority`. !7165 - Fixed sticky merge request tabs when sidebar is pinned. !7167 - Only remove right connector of first build of last stage. !7179 + - Backups do not fail anymore when using tar on annex and custom_hooks only. !5814 + - Adds user project membership expired event to clarify why user was removed (Callum Dryden) + - Trim leading and trailing whitespace on project_path (Linus Thiel) + - Prevent award emoji via notes for issues/MRs authored by user (barthc) + - Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO) + - Fix extra space on Build sidebar on Firefox !7060 + - Fix mobile layout issues in admin user overview page !7087 + - Fix HipChat notifications rendering (airatshigapov, eisnerd) + - Refactor Jira service to use jira-ruby gem + - Add hover to trash icon in notes !7008 (blackst0ne) + - Only show one error message for an invalid email !5905 (lycoperdon) + - Fix sidekiq stats in admin area (blackst0ne) + - Created cycle analytics bundle JavaScript file + - API: Fix booleans not recognized as such when using the `to_boolean` helper + - Removed delete branch tooltip !6954 + - Stop unauthorized users dragging on milestone page (blackst0ne) + - Restore issue boards welcome message when a project is created !6899 + - Escape ref and path for relative links !6050 (winniehell) + - Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose) + - Fix filtering of milestones with quotes in title (airatshigapov) + - Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison) + - Update mail_room and enable sentinel support to Reply By Email (!7101) + - Add task completion status in Issues and Merge Requests tabs: "X of Y tasks completed" (!6527, @gmesalazar) + - Adds JavaScript validation for group path editing field + - Simpler arguments passed to named_route on toggle_award_url helper method + - Fix typo in framework css class. !7086 (Daniel Voogsgerd) + - New issue board list dropdown stays open after adding a new list + - Fix: Backup restore doesn't clear cache + - API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh) + - Replace jquery.cookie plugin with js.cookie !7085 + - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method + - Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens + - Show full status link on MR & commit pipelines + - Fix documents and comments on Build API `scope` + - Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) + - Shortened merge request modal to let clipboard button not overlap + - In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo) + +## 8.13.2 + - Fix builds dropdown overlapping bug !7124 + - Fix applying labels for GitHub-imported MRs !7139 + - Fix importing MR comments from GitHub !7139 + - Modify GitHub importer to be retryable !7003 + - Fix and improve `Sortable.highest_label_priority` + - Fixed sticky merge request tabs when sidebar is pinned ## 8.13.1 (2016-10-25) diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index c766370d5a0..db04ab0e980 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -2,7 +2,7 @@ .panel-heading Group settings .panel-body - = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| + = form_for @group, html: { multipart: true, class: "form-horizontal show-gl-field-errors" }, authenticity_token: true do |f| = form_errors(@group) = render 'shared/group_form', f: f diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index 67072b9fc2a..ba25e09d638 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -9,11 +9,13 @@ = f.label :path, class: 'control-label' do Group path .col-sm-10 - .input-group + .input-group.gl-field-error-anchor .input-group-addon = root_url = f.text_field :path, placeholder: 'open-source', class: 'form-control', - autofocus: local_assigns[:autofocus] || false + autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+", + required: true, title: 'Please choose a group name with no special characters.' + - if @group.persisted? .alert.alert-warning.prepend-top-10 %ul From 6150d2bf9d9349d1a475a4a92894f7c089f1294e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 27 Oct 2016 15:49:07 +0200 Subject: [PATCH 3/9] Break out GlFieldError into separate file. --- app/assets/javascripts/gl_field_error.js.es6 | 164 +++++++++++++++++ app/assets/javascripts/gl_field_errors.js.es6 | 167 +----------------- 2 files changed, 165 insertions(+), 166 deletions(-) create mode 100644 app/assets/javascripts/gl_field_error.js.es6 diff --git a/app/assets/javascripts/gl_field_error.js.es6 b/app/assets/javascripts/gl_field_error.js.es6 new file mode 100644 index 00000000000..9047de645f5 --- /dev/null +++ b/app/assets/javascripts/gl_field_error.js.es6 @@ -0,0 +1,164 @@ +/* eslint-disable no-param-reassign */ +((global) => { + /* + * This class overrides the browser's validation error bubbles, displaying custom + * error messages for invalid fields instead. To begin validating any form, add the + * class `show-gl-field-errors` to the form element, and ensure error messages are + * declared in each inputs' `title` attribute. If no title is declared for an invalid + * field the user attempts to submit, "This field is required." will be shown by default. + * + * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input. + * + * Set a custom error anchor for error message to be injected after with the + * class `gl-field-error-anchor` + * + * Examples: + * + * Basic: + * + *
+ * + *
+ * + * Ignore specific inputs (e.g. UsernameValidator): + * + *
+ *
+ * + *
+ *
+ * + * Custom Error Anchor (allows error message to be injected after specified element): + * + *
+ *
+ * + * // Error message typically injected here + *
+ * // Error message now injected here + *
+ * + * */ + + /* + * Regex Patterns in use: + * + * Only alphanumeric: : "[a-zA-Z0-9]+" + * No special characters : "[a-zA-Z0-9-_]+", + * + * */ + + const errorMessageClass = 'gl-field-error'; + const inputErrorClass = 'gl-field-error-outline'; + const errorAnchorSelector = '.gl-field-error-anchor'; + const ignoreInputSelector = '.gl-field-error-ignore'; + + class GlFieldError { + constructor({ input, formErrors }) { + this.inputElement = $(input); + this.inputDomElement = this.inputElement.get(0); + this.form = formErrors; + this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; + this.fieldErrorElement = $(`

${this.errorMessage}

`); + + this.state = { + valid: false, + empty: true, + }; + + this.initFieldValidation(); + } + + initFieldValidation() { + const customErrorAnchor = this.inputElement.parents(errorAnchorSelector); + const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement; + + // hidden when injected into DOM + errorAnchor.after(this.fieldErrorElement); + this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this)); + this.scopedSiblings = this.safelySelectSiblings(); + } + + safelySelectSiblings() { + // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled + const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`); + const parentContainer = this.inputElement.parent('.form-group'); + + // Only select siblings when they're scoped within a form-group with one input + const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1; + + return safelyScoped ? unignoredSiblings : this.fieldErrorElement; + } + + renderValidity() { + this.renderClear(); + + if (this.state.valid) { + this.renderValid(); + } else if (this.state.empty) { + this.renderEmpty(); + } else if (!this.state.valid) { + this.renderInvalid(); + } + } + + handleInvalidSubmit(event) { + event.preventDefault(); + const currentValue = this.accessCurrentValue(); + this.state.valid = false; + this.state.empty = currentValue === ''; + + this.renderValidity(); + this.form.focusOnFirstInvalid.apply(this.form); + // For UX, wait til after first invalid submission to check each keyup + this.inputElement.off('keyup.field_validator') + .on('keyup.field_validator', this.updateValidity.bind(this)); + } + + /* Get or set current input value */ + accessCurrentValue(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + + getInputValidity() { + return this.inputDomElement.validity.valid; + } + + updateValidity() { + const inputVal = this.accessCurrentValue(); + this.state.empty = !inputVal.length; + this.state.valid = this.getInputValidity(); + this.renderValidity(); + } + + renderValid() { + return this.renderClear(); + } + + renderEmpty() { + return this.renderInvalid(); + } + + renderInvalid() { + this.inputElement.addClass(inputErrorClass); + this.scopedSiblings.hide(); + return this.fieldErrorElement.show(); + } + + renderClear() { + const inputVal = this.accessCurrentValue(); + if (!inputVal.split(' ').length) { + const trimmedInput = inputVal.trim(); + this.accessCurrentValue(trimmedInput); + } + this.inputElement.removeClass(inputErrorClass); + this.scopedSiblings.hide(); + this.fieldErrorElement.hide(); + } + } + + global.GlFieldError = GlFieldError; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index fa3e131c43b..c02e50f1cdf 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -1,170 +1,5 @@ /* eslint-disable */ ((global) => { - /* - * This class overrides the browser's validation error bubbles, displaying custom - * error messages for invalid fields instead. To begin validating any form, add the - * class `show-gl-field-errors` to the form element, and ensure error messages are - * declared in each inputs' `title` attribute. If no title is declared for an invalid - * field the user attempts to submit, "This field is required." will be shown by default. - * - * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input. - * - * Set a custom error anchor for error message to be injected after with the class `gl-field-error-anchor` - * - * Examples: - * - * Basic: - * - *
- * - *
- * - * Ignore specific inputs (e.g. UsernameValidator): - * - *
- *
- * - *
- *
- * - * Custom Error Anchor (allows error message to be injected after specified element): - * - *
- *
- * - * // Error message typically injected here - *
- * // Error message now injected here - *
- * - * */ - - /* - * Regex Patterns in use: - * - * Only alphanumeric: : "[a-zA-Z0-9]+" - * No special characters : "[a-zA-Z0-9-_]+", - * - * */ - - const errorMessageClass = 'gl-field-error'; - const inputErrorClass = 'gl-field-error-outline'; - const errorAnchorSelector = '.gl-field-error-anchor'; - const ignoreInputSelector = '.gl-field-error-ignore'; - - class GlFieldError { - constructor({ input, formErrors }) { - this.inputElement = $(input); - this.inputDomElement = this.inputElement.get(0); - this.form = formErrors; - this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; - this.fieldErrorElement = $(`

${ this.errorMessage }

`); - - this.state = { - valid: false, - empty: true - }; - - this.initFieldValidation(); - } - - initFieldValidation() { - const customErrorAnchor = this.inputElement.parents(errorAnchorSelector); - const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement; - - // hidden when injected into DOM - errorAnchor.after(this.fieldErrorElement); - this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this)); - this.scopedSiblings = this.safelySelectSiblings(); - } - - safelySelectSiblings() { - // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity - const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`); - const parentContainer = this.inputElement.parent('.form-group'); - - // Only select siblings when they're scoped within a form-group with one input - const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1; - - return safelyScoped ? unignoredSiblings : this.fieldErrorElement; - } - - renderValidity() { - this.renderClear(); - - if (this.state.valid) { - return this.renderValid(); - } - - if (this.state.empty) { - return this.renderEmpty(); - } - - if (!this.state.valid) { - return this.renderInvalid(); - } - - } - - handleInvalidSubmit(event) { - event.preventDefault(); - const currentValue = this.accessCurrentValue(); - this.state.valid = false; - this.state.empty = currentValue === ''; - - this.renderValidity(); - this.form.focusOnFirstInvalid.apply(this.form); - // For UX, wait til after first invalid submission to check each keyup - this.inputElement.off('keyup.field_validator') - .on('keyup.field_validator', this.updateValidity.bind(this)); - - } - - /* Get or set current input value */ - accessCurrentValue(newVal) { - return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); - } - - getInputValidity() { - return this.inputDomElement.validity.valid; - } - - updateValidity() { - const inputVal = this.accessCurrentValue(); - this.state.empty = !inputVal.length; - this.state.valid = this.getInputValidity(); - this.renderValidity(); - } - - renderValid() { - return this.renderClear(); - } - - renderEmpty() { - return this.renderInvalid(); - } - - renderInvalid() { - this.inputElement.addClass(inputErrorClass); - this.scopedSiblings.hide(); - return this.fieldErrorElement.show(); - } - - renderClear() { - const inputVal = this.accessCurrentValue(); - if (!inputVal.split(' ').length) { - const trimmedInput = inputVal.trim(); - this.accessCurrentValue(trimmedInput); - } - this.inputElement.removeClass(inputErrorClass); - this.scopedSiblings.hide(); - this.fieldErrorElement.hide(); - } - } - const customValidationFlag = 'gl-field-error-ignore'; class GlFieldErrors { @@ -184,7 +19,7 @@ this.state.inputs = this.form.find(validateSelectors).toArray() .filter((input) => !input.classList.contains(customValidationFlag)) - .map((input) => new GlFieldError({ input, formErrors: this })); + .map((input) => new global.GlFieldError({ input, formErrors: this })); this.form.on('submit', this.catchInvalidFormSubmit); } From 489c591dbb14ce71d9cce9121afe04066aab3195 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 1 Nov 2016 18:40:48 +0100 Subject: [PATCH 4/9] List gl_field_error as gl_field_errors dep. --- app/assets/javascripts/gl_field_errors.js.es6 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index c02e50f1cdf..6ce392d2a5b 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -1,4 +1,7 @@ /* eslint-disable */ + +//= require gl_field_error + ((global) => { const customValidationFlag = 'gl-field-error-ignore'; From b193531b39fb8299477af1413c1861f00d7105b8 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 1 Nov 2016 18:45:10 +0100 Subject: [PATCH 5/9] Fix changelog. --- CHANGELOG.md | 46 +--------------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87635004c78..282069752e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix documents and comments on Build API `scope` - Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) - Shortened merge request modal to let clipboard button not overlap +- Adds JavaScript validation for group path editing field - In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo) ## 8.13.3 @@ -65,51 +66,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix and improve `Sortable.highest_label_priority`. !7165 - Fixed sticky merge request tabs when sidebar is pinned. !7167 - Only remove right connector of first build of last stage. !7179 - - Backups do not fail anymore when using tar on annex and custom_hooks only. !5814 - - Adds user project membership expired event to clarify why user was removed (Callum Dryden) - - Trim leading and trailing whitespace on project_path (Linus Thiel) - - Prevent award emoji via notes for issues/MRs authored by user (barthc) - - Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO) - - Fix extra space on Build sidebar on Firefox !7060 - - Fix mobile layout issues in admin user overview page !7087 - - Fix HipChat notifications rendering (airatshigapov, eisnerd) - - Refactor Jira service to use jira-ruby gem - - Add hover to trash icon in notes !7008 (blackst0ne) - - Only show one error message for an invalid email !5905 (lycoperdon) - - Fix sidekiq stats in admin area (blackst0ne) - - Created cycle analytics bundle JavaScript file - - API: Fix booleans not recognized as such when using the `to_boolean` helper - - Removed delete branch tooltip !6954 - - Stop unauthorized users dragging on milestone page (blackst0ne) - - Restore issue boards welcome message when a project is created !6899 - - Escape ref and path for relative links !6050 (winniehell) - - Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose) - - Fix filtering of milestones with quotes in title (airatshigapov) - - Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison) - - Update mail_room and enable sentinel support to Reply By Email (!7101) - - Add task completion status in Issues and Merge Requests tabs: "X of Y tasks completed" (!6527, @gmesalazar) - - Adds JavaScript validation for group path editing field - - Simpler arguments passed to named_route on toggle_award_url helper method - - Fix typo in framework css class. !7086 (Daniel Voogsgerd) - - New issue board list dropdown stays open after adding a new list - - Fix: Backup restore doesn't clear cache - - API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh) - - Replace jquery.cookie plugin with js.cookie !7085 - - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method - - Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens - - Show full status link on MR & commit pipelines - - Fix documents and comments on Build API `scope` - - Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) - - Shortened merge request modal to let clipboard button not overlap - - In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo) - -## 8.13.2 - - Fix builds dropdown overlapping bug !7124 - - Fix applying labels for GitHub-imported MRs !7139 - - Fix importing MR comments from GitHub !7139 - - Modify GitHub importer to be retryable !7003 - - Fix and improve `Sortable.highest_label_priority` - - Fixed sticky merge request tabs when sidebar is pinned ## 8.13.1 (2016-10-25) From d939fbed0953be5e19308ec7dc7832cfd1d38160 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 2 Nov 2016 11:35:21 +0100 Subject: [PATCH 6/9] Change show-gl-field-errors to gl-show-field-errors --- app/assets/javascripts/dispatcher.js.es6 | 2 +- app/assets/javascripts/gl_field_error.js.es6 | 8 ++++---- app/assets/stylesheets/framework/forms.scss | 2 +- app/views/admin/appearances/preview.html.haml | 2 +- app/views/devise/confirmations/new.html.haml | 2 +- app/views/devise/passwords/edit.html.haml | 2 +- app/views/devise/passwords/new.html.haml | 2 +- app/views/devise/sessions/_new_base.html.haml | 2 +- app/views/devise/sessions/_new_crowd.html.haml | 2 +- app/views/devise/sessions/_new_ldap.html.haml | 2 +- app/views/devise/sessions/two_factor.html.haml | 2 +- app/views/devise/shared/_signup_box.html.haml | 2 +- app/views/devise/unlocks/new.html.haml | 2 +- app/views/groups/edit.html.haml | 2 +- spec/javascripts/fixtures/gl_field_errors.html.haml | 2 +- spec/javascripts/gl_field_errors_spec.js.es6 | 2 +- 16 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index ff8b8f6d0ae..8e4fd1f19ba 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -299,7 +299,7 @@ }; Dispatcher.prototype.initFieldErrors = function() { - $('.show-gl-field-errors').each((i, form) => { + $('.gl-show-field-errors').each((i, form) => { new gl.GlFieldErrors(form); }); }; diff --git a/app/assets/javascripts/gl_field_error.js.es6 b/app/assets/javascripts/gl_field_error.js.es6 index 9047de645f5..12ea69da165 100644 --- a/app/assets/javascripts/gl_field_error.js.es6 +++ b/app/assets/javascripts/gl_field_error.js.es6 @@ -3,7 +3,7 @@ /* * This class overrides the browser's validation error bubbles, displaying custom * error messages for invalid fields instead. To begin validating any form, add the - * class `show-gl-field-errors` to the form element, and ensure error messages are + * class `gl-show-field-errors` to the form element, and ensure error messages are * declared in each inputs' `title` attribute. If no title is declared for an invalid * field the user attempts to submit, "This field is required." will be shown by default. * @@ -16,13 +16,13 @@ * * Basic: * - *
+ * * *
* * Ignore specific inputs (e.g. UsernameValidator): * - *
+ * *
* * // Error message typically injected here diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 514d0868ba1..f0727e9688a 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -136,7 +136,7 @@ label { color: $red-normal; } -.show-gl-field-errors { +.gl-show-field-errors { .gl-field-success-outline { border: 1px solid $green-normal; diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index acbe17036f7..1af7dd5bb67 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,6 +1,6 @@ = render 'devise/shared/tab_single', tab_title: 'Sign in preview' .login-box - %form.show-gl-field-errors + %form.gl-show-field-errors .form-group = label_tag :login = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.' diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 5d25dd398d6..73e70dc63e5 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,7 +1,7 @@ = render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' .login-box .login-body - = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| + = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f| .devise-errors = devise_error_messages! .form-group diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index b518fae7c95..5e189e6dc54 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,7 +1,7 @@ = render 'devise/shared/tab_single', tab_title:'Change your password' .login-box .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f| .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 1fcfd06419a..99ce13adf74 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,7 +1,7 @@ = render 'devise/shared/tab_single', tab_title: 'Reset Password' .login-box .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f| .devise-errors = devise_error_messages! .form-group diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 5fd896f6835..21b89580818 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,4 +1,4 @@ -= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f| += form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f| %div.form-group = f.label "Username or email", for: :login = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 1d381ad7893..a6cadbcbdff 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,4 +1,4 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do .form-group = label_tag :username, 'Username or email' = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index c18bc2ac413..3ab5461f929 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,4 +1,4 @@ -= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do += form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do .form-group = label_tag :username, "#{server['label']} Username" = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index fd77cdbee2e..2cadc424668 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -7,7 +7,7 @@ .login-box .login-body - if @user.two_factor_otp_enabled? - = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| + = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) %div diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 99cde88fb08..7c68e3266e5 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,6 +1,6 @@ #register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } .login-body - = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| .devise-errors = devise_error_messages! %div.form-group diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 49b2f77111f..b2f48a4e0bf 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,7 +1,7 @@ = render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions' .login-box .login-body - = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| + = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f| .devise-errors = devise_error_messages! .form-group.append-bottom-20 diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index db04ab0e980..88eddad5ebe 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -2,7 +2,7 @@ .panel-heading Group settings .panel-body - = form_for @group, html: { multipart: true, class: "form-horizontal show-gl-field-errors" }, authenticity_token: true do |f| + = form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f| = form_errors(@group) = render 'shared/group_form', f: f diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml index 3026af8856d..69445b61367 100644 --- a/spec/javascripts/fixtures/gl_field_errors.html.haml +++ b/spec/javascripts/fixtures/gl_field_errors.html.haml @@ -1,4 +1,4 @@ -%form.show-gl-field-errors{action: 'submit', method: 'post'} +%form.gl-show-field-errors{action: 'submit', method: 'post'} .form-group %input.required-text{required: true, type: 'text'} Text .form-group diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 index 220e8c32447..0713e30e485 100644 --- a/spec/javascripts/gl_field_errors_spec.js.es6 +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -8,7 +8,7 @@ describe('GL Style Field Errors', function() { beforeEach(function() { fixture.load('gl_field_errors.html'); - const $form = this.$form = $('form.show-gl-field-errors'); + const $form = this.$form = $('form.gl-show-field-errors'); this.fieldErrors = new global.GlFieldErrors($form); }); From 4b4fde7528d9ecd4d0947c1a2879cfe4c58e720a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 2 Nov 2016 11:37:35 +0100 Subject: [PATCH 7/9] Move snake_case to camelCase. --- app/assets/javascripts/gl_field_error.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_field_error.js.es6 b/app/assets/javascripts/gl_field_error.js.es6 index 12ea69da165..f7cbecc0385 100644 --- a/app/assets/javascripts/gl_field_error.js.es6 +++ b/app/assets/javascripts/gl_field_error.js.es6 @@ -114,8 +114,8 @@ this.renderValidity(); this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup - this.inputElement.off('keyup.field_validator') - .on('keyup.field_validator', this.updateValidity.bind(this)); + this.inputElement.off('keyup.fieldValidator') + .on('keyup.fieldValidator', this.updateValidity.bind(this)); } /* Get or set current input value */ From b351b2669364dd36e27d21d4389612e6ab423410 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 2 Nov 2016 11:40:32 +0100 Subject: [PATCH 8/9] Unchange username_validator. --- app/assets/javascripts/username_validator.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index 023ec0838dc..c4dde575c6e 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -25,6 +25,7 @@ this.inputElement.on('keyup.username_check', () => { const username = this.inputElement.val(); + this.state.valid = this.inputDomElement.validity.valid; this.state.empty = !username.length; From b8b3f919fd37abddf082e0af22bc28b0c3d7c1b2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 2 Nov 2016 11:47:54 +0100 Subject: [PATCH 9/9] Initialize form validation on new group form. --- app/views/groups/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 2b8bc269e64..d19eaa6add9 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -5,7 +5,7 @@ New Group %hr -= form_for @group, html: { class: 'group-form form-horizontal' } do |f| += form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f| = form_errors(@group) = render 'shared/group_form', f: f, autofocus: true