Merge branch 'MDL-71674-311' of https://github.com/dcai/moodle into MOODLE_311_STABLE

This commit is contained in:
Sara Arjona 2021-09-01 11:36:41 +02:00
commit 0a9017dad3
8 changed files with 203 additions and 80 deletions

View file

@ -21,8 +21,11 @@ Feature: Atto accessibility checker
And I click on ".moodle-dialogue-focused .closebutton" "css_element" And I click on ".moodle-dialogue-focused .closebutton" "css_element"
And I select the text in the "Description" Atto editor And I select the text in the "Description" Atto editor
And I click on "Insert or edit image" "button" And I click on "Insert or edit image" "button"
And I set the field "Enter URL" to "/decorative-image.png"
And I set the field "Describe this image for someone who cannot see it" to "" And I set the field "Describe this image for someone who cannot see it" to ""
And I set the field "This image is decorative only" to "1" And I set the field "Width" to "1"
And I set the field "Height" to "1"
And I click on "This image is decorative only" "checkbox"
And I press "Save image" And I press "Save image"
And I press "Accessibility checker" And I press "Accessibility checker"
And I should see "Congratulations, no accessibility problems found!" And I should see "Congratulations, no accessibility problems found!"

View file

@ -39,6 +39,7 @@ $string['imageproperties'] = 'Image properties';
$string['presentation'] = 'This image is decorative only'; $string['presentation'] = 'This image is decorative only';
$string['pluginname'] = 'Insert or edit image'; $string['pluginname'] = 'Insert or edit image';
$string['presentationoraltrequired'] = 'An image must have a description, unless it is marked as decorative only.'; $string['presentationoraltrequired'] = 'An image must have a description, unless it is marked as decorative only.';
$string['imageurlrequired'] = 'An image must have a URL.';
$string['preview'] = 'Preview'; $string['preview'] = 'Preview';
$string['saveimage'] = 'Save image'; $string['saveimage'] = 'Save image';
$string['size'] = 'Size'; $string['size'] = 'Size';

View file

