1 YUI.add('moodle-atto_image-button', function (Y, NAME) {
3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
20 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 * @module moodle-atto_image_alignment-button
29 * Atto image selection tool.
31 * @namespace M.atto_image
33 * @extends M.editor_atto.EditorPlugin
37 RESPONSIVE: 'img-responsive',
38 INPUTALIGNMENT: 'atto_image_alignment',
39 INPUTALT: 'atto_image_altentry',
40 INPUTHEIGHT: 'atto_image_heightentry',
41 INPUTSUBMIT: 'atto_image_urlentrysubmit',
42 INPUTURL: 'atto_image_urlentry',
43 INPUTSIZE: 'atto_image_size',
44 INPUTWIDTH: 'atto_image_widthentry',
45 IMAGEALTWARNING: 'atto_image_altwarning',
46 IMAGEBROWSER: 'openimagebrowser',
47 IMAGEPRESENTATION: 'atto_image_presentation',
48 INPUTCONSTRAIN: 'atto_image_constrain',
49 INPUTCUSTOMSTYLE: 'atto_image_customstyle',
50 IMAGEPREVIEW: 'atto_image_preview',
51 IMAGEPREVIEWBOX: 'atto_image_preview_box',
52 ALIGNSETTINGS: 'atto_image_button'
55 INPUTURL: '.' + CSS.INPUTURL
58 // Vertical alignment.
60 name: 'verticalAlign',
65 name: 'verticalAlign',
66 str: 'alignment_middle',
70 name: 'verticalAlign',
71 str: 'alignment_bottom',
80 str: 'alignment_left',
85 str: 'alignment_right',
95 COMPONENTNAME = 'atto_image',
98 '<form class="atto_form">' +
100 // Add the repository browser button.
101 '{{#if showFilepicker}}' +
102 '<div class="m-b-1">' +
103 '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
104 '<div class="input-group input-append w-100">' +
105 '<input class="form-control {{CSS.INPUTURL}}" type="url" ' +
106 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
107 '<span class="input-group-append">' +
108 '<button class="btn btn-secondary {{CSS.IMAGEBROWSER}}" type="button">' +
109 '{{get_string "browserepositories" component}}</button>' +
114 '<div class="m-b-1">' +
115 '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
116 '<input class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' +
117 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
122 '<div style="display:none" role="alert" class="alert alert-warning m-b-1 {{CSS.IMAGEALTWARNING}}">' +
123 '{{get_string "presentationoraltrequired" component}}' +
125 '<div class="m-b-1">' +
126 '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
127 '<input class="form-control fullwidth {{CSS.INPUTALT}}" type="text" value="" ' +
128 'id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' +
130 // Add the presentation select box.
131 '<div class="form-check">' +
132 '<input type="checkbox" class="form-check-input {{CSS.IMAGEPRESENTATION}}" ' +
133 'id="{{elementid}}_{{CSS.IMAGEPRESENTATION}}"/>' +
134 '<label class="form-check-label" for="{{elementid}}_{{CSS.IMAGEPRESENTATION}}">' +
135 '{{get_string "presentation" component}}' +
140 // Add the size entry boxes.
141 '<div class="m-b-1">' +
142 '<label class="" for="{{elementid}}_{{CSS.INPUTSIZE}}">{{get_string "size" component}}</label>' +
143 '<div id="{{elementid}}_{{CSS.INPUTSIZE}}" class="form-inline {{CSS.INPUTSIZE}}">' +
144 '<label class="accesshide" for="{{elementid}}_{{CSS.INPUTWIDTH}}">{{get_string "width" component}}</label>' +
145 '<input type="text" class="form-control m-r-1 input-mini {{CSS.INPUTWIDTH}}" ' +
146 'id="{{elementid}}_{{CSS.INPUTWIDTH}}" size="4"/> x' +
148 // Add the height entry box.
149 '<label class="accesshide" for="{{elementid}}_{{CSS.INPUTHEIGHT}}">{{get_string "height" component}}</label>' +
150 '<input type="text" class="form-control m-l-1 input-mini {{CSS.INPUTHEIGHT}}" ' +
151 'id="{{elementid}}_{{CSS.INPUTHEIGHT}}" size="4"/>' +
153 // Add the constrain checkbox.
154 '<div class="form-check m-l-2">' +
155 '<input type="checkbox" class="form-check-input {{CSS.INPUTCONSTRAIN}}" ' +
156 'id="{{elementid}}_{{CSS.INPUTCONSTRAIN}}"/>' +
157 '<label class="form-check-label" for="{{elementid}}_{{CSS.INPUTCONSTRAIN}}">' +
158 '{{get_string "constrain" component}}</label>' +
163 // Add the alignment selector.
164 '<div class="form-inline m-b-1">' +
165 '<label class="for="{{elementid}}_{{CSS.INPUTALIGNMENT}}">{{get_string "alignment" component}}</label>' +
166 '<select class="custom-select {{CSS.INPUTALIGNMENT}}" id="{{elementid}}_{{CSS.INPUTALIGNMENT}}">' +
167 '{{#each alignments}}' +
168 '<option value="{{value}}">{{get_string str ../component}}</option>' +
172 // Hidden input to store custom styles.
173 '<input type="hidden" class="{{CSS.INPUTCUSTOMSTYLE}}"/>' +
176 // Add the image preview.
177 '<div class="mdl-align">' +
178 '<div class="{{CSS.IMAGEPREVIEWBOX}}">' +
179 '<img src="#" class="{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' +
182 // Add the submit button and close the form.
183 '<button class="btn btn-secondary {{CSS.INPUTSUBMIT}}" type="submit">' + '' +
184 '{{get_string "saveimage" component}}</button>' +
189 '<img src="{{url}}" alt="{{alt}}" ' +
190 '{{#if width}}width="{{width}}" {{/if}}' +
191 '{{#if height}}height="{{height}}" {{/if}}' +
192 '{{#if presentation}}role="presentation" {{/if}}' +
193 '{{#if customstyle}}style="{{customstyle}}" {{/if}}' +
194 '{{#if classlist}}class="{{classlist}}" {{/if}}' +
195 '{{#if id}}id="{{id}}" {{/if}}' +
198 Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
200 * A reference to the current selection at the time that the dialogue
203 * @property _currentSelection
207 _currentSelection: null,
210 * The most recently selected image.
212 * @param _selectedImage
216 _selectedImage: null,
219 * A reference to the currently open form.
228 * The dimensions of the raw image before we manipulate it.
230 * @param _rawImageDimensions
234 _rawImageDimensions: null,
236 initializer: function() {
239 icon: 'e/insert_edit_image',
240 callback: this._displayDialogue,
242 tagMatchRequiresAll: false
244 this.editor.delegate('dblclick', this._displayDialogue, 'img', this);
245 this.editor.delegate('click', this._handleClick, 'img', this);
246 this.editor.on('drop', this._handleDragDrop, this);
248 // e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers.
249 this.editor.on('dragover', function(e) {
252 this.editor.on('dragenter', function(e) {
258 * Handle a drag and drop event with an image.
260 * @method _handleDragDrop
261 * @param {EventFacade} e
265 _handleDragDrop: function(e) {
268 host = this.get('host'),
269 template = Y.Handlebars.compile(IMAGETEMPLATE);
271 host.saveSelection();
274 // Only handle the event if an image file was dropped in.
275 var handlesDataTransfer = (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length);
276 if (handlesDataTransfer && /^image\//.test(e.dataTransfer.files[0].type)) {
278 var options = host.get('filepickeroptions').image,
279 savepath = (options.savepath === undefined) ? '/' : options.savepath,
280 formData = new FormData(),
283 xhr = new XMLHttpRequest(),
285 keys = Object.keys(options.repositories);
289 formData.append('repo_upload_file', e.dataTransfer.files[0]);
290 formData.append('itemid', options.itemid);
292 // List of repositories is an object rather than an array. This makes iteration more awkward.
293 for (var i = 0; i < keys.length; i++) {
294 if (options.repositories[keys[i]].type === 'upload') {
295 formData.append('repo_id', options.repositories[keys[i]].id);
299 formData.append('env', options.env);
300 formData.append('sesskey', M.cfg.sesskey);
301 formData.append('client_id', options.client_id);
302 formData.append('savepath', savepath);
303 formData.append('ctx_id', options.context.id);
305 // Insert spinner as a placeholder.
306 timestamp = new Date().getTime();
307 uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp;
309 host.restoreSelection();
310 imagehtml = template({
311 url: M.util.image_url("i/loading_small", 'moodle'),
312 alt: M.util.get_string('uploading', COMPONENTNAME),
315 host.insertContentAtFocusPoint(imagehtml);
318 // Kick off a XMLHttpRequest.
319 xhr.onreadystatechange = function() {
320 var placeholder = self.editor.one('#' + uploadid),
326 if (xhr.readyState === 4) {
327 if (xhr.status === 200) {
328 result = JSON.parse(xhr.responseText);
332 placeholder.remove(true);
334 return new M.core.ajaxException(result);
338 if (result.event && result.event === 'fileexists') {
339 // A file with this name is already in use here - rename to avoid conflict.
340 // Chances are, it's a different image (stored in a different folder on the user's computer).
341 // If the user wants to reuse an existing image, they can copy/paste it within the editor.
342 file = result.newfile;
345 // Replace placeholder with actual image.
350 newimage = Y.Node.create(newhtml);
352 placeholder.replace(newimage);
354 self.editor.appendChild(newimage);
359 Y.use('moodle-core-notification-alert', function() {
360 new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
363 placeholder.remove(true);
368 xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
376 * Handle a click on an image.
378 * @method _handleClick
379 * @param {EventFacade} e
382 _handleClick: function(e) {
383 var image = e.target;
385 var selection = this.get('host').getSelectionFromNode(image);
386 if (this.get('host').getSelection() !== selection) {
387 this.get('host').setSelection(selection);
392 * Display the image editing tool.
394 * @method _displayDialogue
397 _displayDialogue: function() {
398 // Store the current selection.
399 this._currentSelection = this.get('host').getSelection();
400 if (this._currentSelection === false) {
404 // Reset the image dimensions.
405 this._rawImageDimensions = null;
407 var dialogue = this.getDialogue({
408 headerContent: M.util.get_string('imageproperties', COMPONENTNAME),
410 focusAfterHide: true,
411 focusOnShowSelector: SELECTORS.INPUTURL
414 // Set the dialogue content, and then show the dialogue.
415 dialogue.set('bodyContent', this._getDialogueContent())
420 * Set the inputs for width and height if they are not set, and calculate
421 * if the constrain checkbox should be checked or not.
423 * @method _loadPreviewImage
424 * @param {String} url
427 _loadPreviewImage: function(url) {
428 var image = new Image();
431 image.onerror = function() {
432 var preview = self._form.one('.' + CSS.IMAGEPREVIEW);
437 // Centre the dialogue when clearing the image preview.
438 self.getDialogue().centerDialogue();
441 image.onload = function() {
442 var input, currentwidth, currentheight, widthRatio, heightRatio;
444 self._rawImageDimensions = {
449 input = self._form.one('.' + CSS.INPUTWIDTH);
450 currentwidth = input.get('value');
451 if (currentwidth === '') {
452 input.set('value', this.width);
453 currentwidth = "" + this.width;
455 input = self._form.one('.' + CSS.INPUTHEIGHT);
456 currentheight = input.get('value');
457 if (currentheight === '') {
458 input.set('value', this.height);
459 currentheight = "" + this.height;
461 input = self._form.one('.' + CSS.IMAGEPREVIEW);
462 input.setAttribute('src', this.src);
467 input = self._form.one('.' + CSS.INPUTCONSTRAIN);
468 if (currentwidth.match(REGEX.ISPERCENT) && currentheight.match(REGEX.ISPERCENT)) {
469 input.set('checked', currentwidth === currentheight);
471 if (this.width === 0) {
474 if (this.height === 0) {
477 // This is the same as comparing to 3 decimal places.
478 widthRatio = Math.round(1000 * parseInt(currentwidth, 10) / this.width);
479 heightRatio = Math.round(1000 * parseInt(currentheight, 10) / this.height);
480 input.set('checked', widthRatio === heightRatio);
483 // Apply the image sizing.
484 self._autoAdjustSize(self);
486 // Centre the dialogue once the preview image has loaded.
487 self.getDialogue().centerDialogue();
494 * Return the dialogue content for the tool, attaching any required
497 * @method _getDialogueContent
498 * @return {Node} The content to place in the dialogue.
501 _getDialogueContent: function() {
502 var template = Y.Handlebars.compile(TEMPLATE),
503 canShowFilepicker = this.get('host').canShowFilepicker('image'),
504 content = Y.Node.create(template({
505 elementid: this.get('host').get('elementid'),
507 component: COMPONENTNAME,
508 showFilepicker: canShowFilepicker,
509 alignments: ALIGNMENTS
512 this._form = content;
514 // Configure the view of the current image.
515 this._applyImageProperties(this._form);
517 this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
518 this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._updateWarning, this);
519 this._form.one('.' + CSS.INPUTALT).on('change', this._updateWarning, this);
520 this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this);
521 this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true);
522 this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) {
523 if (event.target.get('checked')) {
524 this._autoAdjustSize(event);
527 this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
528 this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
530 if (canShowFilepicker) {
531 this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
532 this.get('host').showFilepicker('image', this._filepickerCallback, this);
539 _autoAdjustSize: function(e, forceHeight) {
540 forceHeight = forceHeight || false;
542 var keyField = this._form.one('.' + CSS.INPUTWIDTH),
543 keyFieldType = 'width',
544 subField = this._form.one('.' + CSS.INPUTHEIGHT),
545 subFieldType = 'height',
546 constrainField = this._form.one('.' + CSS.INPUTCONSTRAIN),
547 keyFieldValue = keyField.get('value'),
548 subFieldValue = subField.get('value'),
549 imagePreview = this._form.one('.' + CSS.IMAGEPREVIEW),
553 // If we do not know the image size, do not do anything.
554 if (!this._rawImageDimensions) {
558 // Set the width back to default if it is empty.
559 if (keyFieldValue === '') {
560 keyFieldValue = this._rawImageDimensions[keyFieldType];
561 keyField.set('value', keyFieldValue);
562 keyFieldValue = keyField.get('value');
565 // Clear the existing preview sizes.
566 imagePreview.setStyles({
571 // Now update with the new values.
572 if (!constrainField.get('checked')) {
573 // We are not keeping the image proportion - update the preview accordingly.
576 if (keyFieldValue.match(REGEX.ISPERCENT)) {
577 rawPercentage = parseInt(keyFieldValue, 10);
578 rawSize = this._rawImageDimensions.width / 100 * rawPercentage;
579 imagePreview.setStyle('width', rawSize + 'px');
581 imagePreview.setStyle('width', keyFieldValue + 'px');
585 if (subFieldValue.match(REGEX.ISPERCENT)) {
586 rawPercentage = parseInt(subFieldValue, 10);
587 rawSize = this._rawImageDimensions.height / 100 * rawPercentage;
588 imagePreview.setStyle('height', rawSize + 'px');
590 imagePreview.setStyle('height', subFieldValue + 'px');
593 // We are keeping the image in proportion.
595 // By default we update based on width. Swap the key and sub fields around to achieve a height-based scale.
597 _temporaryValue = keyField;
599 subField = _temporaryValue;
601 _temporaryValue = keyFieldType;
602 keyFieldType = subFieldType;
603 subFieldType = _temporaryValue;
605 _temporaryValue = keyFieldValue;
606 keyFieldValue = subFieldValue;
607 subFieldValue = _temporaryValue;
610 if (keyFieldValue.match(REGEX.ISPERCENT)) {
611 // This is a percentage based change. Copy it verbatim.
612 subFieldValue = keyFieldValue;
614 // Set the width to the calculated pixel width.
615 rawPercentage = parseInt(keyFieldValue, 10);
616 rawSize = this._rawImageDimensions.width / 100 * rawPercentage;
618 // And apply the width/height to the container.
619 imagePreview.setStyle('width', rawSize);
620 rawSize = this._rawImageDimensions.height / 100 * rawPercentage;
621 imagePreview.setStyle('height', rawSize);
623 // Calculate the scaled subFieldValue from the keyFieldValue.
624 subFieldValue = Math.round((keyFieldValue / this._rawImageDimensions[keyFieldType]) *
625 this._rawImageDimensions[subFieldType]);
628 imagePreview.setStyles({
629 'width': subFieldValue,
630 'height': keyFieldValue
633 imagePreview.setStyles({
634 'width': keyFieldValue,
635 'height': subFieldValue
640 // Update the subField's value within the form to reflect the changes.
641 subField.set('value', subFieldValue);
646 * Update the dialogue after an image was selected in the File Picker.
648 * @method _filepickerCallback
649 * @param {object} params The parameters provided by the filepicker
650 * containing information about the image.
653 _filepickerCallback: function(params) {
654 if (params.url !== '') {
655 var input = this._form.one('.' + CSS.INPUTURL);
656 input.set('value', params.url);
658 // Auto set the width and height.
659 this._form.one('.' + CSS.INPUTWIDTH).set('value', '');
660 this._form.one('.' + CSS.INPUTHEIGHT).set('value', '');
662 // Load the preview image.
663 this._loadPreviewImage(params.url);
668 * Applies properties of an existing image to the image dialogue for editing.
670 * @method _applyImageProperties
674 _applyImageProperties: function(form) {
675 var properties = this._getSelectedImageProperties(),
676 img = form.one('.' + CSS.IMAGEPREVIEW);
678 if (properties === false) {
679 img.setStyle('display', 'none');
680 // Set the default alignment.
681 ALIGNMENTS.some(function(alignment) {
682 if (alignment.isDefault) {
683 form.one('.' + CSS.INPUTALIGNMENT).set('value', alignment.value);
693 if (properties.align) {
694 form.one('.' + CSS.INPUTALIGNMENT).set('value', properties.align);
696 if (properties.customstyle) {
697 form.one('.' + CSS.INPUTCUSTOMSTYLE).set('value', properties.customstyle);
699 if (properties.width) {
700 form.one('.' + CSS.INPUTWIDTH).set('value', properties.width);
702 if (properties.height) {
703 form.one('.' + CSS.INPUTHEIGHT).set('value', properties.height);
705 if (properties.alt) {
706 form.one('.' + CSS.INPUTALT).set('value', properties.alt);
708 if (properties.src) {
709 form.one('.' + CSS.INPUTURL).set('value', properties.src);
710 this._loadPreviewImage(properties.src);
712 if (properties.presentation) {
713 form.one('.' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
716 // Update the image preview based on the form properties.
717 this._autoAdjustSize();
721 * Gets the properties of the currently selected image.
723 * The first image only if multiple images are selected.
725 * @method _getSelectedImageProperties
729 _getSelectedImageProperties: function() {
739 // Get the current selection.
740 images = this.get('host').getSelectedNodes(),
747 images = images.filter('img');
750 if (images && images.size()) {
751 image = this._removeLegacyAlignment(images.item(0));
752 this._selectedImage = image;
754 style = image.getAttribute('style');
755 properties.customstyle = style;
757 width = image.getAttribute('width');
758 if (!width.match(REGEX.ISPERCENT)) {
759 width = parseInt(width, 10);
761 height = image.getAttribute('height');
762 if (!height.match(REGEX.ISPERCENT)) {
763 height = parseInt(height, 10);
767 properties.width = width;
770 properties.height = height;
772 this._getAlignmentPropeties(image, properties);
773 properties.src = image.getAttribute('src');
774 properties.alt = image.getAttribute('alt') || '';
775 properties.presentation = (image.get('role') === 'presentation');
779 // No image selected - clean up.
780 this._selectedImage = null;
785 * Sets the alignment of a properties object.
787 * @method _getAlignmentPropeties
788 * @param {Node} image The image that the alignment properties should be found for
789 * @param {Object} properties The properties object that is created in _getSelectedImageProperties()
792 _getAlignmentPropeties: function(image, properties) {
793 var complete = false,
796 // Check for an alignment value.
797 complete = ALIGNMENTS.some(function(alignment) {
798 var classname = this._getAlignmentClass(alignment.value);
799 if (image.hasClass(classname)) {
800 properties.align = alignment.value;
801 Y.log('Found alignment ' + alignment.value, 'debug', 'atto_image-button');
806 if (alignment.isDefault) {
807 defaultAlignment = alignment.value;
813 if (!complete && defaultAlignment) {
814 properties.align = defaultAlignment;
819 * Update the form when the URL was changed. This includes updating the
820 * height, width, and image preview.
822 * @method _urlChanged
825 _urlChanged: function() {
826 var input = this._form.one('.' + CSS.INPUTURL);
828 if (input.get('value') !== '') {
829 // Load the preview image.
830 this._loadPreviewImage(input.get('value'));
835 * Update the image in the contenteditable.
838 * @param {EventFacade} e
841 _setImage: function(e) {
842 var form = this._form,
843 url = form.one('.' + CSS.INPUTURL).get('value'),
844 alt = form.one('.' + CSS.INPUTALT).get('value'),
845 width = form.one('.' + CSS.INPUTWIDTH).get('value'),
846 height = form.one('.' + CSS.INPUTHEIGHT).get('value'),
847 alignment = this._getAlignmentClass(form.one('.' + CSS.INPUTALIGNMENT).get('value')),
848 presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'),
849 constrain = form.one('.' + CSS.INPUTCONSTRAIN).get('checked'),
851 customstyle = form.one('.' + CSS.INPUTCUSTOMSTYLE).get('value'),
853 host = this.get('host');
857 // Check if there are any accessibility issues.
858 if (this._updateWarning()) {
862 // Focus on the editor in preparation for inserting the image.
865 if (this._selectedImage) {
866 host.setSelection(host.getSelectionFromNode(this._selectedImage));
868 host.setSelection(this._currentSelection);
872 classlist.push(CSS.RESPONSIVE);
875 // Add the alignment class for the image.
876 classlist.push(alignment);
878 if (!width.match(REGEX.ISPERCENT) && isNaN(parseInt(width, 10))) {
879 form.one('.' + CSS.INPUTWIDTH).focus();
882 if (!height.match(REGEX.ISPERCENT) && isNaN(parseInt(height, 10))) {
883 form.one('.' + CSS.INPUTHEIGHT).focus();
887 var template = Y.Handlebars.compile(IMAGETEMPLATE);
888 imagehtml = template({
893 presentation: presentation,
894 customstyle: customstyle,
895 classlist: classlist.join(' ')
898 this.get('host').insertContentAtFocusPoint(imagehtml);
910 * Removes any legacy styles added by previous versions of the atto image button.
912 * @method _removeLegacyAlignment
913 * @param {Y.Node} imageNode
917 _removeLegacyAlignment: function(imageNode) {
918 if (!imageNode.getStyle('margin')) {
919 // There is no margin therefore this cannot match any known alignments.
923 ALIGNMENTS.some(function(alignment) {
924 if (imageNode.getStyle(alignment.name) !== alignment.value) {
925 // The name/value do not match. Skip.
929 var normalisedNode = Y.Node.create('<div>');
930 normalisedNode.setStyle('margin', alignment.margin);
931 if (imageNode.getStyle('margin') !== normalisedNode.getStyle('margin')) {
932 // The margin does not match.
936 Y.log('Legacy alignment found and removed.', 'info', 'atto_image-button');
937 imageNode.addClass(this._getAlignmentClass(alignment.value));
938 imageNode.setStyle(alignment.name, null);
939 imageNode.setStyle('margin', null);
947 _getAlignmentClass: function(alignment) {
948 return CSS.ALIGNSETTINGS + '_' + alignment;
952 * Update the alt text warning live.
954 * @method _updateWarning
955 * @return {boolean} whether a warning should be displayed.
958 _updateWarning: function() {
959 var form = this._form,
961 alt = form.one('.' + CSS.INPUTALT).get('value'),
962 presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
963 if (alt === '' && !presentation) {
964 form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
965 form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
966 form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
969 form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
970 form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
971 form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
974 this.getDialogue().centerDialogue();
980 }, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});