@ -47,6 +47,7 @@ function atto_image_strings_for_js() {
'height', 'height',
'presentation', 'presentation',
'presentationoraltrequired', 'presentationoraltrequired',
'imageurlrequired',
'size', 'size',
'width', 'width',
'uploading', 'uploading',

View file

@ -58,7 +58,8 @@ Feature: Add images to Atto
When I click on "Insert or edit image" "button" When I click on "Insert or edit image" "button"
Then the field "Enter URL" matches value "/nothing/here" Then the field "Enter URL" matches value "/nothing/here"
And I set the field "Describe this image for someone who cannot see it" to "Something" And I set the field "Describe this image for someone who cannot see it" to "Something"
And I set the field "Enter URL" to "" And I set the field "Width" to "1"
And I set the field "Height" to "1"
And I press "Save image" And I press "Save image"
And I set the field "Description" to "<p>Image: <img src='/nothing/again' width='123' height='456' alt='Awesome!'>.</p>" And I set the field "Description" to "<p>Image: <img src='/nothing/again' width='123' height='456' alt='Awesome!'>.</p>"
And I press "Update profile" And I press "Update profile"
@ -69,3 +70,24 @@ Feature: Add images to Atto
And the field "Width" matches value "123" And the field "Width" matches value "123"
And the field "Height" matches value "456" And the field "Height" matches value "456"
And the field "Describe this image" matches value "Awesome!" And the field "Describe this image" matches value "Awesome!"
@javascript
Scenario: Error handling when inserting an image manually
Given I log in as "admin"
And I open my profile in edit mode
And I set the field "Description" to "<p>Image: <img src='/nothing/here'>.</p>"
And I select the text in the "Description" Atto editor
When I click on "Insert or edit image" "button"
Then the field "Enter URL" matches value "/nothing/here"
And I set the field "Describe this image for someone who cannot see it" to ""
And I take focus off "Describe this image for someone who cannot see it" "field"
And I should see "An image must have a description, unless it is marked as decorative only."
And I set the field "Describe this image for someone who cannot see it" to "Something"
And I set the field "Enter URL" to ""
And I press "Save image"
And I should see "An image must have a URL."
And I set the field "Enter URL" to "/nothing/here"
And I set the field "Width" to "1"
And I set the field "Height" to "1"
And I press "Save image"
And I press "Update profile"

View file

@ -43,6 +43,7 @@ var CSS = {
INPUTSIZE: 'atto_image_size', INPUTSIZE: 'atto_image_size',
INPUTWIDTH: 'atto_image_widthentry', INPUTWIDTH: 'atto_image_widthentry',
IMAGEALTWARNING: 'atto_image_altwarning', IMAGEALTWARNING: 'atto_image_altwarning',
IMAGEURLWARNING: 'atto_image_urlwarning',
IMAGEBROWSER: 'openimagebrowser', IMAGEBROWSER: 'openimagebrowser',
IMAGEPRESENTATION: 'atto_image_presentation', IMAGEPRESENTATION: 'atto_image_presentation',
INPUTCONSTRAIN: 'atto_image_constrain', INPUTCONSTRAIN: 'atto_image_constrain',
@ -51,6 +52,10 @@ var CSS = {
IMAGEPREVIEWBOX: 'atto_image_preview_box', IMAGEPREVIEWBOX: 'atto_image_preview_box',
ALIGNSETTINGS: 'atto_image_button' ALIGNSETTINGS: 'atto_image_button'
}, },
FORMNAMES = {
URL: 'urlentry',
ALT: 'altentry'
},
SELECTORS = { SELECTORS = {
INPUTURL: '.' + CSS.INPUTURL INPUTURL: '.' + CSS.INPUTURL
}, },
@ -96,13 +101,17 @@ var CSS = {
TEMPLATE = '' + TEMPLATE = '' +
'<form class="atto_form">' + '<form class="atto_form">' +
// Add the repository browser button. // Add the repository browser button.
'<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEURLWARNING}}">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">' +
'{{get_string "imageurlrequired" component}}' +
'</label>' +
'</div>' +
'{{#if showFilepicker}}' + '{{#if showFilepicker}}' +
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<div class="input-group input-append w-100">' + '<div class="input-group input-append w-100">' +
'<input class="form-control {{CSS.INPUTURL}}" type="url" ' + '<input name="{{FORMNAMES.URL}}" class="form-control {{CSS.INPUTURL}}" type="url" ' +
'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'<span class="input-group-append">' + '<span class="input-group-append">' +
'<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' + '<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' +
@ -113,19 +122,21 @@ var CSS = {
'{{else}}' + '{{else}}' +
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<input class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' + '<input name="{{FORMNAMES.URL}}" class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' +
'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'</div>' + '</div>' +
'{{/if}}' + '{{/if}}' +
// Add the Alt box.
'<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEALTWARNING}}">' + '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEALTWARNING}}">' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">' +
'{{get_string "presentationoraltrequired" component}}' + '{{get_string "presentationoraltrequired" component}}' +
'</label>' +
'</div>' + '</div>' +
// Add the Alt box.
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
'<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' + '<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' +
'id="{{elementid}}_{{CSS.INPUTALT}}" maxlength="125"></textarea>' + 'id="{{elementid}}_{{CSS.INPUTALT}}" name="{{FORMNAMES.ALT}}" maxlength="125"></textarea>' +
// Add the character count. // Add the character count.
'<div id="the-count" class="d-flex justify-content-end small">' + '<div id="the-count" class="d-flex justify-content-end small">' +
@ -252,7 +263,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this.editor.on('paste', this._handlePaste, this); this.editor.on('paste', this._handlePaste, this);
this.editor.on('drop', this._handleDragDrop, this); this.editor.on('drop', this._handleDragDrop, this);
// e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers. // ...e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers.
this.editor.on('dragover', function(e) { this.editor.on('dragover', function(e) {
e.preventDefault(); e.preventDefault();
}, this); }, this);
@ -602,6 +613,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
content = Y.Node.create(template({ content = Y.Node.create(template({
elementid: this.get('host').get('elementid'), elementid: this.get('host').get('elementid'),
CSS: CSS, CSS: CSS,
FORMNAMES: FORMNAMES,
component: COMPONENTNAME, component: COMPONENTNAME,
showFilepicker: canShowFilepicker, showFilepicker: canShowFilepicker,
alignments: ALIGNMENTS alignments: ALIGNMENTS
@ -613,8 +625,9 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._applyImageProperties(this._form); this._applyImageProperties(this._form);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this); this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._updateWarning, this); this._form.one('.' + CSS.INPUTURL).on('change', this._hasErrorUrlField, this);
this._form.one('.' + CSS.INPUTALT).on('change', this._updateWarning, this); this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._hasErrorAltField, this);
this._form.one('.' + CSS.INPUTALT).on('blur', this._hasErrorAltField, this);
this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this); this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this);
this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true); this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true);
this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) { this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) {
@ -622,7 +635,6 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._autoAdjustSize(event); this._autoAdjustSize(event);
} }
}, this); }, this);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this); this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
if (canShowFilepicker) { if (canShowFilepicker) {
@ -1049,6 +1061,38 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
return CSS.ALIGNSETTINGS + '_' + alignment; return CSS.ALIGNSETTINGS + '_' + alignment;
}, },
_toggleVisibility: function(selector, predicate) {
var form = this._form;
var element = form.all(selector);
element.setStyle('display', predicate ? 'block' : 'none');
},
_toggleAriaInvalid: function(selectors, predicate) {
var form = this._form;
selectors.forEach(function(selector) {
var element = form.all(selector);
element.setAttribute('aria-invalid', predicate);
});
},
_hasErrorUrlField: function() {
var form = this._form;
var url = form.one('.' + CSS.INPUTURL).get('value');
var urlerror = url === '';
this._toggleVisibility('.' + CSS.IMAGEURLWARNING, urlerror);
this._toggleAriaInvalid(['.' + CSS.INPUTURL], urlerror);
return urlerror;
},
_hasErrorAltField: function() {
var form = this._form;
var alt = form.one('.' + CSS.INPUTALT).get('value');
var presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
var imagealterror = alt === '' && !presentation;
this._toggleVisibility('.' + CSS.IMAGEALTWARNING, imagealterror);
this._toggleAriaInvalid(['.' + CSS.INPUTALT, '.' + CSS.IMAGEPRESENTATION], imagealterror);
return imagealterror;
},
/** /**
* Update the alt text warning live. * Update the alt text warning live.
* *
@ -1057,23 +1101,11 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
* @private * @private
*/ */
_updateWarning: function() { _updateWarning: function() {
var form = this._form, var urlerror = this._hasErrorUrlField();
state = true, var imagealterror = this._hasErrorAltField();
alt = form.one('.' + CSS.INPUTALT).get('value'), var haserrors = urlerror || imagealterror;
presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
if (alt === '' && !presentation) {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
state = true;
} else {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
state = false;
}
this.getDialogue().centerDialogue(); this.getDialogue().centerDialogue();
return state; return haserrors;
}, },
/** /**

File diff suppressed because one or more lines are too long

View file

@ -43,6 +43,7 @@ var CSS = {
INPUTSIZE: 'atto_image_size', INPUTSIZE: 'atto_image_size',
INPUTWIDTH: 'atto_image_widthentry', INPUTWIDTH: 'atto_image_widthentry',
IMAGEALTWARNING: 'atto_image_altwarning', IMAGEALTWARNING: 'atto_image_altwarning',
IMAGEURLWARNING: 'atto_image_urlwarning',
IMAGEBROWSER: 'openimagebrowser', IMAGEBROWSER: 'openimagebrowser',
IMAGEPRESENTATION: 'atto_image_presentation', IMAGEPRESENTATION: 'atto_image_presentation',
INPUTCONSTRAIN: 'atto_image_constrain', INPUTCONSTRAIN: 'atto_image_constrain',
@ -51,6 +52,10 @@ var CSS = {
IMAGEPREVIEWBOX: 'atto_image_preview_box', IMAGEPREVIEWBOX: 'atto_image_preview_box',
ALIGNSETTINGS: 'atto_image_button' ALIGNSETTINGS: 'atto_image_button'
}, },
FORMNAMES = {
URL: 'urlentry',
ALT: 'altentry'
},
SELECTORS = { SELECTORS = {
INPUTURL: '.' + CSS.INPUTURL INPUTURL: '.' + CSS.INPUTURL
}, },
@ -96,13 +101,17 @@ var CSS = {
TEMPLATE = '' + TEMPLATE = '' +
'<form class="atto_form">' + '<form class="atto_form">' +
// Add the repository browser button. // Add the repository browser button.
'<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEURLWARNING}}">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">' +
'{{get_string "imageurlrequired" component}}' +
'</label>' +
'</div>' +
'{{#if showFilepicker}}' + '{{#if showFilepicker}}' +
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<div class="input-group input-append w-100">' + '<div class="input-group input-append w-100">' +
'<input class="form-control {{CSS.INPUTURL}}" type="url" ' + '<input name="{{FORMNAMES.URL}}" class="form-control {{CSS.INPUTURL}}" type="url" ' +
'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'<span class="input-group-append">' + '<span class="input-group-append">' +
'<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' + '<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' +
@ -113,19 +122,21 @@ var CSS = {
'{{else}}' + '{{else}}' +
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<input class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' + '<input name="{{FORMNAMES.URL}}" class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' +
'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'</div>' + '</div>' +
'{{/if}}' + '{{/if}}' +
// Add the Alt box.
'<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEALTWARNING}}">' + '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEALTWARNING}}">' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">' +
'{{get_string "presentationoraltrequired" component}}' + '{{get_string "presentationoraltrequired" component}}' +
'</label>' +
'</div>' + '</div>' +
// Add the Alt box.
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
'<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' + '<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' +
'id="{{elementid}}_{{CSS.INPUTALT}}" maxlength="125"></textarea>' + 'id="{{elementid}}_{{CSS.INPUTALT}}" name="{{FORMNAMES.ALT}}" maxlength="125"></textarea>' +
// Add the character count. // Add the character count.
'<div id="the-count" class="d-flex justify-content-end small">' + '<div id="the-count" class="d-flex justify-content-end small">' +
@ -252,7 +263,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this.editor.on('paste', this._handlePaste, this); this.editor.on('paste', this._handlePaste, this);
this.editor.on('drop', this._handleDragDrop, this); this.editor.on('drop', this._handleDragDrop, this);
// e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers. // ...e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers.
this.editor.on('dragover', function(e) { this.editor.on('dragover', function(e) {
e.preventDefault(); e.preventDefault();
}, this); }, this);
@ -602,6 +613,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
content = Y.Node.create(template({ content = Y.Node.create(template({
elementid: this.get('host').get('elementid'), elementid: this.get('host').get('elementid'),
CSS: CSS, CSS: CSS,
FORMNAMES: FORMNAMES,
component: COMPONENTNAME, component: COMPONENTNAME,
showFilepicker: canShowFilepicker, showFilepicker: canShowFilepicker,
alignments: ALIGNMENTS alignments: ALIGNMENTS
@ -613,8 +625,9 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._applyImageProperties(this._form); this._applyImageProperties(this._form);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this); this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._updateWarning, this); this._form.one('.' + CSS.INPUTURL).on('change', this._hasErrorUrlField, this);
this._form.one('.' + CSS.INPUTALT).on('change', this._updateWarning, this); this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._hasErrorAltField, this);
this._form.one('.' + CSS.INPUTALT).on('blur', this._hasErrorAltField, this);
this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this); this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this);
this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true); this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true);
this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) { this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) {
@ -622,7 +635,6 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._autoAdjustSize(event); this._autoAdjustSize(event);
} }
}, this); }, this);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this); this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
if (canShowFilepicker) { if (canShowFilepicker) {
@ -1047,6 +1059,38 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
return CSS.ALIGNSETTINGS + '_' + alignment; return CSS.ALIGNSETTINGS + '_' + alignment;
}, },
_toggleVisibility: function(selector, predicate) {
var form = this._form;
var element = form.all(selector);
element.setStyle('display', predicate ? 'block' : 'none');
},
_toggleAriaInvalid: function(selectors, predicate) {
var form = this._form;
selectors.forEach(function(selector) {
var element = form.all(selector);
element.setAttribute('aria-invalid', predicate);
});
},
_hasErrorUrlField: function() {
var form = this._form;
var url = form.one('.' + CSS.INPUTURL).get('value');
var urlerror = url === '';
this._toggleVisibility('.' + CSS.IMAGEURLWARNING, urlerror);
this._toggleAriaInvalid(['.' + CSS.INPUTURL], urlerror);
return urlerror;
},
_hasErrorAltField: function() {
var form = this._form;
var alt = form.one('.' + CSS.INPUTALT).get('value');
var presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
var imagealterror = alt === '' && !presentation;
this._toggleVisibility('.' + CSS.IMAGEALTWARNING, imagealterror);
this._toggleAriaInvalid(['.' + CSS.INPUTALT, '.' + CSS.IMAGEPRESENTATION], imagealterror);
return imagealterror;
},
/** /**
* Update the alt text warning live. * Update the alt text warning live.
* *
@ -1055,23 +1099,11 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
* @private * @private
*/ */
_updateWarning: function() { _updateWarning: function() {
var form = this._form, var urlerror = this._hasErrorUrlField();
state = true, var imagealterror = this._hasErrorAltField();
alt = form.one('.' + CSS.INPUTALT).get('value'), var haserrors = urlerror || imagealterror;
presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
if (alt === '' && !presentation) {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
state = true;
} else {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
state = false;
}
this.getDialogue().centerDialogue(); this.getDialogue().centerDialogue();
return state; return haserrors;
}, },
/** /**

View file

@ -41,6 +41,7 @@ var CSS = {
INPUTSIZE: 'atto_image_size', INPUTSIZE: 'atto_image_size',
INPUTWIDTH: 'atto_image_widthentry', INPUTWIDTH: 'atto_image_widthentry',
IMAGEALTWARNING: 'atto_image_altwarning', IMAGEALTWARNING: 'atto_image_altwarning',
IMAGEURLWARNING: 'atto_image_urlwarning',
IMAGEBROWSER: 'openimagebrowser', IMAGEBROWSER: 'openimagebrowser',
IMAGEPRESENTATION: 'atto_image_presentation', IMAGEPRESENTATION: 'atto_image_presentation',
INPUTCONSTRAIN: 'atto_image_constrain', INPUTCONSTRAIN: 'atto_image_constrain',
@ -49,6 +50,10 @@ var CSS = {
IMAGEPREVIEWBOX: 'atto_image_preview_box', IMAGEPREVIEWBOX: 'atto_image_preview_box',
ALIGNSETTINGS: 'atto_image_button' ALIGNSETTINGS: 'atto_image_button'
}, },
FORMNAMES = {
URL: 'urlentry',
ALT: 'altentry'
},
SELECTORS = { SELECTORS = {
INPUTURL: '.' + CSS.INPUTURL INPUTURL: '.' + CSS.INPUTURL
}, },
@ -94,13 +99,17 @@ var CSS = {
TEMPLATE = '' + TEMPLATE = '' +
'<form class="atto_form">' + '<form class="atto_form">' +
// Add the repository browser button. // Add the repository browser button.
'<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEURLWARNING}}">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">' +
'{{get_string "imageurlrequired" component}}' +
'</label>' +
'</div>' +
'{{#if showFilepicker}}' + '{{#if showFilepicker}}' +
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<div class="input-group input-append w-100">' + '<div class="input-group input-append w-100">' +
'<input class="form-control {{CSS.INPUTURL}}" type="url" ' + '<input name="{{FORMNAMES.URL}}" class="form-control {{CSS.INPUTURL}}" type="url" ' +
'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'<span class="input-group-append">' + '<span class="input-group-append">' +
'<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' + '<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' +
@ -111,19 +120,21 @@ var CSS = {
'{{else}}' + '{{else}}' +
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<input class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' + '<input name="{{FORMNAMES.URL}}" class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' +
'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'</div>' + '</div>' +
'{{/if}}' + '{{/if}}' +
// Add the Alt box.
'<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEALTWARNING}}">' + '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.IMAGEALTWARNING}}">' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">' +
'{{get_string "presentationoraltrequired" component}}' + '{{get_string "presentationoraltrequired" component}}' +
'</label>' +
'</div>' + '</div>' +
// Add the Alt box.
'<div class="mb-1">' + '<div class="mb-1">' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' + '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
'<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' + '<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' +
'id="{{elementid}}_{{CSS.INPUTALT}}" maxlength="125"></textarea>' + 'id="{{elementid}}_{{CSS.INPUTALT}}" name="{{FORMNAMES.ALT}}" maxlength="125"></textarea>' +
// Add the character count. // Add the character count.
'<div id="the-count" class="d-flex justify-content-end small">' + '<div id="the-count" class="d-flex justify-content-end small">' +
@ -250,7 +261,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this.editor.on('paste', this._handlePaste, this); this.editor.on('paste', this._handlePaste, this);
this.editor.on('drop', this._handleDragDrop, this); this.editor.on('drop', this._handleDragDrop, this);
// e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers. // ...e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers.
this.editor.on('dragover', function(e) { this.editor.on('dragover', function(e) {
e.preventDefault(); e.preventDefault();
}, this); }, this);
@ -600,6 +611,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
content = Y.Node.create(template({ content = Y.Node.create(template({
elementid: this.get('host').get('elementid'), elementid: this.get('host').get('elementid'),
CSS: CSS, CSS: CSS,
FORMNAMES: FORMNAMES,
component: COMPONENTNAME, component: COMPONENTNAME,
showFilepicker: canShowFilepicker, showFilepicker: canShowFilepicker,
alignments: ALIGNMENTS alignments: ALIGNMENTS
@ -611,8 +623,9 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._applyImageProperties(this._form); this._applyImageProperties(this._form);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this); this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._updateWarning, this); this._form.one('.' + CSS.INPUTURL).on('change', this._hasErrorUrlField, this);
this._form.one('.' + CSS.INPUTALT).on('change', this._updateWarning, this); this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._hasErrorAltField, this);
this._form.one('.' + CSS.INPUTALT).on('blur', this._hasErrorAltField, this);
this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this); this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this);
this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true); this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true);
this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) { this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) {
@ -620,7 +633,6 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._autoAdjustSize(event); this._autoAdjustSize(event);
} }
}, this); }, this);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this); this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
if (canShowFilepicker) { if (canShowFilepicker) {
@ -1047,6 +1059,38 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
return CSS.ALIGNSETTINGS + '_' + alignment; return CSS.ALIGNSETTINGS + '_' + alignment;
}, },
_toggleVisibility: function(selector, predicate) {
var form = this._form;
var element = form.all(selector);
element.setStyle('display', predicate ? 'block' : 'none');
},
_toggleAriaInvalid: function(selectors, predicate) {
var form = this._form;
selectors.forEach(function(selector) {
var element = form.all(selector);
element.setAttribute('aria-invalid', predicate);
});
},
_hasErrorUrlField: function() {
var form = this._form;
var url = form.one('.' + CSS.INPUTURL).get('value');
var urlerror = url === '';
this._toggleVisibility('.' + CSS.IMAGEURLWARNING, urlerror);
this._toggleAriaInvalid(['.' + CSS.INPUTURL], urlerror);
return urlerror;
},
_hasErrorAltField: function() {
var form = this._form;
var alt = form.one('.' + CSS.INPUTALT).get('value');
var presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
var imagealterror = alt === '' && !presentation;
this._toggleVisibility('.' + CSS.IMAGEALTWARNING, imagealterror);
this._toggleAriaInvalid(['.' + CSS.INPUTALT, '.' + CSS.IMAGEPRESENTATION], imagealterror);
return imagealterror;
},
/** /**
* Update the alt text warning live. * Update the alt text warning live.
* *
@ -1055,23 +1099,11 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
* @private * @private
*/ */
_updateWarning: function() { _updateWarning: function() {
var form = this._form, var urlerror = this._hasErrorUrlField();
state = true, var imagealterror = this._hasErrorAltField();
alt = form.one('.' + CSS.INPUTALT).get('value'), var haserrors = urlerror || imagealterror;
presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
if (alt === '' && !presentation) {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
state = true;
} else {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
state = false;
}
this.getDialogue().centerDialogue(); this.getDialogue().centerDialogue();
return state; return haserrors;
}, },
/** /**