diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index 282158d0a7d..5cf4c0670c8 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -1288,6 +1288,7 @@ $string['maximumgradex'] = 'Maximum grade: {$a}';
$string['maximumshort'] = 'Max';
$string['maximumupload'] = 'Maximum upload size';
$string['maximumupload_help'] = 'The maximum file size allowed for student uploads to the course. Additionally, you can further restrict the maximum upload size for each activity.';
+$string['maxlengthreached'] = 'Maximum character limit of {$a} has been reached';
$string['maxnumberweeks'] = 'Maximum number of sections';
$string['maxnumberweeks_desc'] = 'The maximum value in the number of sections drop-down menu (applies to certain course formats only).';
$string['maxnumcoursesincombo'] = 'Browse {$a->numberofcourses} courses.';
diff --git a/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js b/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js
index 70b10090068..319e1ba0b7a 100644
--- a/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js
+++ b/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js
@@ -1,3 +1,3 @@
-define("tiny_media/imagedetails",["exports","core/config","core/modal_events","core/notification","core/pending","./selectors","core/templates","core/str","tiny_media/imageinsert","tiny_media/imagehelpers"],(function(_exports,_config,_modal_events,_notification,_pending,_selectors,_templates,_str,_imageinsert,_imagehelpers){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ImageDetails=void 0,_config=_interopRequireDefault(_config),_modal_events=_interopRequireDefault(_modal_events),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates);_exports.ImageDetails=class{constructor(root,editor,currentModal,canShowFilePicker,canShowDropZone,currentUrl,_image){_defineProperty(this,"DEFAULTS",{WIDTH:160,HEIGHT:160}),_defineProperty(this,"rawImageDimensions",null),_defineProperty(this,"init",(function(){this.currentModal.setTitle((0,_str.getString)("imagedetails","tiny_media")),this.imageTypeChecked(),this.presentationChanged(),this.storeImageDimensions(this.image),this.setImageDimensions(),this.registerEventListeners()})),_defineProperty(this,"loadInsertImage",(async function(){const templateContext={elementid:this.editor.id,showfilepicker:this.canShowFilePicker,showdropzone:this.canShowDropZone};Promise.all([(0,_imagehelpers.bodyImageInsert)(templateContext,this.root),(0,_imagehelpers.footerImageInsert)(templateContext,this.root)]).then((()=>{new _imageinsert.ImageInsert(this.root,this.editor,this.currentModal,this.canShowFilePicker,this.canShowDropZone).init()})).catch((error=>{window.console.log(error)}))})),_defineProperty(this,"setImageDimensions",(()=>{const imagePreviewBox=this.root.querySelector(_selectors.default.IMAGE.elements.previewBox),image=this.root.querySelector(_selectors.default.IMAGE.elements.preview),widthField=this.root.querySelector(_selectors.default.IMAGE.elements.width),heightField=this.root.querySelector(_selectors.default.IMAGE.elements.height),updateImageDimensions=()=>{const boxWidth=imagePreviewBox.clientWidth,boxHeight=imagePreviewBox.clientHeight,dimensions=this.fitSquareIntoBox(widthField.value,heightField.value,boxWidth,boxHeight);image.style.width="".concat(dimensions.width,"px"),image.style.height="".concat(dimensions.height,"px")};0===imagePreviewBox.clientWidth?this.currentModal.getRoot().on(_modal_events.default.shown,(()=>{updateImageDimensions()})):updateImageDimensions()})),_defineProperty(this,"fitSquareIntoBox",((squareWidth,squareHeight,boxWidth,boxHeight)=>{if(squareWidth(""===element.value&&(element.value=this.rawImageDimensions.width),element.value))(this.root.querySelector(_selectors.default.IMAGE.elements.width)),currentHeight=(element=>(""===element.value&&(element.value=this.rawImageDimensions.height),element.value))(this.root.querySelector(_selectors.default.IMAGE.elements.height)),preview=this.root.querySelector(_selectors.default.IMAGE.elements.preview);preview.setAttribute("src",image.src),preview.style.display="";const constrain=this.root.querySelector(_selectors.default.IMAGE.elements.constrain);if((0,_imagehelpers.isPercentageValue)(currentWidth)&&(0,_imagehelpers.isPercentageValue)(currentHeight))constrain.checked=currentWidth===currentHeight;else if(0===image.width||0===image.height)constrain.disabled="disabled";else{const widthRatio=Math.round(100*parseInt(currentWidth,10)/image.width),heightRatio=Math.round(100*parseInt(currentHeight,10)/image.height);constrain.checked=widthRatio===heightRatio}((currentWidth,currentHeight)=>{this.rawImageDimensions.width===currentWidth&&this.rawImageDimensions.height===currentHeight?(this.currentWidth=this.rawImageDimensions.width,this.currentHeight=this.rawImageDimensions.height,this.sizeChecked("original")):(this.currentWidth=currentWidth,this.currentHeight=currentHeight,this.sizeChecked("custom"))})(Number(currentWidth),Number(currentHeight))}sizeChecked(option){const widthInput=this.root.querySelector(_selectors.default.IMAGE.elements.width),heightInput=this.root.querySelector(_selectors.default.IMAGE.elements.height);if("original"===option)this.sizeOriginalChecked(),widthInput.value=this.rawImageDimensions.width,heightInput.value=this.rawImageDimensions.height;else if("custom"===option&&(this.sizeCustomChecked(),widthInput.value=this.currentWidth,heightInput.value=this.currentHeight,this.currentWidth===this.rawImageDimensions.width&&this.currentHeight===this.rawImageDimensions.height)){this.root.querySelector(_selectors.default.IMAGE.elements.constrain).checked=!0}this.autoAdjustSize()}autoAdjustSize(){let forceHeight=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(!this.rawImageDimensions)return;const widthField=this.root.querySelector(_selectors.default.IMAGE.elements.width),heightField=this.root.querySelector(_selectors.default.IMAGE.elements.height),normalizeFieldData=fieldData=>(fieldData.isPercentageValue=!!(0,_imagehelpers.isPercentageValue)(fieldData.field.value),fieldData.isPercentageValue?(fieldData.percentValue=parseInt(fieldData.field.value,10),fieldData.pixelSize=this.rawImageDimensions[fieldData.type]/100*fieldData.percentValue):(fieldData.pixelSize=parseInt(fieldData.field.value,10),fieldData.percentValue=fieldData.pixelSize/this.rawImageDimensions[fieldData.type]*100),fieldData),getKeyField=()=>{const currentValue=forceHeight?{field:heightField,type:"height"}:{field:widthField,type:"width"};return""===currentValue.field.value&&(currentValue.field.value=this.rawImageDimensions[currentValue.type]),normalizeFieldData(currentValue)};if(this.root.querySelector(_selectors.default.IMAGE.elements.constrain).checked){const keyField=getKeyField(),relativeField=normalizeFieldData(forceHeight?{field:widthField,type:"width"}:{field:heightField,type:"height"});keyField.isPercentageValue?(relativeField.field.value=keyField.field.value,relativeField.percentValue=keyField.percentValue):(relativeField.pixelSize=Math.round(keyField.pixelSize/this.rawImageDimensions[keyField.type]*this.rawImageDimensions[relativeField.type]),relativeField.field.value=relativeField.pixelSize)}this.currentWidth=Number(widthField.value)!==this.rawImageDimensions.width?widthField.value:this.currentWidth,this.currentHeight=Number(heightField.value)!==this.rawImageDimensions.height?heightField.value:this.currentHeight}sizeOriginalChecked(){this.root.querySelector(_selectors.default.IMAGE.elements.sizeOriginal).checked=!0,this.root.querySelector(_selectors.default.IMAGE.elements.sizeCustom).checked=!1,(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.properties,this.root)}sizeCustomChecked(){this.root.querySelector(_selectors.default.IMAGE.elements.sizeOriginal).checked=!1,this.root.querySelector(_selectors.default.IMAGE.elements.sizeCustom).checked=!0,(0,_imagehelpers.showElements)(_selectors.default.IMAGE.elements.properties,this.root)}presentationChanged(){const presentation=this.root.querySelector(_selectors.default.IMAGE.elements.presentation);this.root.querySelector(_selectors.default.IMAGE.elements.alt).disabled=presentation.checked,this.handleKeyupCharacterCount()}imageTypeChecked(){const isExternalUrl=!1===new RegExp("".concat(_config.default.wwwroot)).test(this.currentUrl);if((0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.url,this.root),isExternalUrl)this.setFilenameLabel(decodeURI(this.currentUrl));else{const filename=this.currentUrl.split("/").pop().split("?")[0];this.setFilenameLabel(decodeURI(filename))}}setFilenameLabel(label){const urlLabelEle=this.root.querySelector(_selectors.default.IMAGE.elements.fileNameLabel);urlLabelEle&&(urlLabelEle.innerHTML=label,urlLabelEle.setAttribute("title",label))}toggleAriaInvalid(selectors,predicate){selectors.forEach((selector=>{this.root.querySelectorAll(selector).forEach((element=>element.setAttribute("aria-invalid",predicate)))}))}hasErrorUrlField(){const urlError=""===this.currentUrl;return urlError?(0,_imagehelpers.showElements)(_selectors.default.IMAGE.elements.urlWarning,this.root):(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.urlWarning,this.root),this.toggleAriaInvalid([_selectors.default.IMAGE.elements.url],urlError),urlError}hasErrorAltField(){const alt=this.root.querySelector(_selectors.default.IMAGE.elements.alt).value,presentation=this.root.querySelector(_selectors.default.IMAGE.elements.presentation).checked,imageAltError=""===alt&&!presentation;return imageAltError?(0,_imagehelpers.showElements)(_selectors.default.IMAGE.elements.altWarning,this.root):(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.urlWaaltWarningrning,this.root),this.toggleAriaInvalid([_selectors.default.IMAGE.elements.alt,_selectors.default.IMAGE.elements.presentation],imageAltError),imageAltError}updateWarning(){const urlError=this.hasErrorUrlField(),imageAltError=this.hasErrorAltField();return urlError||imageAltError}getImageContext(){if(this.updateWarning())return null;const classList=[],constrain=this.root.querySelector(_selectors.default.IMAGE.elements.constrain).checked,sizeOriginal=this.root.querySelector(_selectors.default.IMAGE.elements.sizeOriginal).checked;return constrain||sizeOriginal?classList.push(_selectors.default.IMAGE.styles.responsive):classList.pop(_selectors.default.IMAGE.styles.responsive),{url:this.currentUrl,alt:this.root.querySelector(_selectors.default.IMAGE.elements.alt).value,width:this.root.querySelector(_selectors.default.IMAGE.elements.width).value,height:this.root.querySelector(_selectors.default.IMAGE.elements.height).value,presentation:this.root.querySelector(_selectors.default.IMAGE.elements.presentation).checked,customStyle:this.root.querySelector(_selectors.default.IMAGE.elements.customStyle).value,classlist:classList.join(" ")}}setImage(){const pendingPromise=new _pending.default("tiny_media:setImage");if(""===this.currentUrl)return;if(this.updateWarning())return void pendingPromise.resolve();const width=this.root.querySelector(_selectors.default.IMAGE.elements.width).value;if(!(0,_imagehelpers.isPercentageValue)(width)&&isNaN(parseInt(width,10)))return this.root.querySelector(_selectors.default.IMAGE.elements.width).focus(),void pendingPromise.resolve();const height=this.root.querySelector(_selectors.default.IMAGE.elements.height).value;if(!(0,_imagehelpers.isPercentageValue)(height)&&isNaN(parseInt(height,10)))return this.root.querySelector(_selectors.default.IMAGE.elements.height).focus(),void pendingPromise.resolve();_templates.default.render("tiny_media/image",this.getImageContext()).then((html=>(this.editor.insertContent(html),this.currentModal.destroy(),pendingPromise.resolve(),html))).catch((error=>{window.console.log(error)}))}deleteImage(){_notification.default.deleteCancelPromise((0,_str.getString)("deleteimage","tiny_media"),(0,_str.getString)("deleteimagewarning","tiny_media")).then((()=>{(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.altWarning,this.root),this.loadInsertImage()})).catch((error=>{window.console.log(error)}))}registerEventListeners(){this.root.querySelector(_selectors.default.IMAGE.actions.submit).addEventListener("click",(e=>{e.preventDefault(),this.setImage()}));const deleteImageEle=this.root.querySelector(_selectors.default.IMAGE.actions.deleteImage);deleteImageEle.addEventListener("click",(()=>{this.deleteImage()})),deleteImageEle.addEventListener("keydown",(e=>{"Enter"===e.key&&this.deleteImage()})),this.root.addEventListener("change",(e=>{e.target.closest(_selectors.default.IMAGE.elements.presentation)&&this.presentationChanged();e.target.closest(_selectors.default.IMAGE.elements.constrain)&&this.autoAdjustSize();e.target.closest(_selectors.default.IMAGE.elements.sizeOriginal)&&this.sizeChecked("original");e.target.closest(_selectors.default.IMAGE.elements.sizeCustom)&&this.sizeChecked("custom")})),this.root.addEventListener("blur",(e=>{if(e.target.nodeType===Node.ELEMENT_NODE){e.target.closest(_selectors.default.IMAGE.elements.presentation)&&this.presentationChanged()}}),!0),this.root.addEventListener("keyup",(e=>{e.target.closest(_selectors.default.IMAGE.elements.alt)&&this.handleKeyupCharacterCount()})),this.root.addEventListener("input",(e=>{const widthEle=e.target.closest(_selectors.default.IMAGE.elements.width);widthEle&&(widthEle.value=""===widthEle.value?0:Number(widthEle.value),this.autoAdjustSize());const heightEle=e.target.closest(_selectors.default.IMAGE.elements.height);heightEle&&(heightEle.value=""===heightEle.value?0:Number(heightEle.value),this.autoAdjustSize(!0))}))}handleKeyupCharacterCount(){const alt=this.root.querySelector(_selectors.default.IMAGE.elements.alt).value;this.root.querySelector("#currentcount").innerHTML=alt.length}}}));
+define("tiny_media/imagedetails",["exports","core/config","core/modal_events","core/notification","core/pending","./selectors","core/templates","core/str","tiny_media/imageinsert","tiny_media/imagehelpers"],(function(_exports,_config,_modal_events,_notification,_pending,_selectors,_templates,_str,_imageinsert,_imagehelpers){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ImageDetails=void 0,_config=_interopRequireDefault(_config),_modal_events=_interopRequireDefault(_modal_events),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates);_exports.ImageDetails=class{constructor(root,editor,currentModal,canShowFilePicker,canShowDropZone,currentUrl,_image){_defineProperty(this,"DEFAULTS",{WIDTH:160,HEIGHT:160}),_defineProperty(this,"rawImageDimensions",null),_defineProperty(this,"init",(function(){this.currentModal.setTitle((0,_str.getString)("imagedetails","tiny_media")),this.imageTypeChecked(),this.presentationChanged(),this.storeImageDimensions(this.image),this.setImageDimensions(),this.registerEventListeners()})),_defineProperty(this,"loadInsertImage",(async function(){const templateContext={elementid:this.editor.id,showfilepicker:this.canShowFilePicker,showdropzone:this.canShowDropZone};Promise.all([(0,_imagehelpers.bodyImageInsert)(templateContext,this.root),(0,_imagehelpers.footerImageInsert)(templateContext,this.root)]).then((()=>{new _imageinsert.ImageInsert(this.root,this.editor,this.currentModal,this.canShowFilePicker,this.canShowDropZone).init()})).catch((error=>{window.console.log(error)}))})),_defineProperty(this,"setImageDimensions",(()=>{const imagePreviewBox=this.root.querySelector(_selectors.default.IMAGE.elements.previewBox),image=this.root.querySelector(_selectors.default.IMAGE.elements.preview),widthField=this.root.querySelector(_selectors.default.IMAGE.elements.width),heightField=this.root.querySelector(_selectors.default.IMAGE.elements.height),updateImageDimensions=()=>{const boxWidth=imagePreviewBox.clientWidth,boxHeight=imagePreviewBox.clientHeight,dimensions=this.fitSquareIntoBox(widthField.value,heightField.value,boxWidth,boxHeight);image.style.width="".concat(dimensions.width,"px"),image.style.height="".concat(dimensions.height,"px")};0===imagePreviewBox.clientWidth?this.currentModal.getRoot().on(_modal_events.default.shown,(()=>{updateImageDimensions()})):updateImageDimensions()})),_defineProperty(this,"fitSquareIntoBox",((squareWidth,squareHeight,boxWidth,boxHeight)=>{if(squareWidth(""===element.value&&(element.value=this.rawImageDimensions.width),element.value))(this.root.querySelector(_selectors.default.IMAGE.elements.width)),currentHeight=(element=>(""===element.value&&(element.value=this.rawImageDimensions.height),element.value))(this.root.querySelector(_selectors.default.IMAGE.elements.height)),preview=this.root.querySelector(_selectors.default.IMAGE.elements.preview);preview.setAttribute("src",image.src),preview.style.display="";const constrain=this.root.querySelector(_selectors.default.IMAGE.elements.constrain);if((0,_imagehelpers.isPercentageValue)(currentWidth)&&(0,_imagehelpers.isPercentageValue)(currentHeight))constrain.checked=currentWidth===currentHeight;else if(0===image.width||0===image.height)constrain.disabled="disabled";else{const widthRatio=Math.round(100*parseInt(currentWidth,10)/image.width),heightRatio=Math.round(100*parseInt(currentHeight,10)/image.height);constrain.checked=widthRatio===heightRatio}((currentWidth,currentHeight)=>{this.rawImageDimensions.width===currentWidth&&this.rawImageDimensions.height===currentHeight?(this.currentWidth=this.rawImageDimensions.width,this.currentHeight=this.rawImageDimensions.height,this.sizeChecked("original")):(this.currentWidth=currentWidth,this.currentHeight=currentHeight,this.sizeChecked("custom"))})(Number(currentWidth),Number(currentHeight))}sizeChecked(option){const widthInput=this.root.querySelector(_selectors.default.IMAGE.elements.width),heightInput=this.root.querySelector(_selectors.default.IMAGE.elements.height);if("original"===option)this.sizeOriginalChecked(),widthInput.value=this.rawImageDimensions.width,heightInput.value=this.rawImageDimensions.height;else if("custom"===option&&(this.sizeCustomChecked(),widthInput.value=this.currentWidth,heightInput.value=this.currentHeight,this.currentWidth===this.rawImageDimensions.width&&this.currentHeight===this.rawImageDimensions.height)){this.root.querySelector(_selectors.default.IMAGE.elements.constrain).checked=!0}this.autoAdjustSize()}autoAdjustSize(){let forceHeight=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(!this.rawImageDimensions)return;const widthField=this.root.querySelector(_selectors.default.IMAGE.elements.width),heightField=this.root.querySelector(_selectors.default.IMAGE.elements.height),normalizeFieldData=fieldData=>(fieldData.isPercentageValue=!!(0,_imagehelpers.isPercentageValue)(fieldData.field.value),fieldData.isPercentageValue?(fieldData.percentValue=parseInt(fieldData.field.value,10),fieldData.pixelSize=this.rawImageDimensions[fieldData.type]/100*fieldData.percentValue):(fieldData.pixelSize=parseInt(fieldData.field.value,10),fieldData.percentValue=fieldData.pixelSize/this.rawImageDimensions[fieldData.type]*100),fieldData),getKeyField=()=>{const currentValue=forceHeight?{field:heightField,type:"height"}:{field:widthField,type:"width"};return""===currentValue.field.value&&(currentValue.field.value=this.rawImageDimensions[currentValue.type]),normalizeFieldData(currentValue)};if(this.root.querySelector(_selectors.default.IMAGE.elements.constrain).checked){const keyField=getKeyField(),relativeField=normalizeFieldData(forceHeight?{field:widthField,type:"width"}:{field:heightField,type:"height"});keyField.isPercentageValue?(relativeField.field.value=keyField.field.value,relativeField.percentValue=keyField.percentValue):(relativeField.pixelSize=Math.round(keyField.pixelSize/this.rawImageDimensions[keyField.type]*this.rawImageDimensions[relativeField.type]),relativeField.field.value=relativeField.pixelSize)}this.currentWidth=Number(widthField.value)!==this.rawImageDimensions.width?widthField.value:this.currentWidth,this.currentHeight=Number(heightField.value)!==this.rawImageDimensions.height?heightField.value:this.currentHeight}sizeOriginalChecked(){this.root.querySelector(_selectors.default.IMAGE.elements.sizeOriginal).checked=!0,this.root.querySelector(_selectors.default.IMAGE.elements.sizeCustom).checked=!1,(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.properties,this.root)}sizeCustomChecked(){this.root.querySelector(_selectors.default.IMAGE.elements.sizeOriginal).checked=!1,this.root.querySelector(_selectors.default.IMAGE.elements.sizeCustom).checked=!0,(0,_imagehelpers.showElements)(_selectors.default.IMAGE.elements.properties,this.root)}async presentationChanged(){const presentation=this.root.querySelector(_selectors.default.IMAGE.elements.presentation);this.root.querySelector(_selectors.default.IMAGE.elements.alt).disabled=presentation.checked,await this.handleKeyupCharacterCount()}imageTypeChecked(){const isExternalUrl=!1===new RegExp("".concat(_config.default.wwwroot)).test(this.currentUrl);if((0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.url,this.root),isExternalUrl)this.setFilenameLabel(decodeURI(this.currentUrl));else{const filename=this.currentUrl.split("/").pop().split("?")[0];this.setFilenameLabel(decodeURI(filename))}}setFilenameLabel(label){const urlLabelEle=this.root.querySelector(_selectors.default.IMAGE.elements.fileNameLabel);urlLabelEle&&(urlLabelEle.innerHTML=label,urlLabelEle.setAttribute("title",label))}toggleAriaInvalid(selectors,predicate){selectors.forEach((selector=>{this.root.querySelectorAll(selector).forEach((element=>element.setAttribute("aria-invalid",predicate)))}))}hasErrorUrlField(){const urlError=""===this.currentUrl;return urlError?(0,_imagehelpers.showElements)(_selectors.default.IMAGE.elements.urlWarning,this.root):(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.urlWarning,this.root),this.toggleAriaInvalid([_selectors.default.IMAGE.elements.url],urlError),urlError}hasErrorAltField(){const alt=this.root.querySelector(_selectors.default.IMAGE.elements.alt).value,presentation=this.root.querySelector(_selectors.default.IMAGE.elements.presentation).checked,imageAltError=""===alt&&!presentation;return imageAltError?(0,_imagehelpers.showElements)(_selectors.default.IMAGE.elements.altWarning,this.root):(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.urlWaaltWarningrning,this.root),this.toggleAriaInvalid([_selectors.default.IMAGE.elements.alt,_selectors.default.IMAGE.elements.presentation],imageAltError),imageAltError}updateWarning(){const urlError=this.hasErrorUrlField(),imageAltError=this.hasErrorAltField();return urlError||imageAltError}getImageContext(){if(this.updateWarning())return null;const classList=[],constrain=this.root.querySelector(_selectors.default.IMAGE.elements.constrain).checked,sizeOriginal=this.root.querySelector(_selectors.default.IMAGE.elements.sizeOriginal).checked;return constrain||sizeOriginal?classList.push(_selectors.default.IMAGE.styles.responsive):classList.pop(_selectors.default.IMAGE.styles.responsive),{url:this.currentUrl,alt:this.root.querySelector(_selectors.default.IMAGE.elements.alt).value,width:this.root.querySelector(_selectors.default.IMAGE.elements.width).value,height:this.root.querySelector(_selectors.default.IMAGE.elements.height).value,presentation:this.root.querySelector(_selectors.default.IMAGE.elements.presentation).checked,customStyle:this.root.querySelector(_selectors.default.IMAGE.elements.customStyle).value,classlist:classList.join(" ")}}setImage(){const pendingPromise=new _pending.default("tiny_media:setImage");if(""===this.currentUrl)return;if(this.updateWarning())return void pendingPromise.resolve();const width=this.root.querySelector(_selectors.default.IMAGE.elements.width).value;if(!(0,_imagehelpers.isPercentageValue)(width)&&isNaN(parseInt(width,10)))return this.root.querySelector(_selectors.default.IMAGE.elements.width).focus(),void pendingPromise.resolve();const height=this.root.querySelector(_selectors.default.IMAGE.elements.height).value;if(!(0,_imagehelpers.isPercentageValue)(height)&&isNaN(parseInt(height,10)))return this.root.querySelector(_selectors.default.IMAGE.elements.height).focus(),void pendingPromise.resolve();_templates.default.render("tiny_media/image",this.getImageContext()).then((html=>(this.editor.insertContent(html),this.currentModal.destroy(),pendingPromise.resolve(),html))).catch((error=>{window.console.log(error)}))}deleteImage(){_notification.default.deleteCancelPromise((0,_str.getString)("deleteimage","tiny_media"),(0,_str.getString)("deleteimagewarning","tiny_media")).then((()=>{(0,_imagehelpers.hideElements)(_selectors.default.IMAGE.elements.altWarning,this.root),this.loadInsertImage()})).catch((error=>{window.console.log(error)}))}registerEventListeners(){this.root.querySelector(_selectors.default.IMAGE.actions.submit).addEventListener("click",(e=>{e.preventDefault(),this.setImage()}));const deleteImageEle=this.root.querySelector(_selectors.default.IMAGE.actions.deleteImage);deleteImageEle.addEventListener("click",(()=>{this.deleteImage()})),deleteImageEle.addEventListener("keydown",(e=>{"Enter"===e.key&&this.deleteImage()})),this.root.addEventListener("change",(async e=>{e.target.closest(_selectors.default.IMAGE.elements.presentation)&&await this.presentationChanged();e.target.closest(_selectors.default.IMAGE.elements.constrain)&&this.autoAdjustSize();e.target.closest(_selectors.default.IMAGE.elements.sizeOriginal)&&this.sizeChecked("original");e.target.closest(_selectors.default.IMAGE.elements.sizeCustom)&&this.sizeChecked("custom")})),this.root.addEventListener("blur",(async e=>{if(e.target.nodeType===Node.ELEMENT_NODE){e.target.closest(_selectors.default.IMAGE.elements.presentation)&&await this.presentationChanged()}}),!0),this.root.addEventListener("keyup",(async e=>{e.target.closest(_selectors.default.IMAGE.elements.alt)&&await this.handleKeyupCharacterCount()})),this.root.addEventListener("input",(e=>{const widthEle=e.target.closest(_selectors.default.IMAGE.elements.width);widthEle&&(widthEle.value=""===widthEle.value?0:Number(widthEle.value),this.autoAdjustSize());const heightEle=e.target.closest(_selectors.default.IMAGE.elements.height);heightEle&&(heightEle.value=""===heightEle.value?0:Number(heightEle.value),this.autoAdjustSize(!0))}))}async handleKeyupCharacterCount(){const altField=this.root.querySelector(_selectors.default.IMAGE.elements.alt),alt=altField.value;this.root.querySelector("#currentcount").innerHTML=alt.length;const maxLength=altField.getAttribute("maxlength"),maxLengthFeedback=document.getElementById("maxlength_feedback");if(alt.length>=maxLength){maxLengthFeedback.textContent=await(0,_str.getString)("maxlengthreached","core",maxLength);const suffix=this.toggleMaxlengthFeedbackSuffix?"":".";maxLengthFeedback.textContent+=suffix,this.toggleMaxlengthFeedbackSuffix=!this.toggleMaxlengthFeedbackSuffix,setTimeout((()=>{maxLengthFeedback.textContent=""}),4e3)}}}}));
//# sourceMappingURL=imagedetails.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js.map b/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js.map
index 0491a9c9c6a..6a8c3087d59 100644
--- a/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js.map
+++ b/lib/editor/tiny/plugins/media/amd/build/imagedetails.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"imagedetails.min.js","sources":["../src/imagedetails.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny media plugin image details class for Moodle.\n *\n * @module tiny_media/imagedetails\n * @copyright 2024 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from './selectors';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport {ImageInsert} from 'tiny_media/imageinsert';\nimport {\n bodyImageInsert,\n footerImageInsert,\n showElements,\n hideElements,\n isPercentageValue,\n} from 'tiny_media/imagehelpers';\n\nexport class ImageDetails {\n DEFAULTS = {\n WIDTH: 160,\n HEIGHT: 160,\n };\n\n rawImageDimensions = null;\n\n constructor(\n root,\n editor,\n currentModal,\n canShowFilePicker,\n canShowDropZone,\n currentUrl,\n image,\n ) {\n this.root = root;\n this.editor = editor;\n this.currentModal = currentModal;\n this.canShowFilePicker = canShowFilePicker;\n this.canShowDropZone = canShowDropZone;\n this.currentUrl = currentUrl;\n this.image = image;\n }\n\n init = function() {\n this.currentModal.setTitle(getString('imagedetails', 'tiny_media'));\n this.imageTypeChecked();\n this.presentationChanged();\n this.storeImageDimensions(this.image);\n this.setImageDimensions();\n this.registerEventListeners();\n };\n\n /**\n * Loads and displays a preview image based on the provided URL, and handles image loading events.\n */\n loadInsertImage = async function() {\n const templateContext = {\n elementid: this.editor.id,\n showfilepicker: this.canShowFilePicker,\n showdropzone: this.canShowDropZone,\n };\n\n Promise.all([bodyImageInsert(templateContext, this.root), footerImageInsert(templateContext, this.root)])\n .then(() => {\n const imageinsert = new ImageInsert(\n this.root,\n this.editor,\n this.currentModal,\n this.canShowFilePicker,\n this.canShowDropZone,\n );\n imageinsert.init();\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n };\n\n storeImageDimensions(image) {\n // Store dimensions of the raw image, falling back to defaults for images without dimensions (e.g. SVG).\n this.rawImageDimensions = {\n width: image.width || this.DEFAULTS.WIDTH,\n height: image.height || this.DEFAULTS.HEIGHT,\n };\n\n const getCurrentWidth = (element) => {\n if (element.value === '') {\n element.value = this.rawImageDimensions.width;\n }\n return element.value;\n };\n\n const getCurrentHeight = (element) => {\n if (element.value === '') {\n element.value = this.rawImageDimensions.height;\n }\n return element.value;\n };\n\n const widthInput = this.root.querySelector(Selectors.IMAGE.elements.width);\n const currentWidth = getCurrentWidth(widthInput);\n\n const heightInput = this.root.querySelector(Selectors.IMAGE.elements.height);\n const currentHeight = getCurrentHeight(heightInput);\n\n const preview = this.root.querySelector(Selectors.IMAGE.elements.preview);\n preview.setAttribute('src', image.src);\n preview.style.display = '';\n\n // Ensure the checkbox always in unchecked status when an image loads at first.\n const constrain = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n if (isPercentageValue(currentWidth) && isPercentageValue(currentHeight)) {\n constrain.checked = currentWidth === currentHeight;\n } else if (image.width === 0 || image.height === 0) {\n // If we don't have both dimensions of the image, we can't auto-size it, so disable control.\n constrain.disabled = 'disabled';\n } else {\n // This is the same as comparing to 3 decimal places.\n const widthRatio = Math.round(100 * parseInt(currentWidth, 10) / image.width);\n const heightRatio = Math.round(100 * parseInt(currentHeight, 10) / image.height);\n constrain.checked = widthRatio === heightRatio;\n }\n\n /**\n * Sets the selected size option based on current width and height values.\n *\n * @param {number} currentWidth - The current width value.\n * @param {number} currentHeight - The current height value.\n */\n const setSelectedSize = (currentWidth, currentHeight) => {\n if (this.rawImageDimensions.width === currentWidth &&\n this.rawImageDimensions.height === currentHeight\n ) {\n this.currentWidth = this.rawImageDimensions.width;\n this.currentHeight = this.rawImageDimensions.height;\n this.sizeChecked('original');\n } else {\n this.currentWidth = currentWidth;\n this.currentHeight = currentHeight;\n this.sizeChecked('custom');\n }\n };\n\n setSelectedSize(Number(currentWidth), Number(currentHeight));\n }\n\n /**\n * Handles the selection of image size options and updates the form inputs accordingly.\n *\n * @param {string} option - The selected image size option (\"original\" or \"custom\").\n */\n sizeChecked(option) {\n const widthInput = this.root.querySelector(Selectors.IMAGE.elements.width);\n const heightInput = this.root.querySelector(Selectors.IMAGE.elements.height);\n if (option === \"original\") {\n this.sizeOriginalChecked();\n widthInput.value = this.rawImageDimensions.width;\n heightInput.value = this.rawImageDimensions.height;\n } else if (option === \"custom\") {\n this.sizeCustomChecked();\n widthInput.value = this.currentWidth;\n heightInput.value = this.currentHeight;\n\n // If the current size is equal to the original size, then check the Keep proportion checkbox.\n if (this.currentWidth === this.rawImageDimensions.width && this.currentHeight === this.rawImageDimensions.height) {\n const constrainField = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n constrainField.checked = true;\n }\n }\n this.autoAdjustSize();\n }\n\n autoAdjustSize(forceHeight = false) {\n // If we do not know the image size, do not do anything.\n if (!this.rawImageDimensions) {\n return;\n }\n\n const widthField = this.root.querySelector(Selectors.IMAGE.elements.width);\n const heightField = this.root.querySelector(Selectors.IMAGE.elements.height);\n\n const normalizeFieldData = (fieldData) => {\n fieldData.isPercentageValue = !!isPercentageValue(fieldData.field.value);\n if (fieldData.isPercentageValue) {\n fieldData.percentValue = parseInt(fieldData.field.value, 10);\n fieldData.pixelSize = this.rawImageDimensions[fieldData.type] / 100 * fieldData.percentValue;\n } else {\n fieldData.pixelSize = parseInt(fieldData.field.value, 10);\n fieldData.percentValue = fieldData.pixelSize / this.rawImageDimensions[fieldData.type] * 100;\n }\n\n return fieldData;\n };\n\n const getKeyField = () => {\n const getValue = () => {\n if (forceHeight) {\n return {\n field: heightField,\n type: 'height',\n };\n } else {\n return {\n field: widthField,\n type: 'width',\n };\n }\n };\n\n const currentValue = getValue();\n if (currentValue.field.value === '') {\n currentValue.field.value = this.rawImageDimensions[currentValue.type];\n }\n\n return normalizeFieldData(currentValue);\n };\n\n const getRelativeField = () => {\n if (forceHeight) {\n return normalizeFieldData({\n field: widthField,\n type: 'width',\n });\n } else {\n return normalizeFieldData({\n field: heightField,\n type: 'height',\n });\n }\n };\n\n // Now update with the new values.\n const constrainField = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n if (constrainField.checked) {\n const keyField = getKeyField();\n const relativeField = getRelativeField();\n // We are keeping the image in proportion.\n // Calculate the size for the relative field.\n if (keyField.isPercentageValue) {\n // In proportion, so the percentages are the same.\n relativeField.field.value = keyField.field.value;\n relativeField.percentValue = keyField.percentValue;\n } else {\n relativeField.pixelSize = Math.round(\n keyField.pixelSize / this.rawImageDimensions[keyField.type] * this.rawImageDimensions[relativeField.type]\n );\n relativeField.field.value = relativeField.pixelSize;\n }\n }\n\n // Store the custom width and height to reuse.\n this.currentWidth = Number(widthField.value) !== this.rawImageDimensions.width ? widthField.value : this.currentWidth;\n this.currentHeight = Number(heightField.value) !== this.rawImageDimensions.height ? heightField.value : this.currentHeight;\n }\n\n /**\n * Sets the dimensions of the image preview element based on user input and constraints.\n */\n setImageDimensions = () => {\n const imagePreviewBox = this.root.querySelector(Selectors.IMAGE.elements.previewBox);\n const image = this.root.querySelector(Selectors.IMAGE.elements.preview);\n const widthField = this.root.querySelector(Selectors.IMAGE.elements.width);\n const heightField = this.root.querySelector(Selectors.IMAGE.elements.height);\n\n const updateImageDimensions = () => {\n // Get the latest dimensions of the preview box for responsiveness.\n const boxWidth = imagePreviewBox.clientWidth;\n const boxHeight = imagePreviewBox.clientHeight;\n // Get the new width and height for the image.\n const dimensions = this.fitSquareIntoBox(widthField.value, heightField.value, boxWidth, boxHeight);\n image.style.width = `${dimensions.width}px`;\n image.style.height = `${dimensions.height}px`;\n };\n // If the client size is zero, then get the new dimensions once the modal is shown.\n if (imagePreviewBox.clientWidth === 0) {\n // Call the shown event.\n this.currentModal.getRoot().on(ModalEvents.shown, () => {\n updateImageDimensions();\n });\n } else {\n updateImageDimensions();\n }\n };\n\n /**\n * Handles the selection of the \"Original Size\" option and updates the form elements accordingly.\n */\n sizeOriginalChecked() {\n this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked = true;\n this.root.querySelector(Selectors.IMAGE.elements.sizeCustom).checked = false;\n hideElements(Selectors.IMAGE.elements.properties, this.root);\n }\n\n /**\n * Handles the selection of the \"Custom Size\" option and updates the form elements accordingly.\n */\n sizeCustomChecked() {\n this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked = false;\n this.root.querySelector(Selectors.IMAGE.elements.sizeCustom).checked = true;\n showElements(Selectors.IMAGE.elements.properties, this.root);\n }\n\n /**\n * Handles changes in the image presentation checkbox and enables/disables the image alt text input accordingly.\n */\n presentationChanged() {\n const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation);\n const alt = this.root.querySelector(Selectors.IMAGE.elements.alt);\n alt.disabled = presentation.checked;\n\n // Counting the image description characters.\n this.handleKeyupCharacterCount();\n }\n\n /**\n * This function checks whether an image URL is local (within the same website's domain) or external (from an external source).\n * Depending on the result, it dynamically updates the visibility and content of HTML elements in a user interface.\n * If the image is local then we only show it's filename.\n * If the image is external then it will show full URL and it can be updated.\n */\n imageTypeChecked() {\n const regex = new RegExp(`${Config.wwwroot}`);\n\n // True if the URL is from external, otherwise false.\n const isExternalUrl = regex.test(this.currentUrl) === false;\n\n // Hide the URL input.\n hideElements(Selectors.IMAGE.elements.url, this.root);\n\n if (!isExternalUrl) {\n // Split the URL by '/' to get an array of segments.\n const segments = this.currentUrl.split('/');\n // Get the last segment, which should be the filename.\n const filename = segments.pop().split('?')[0];\n // Show the file name.\n this.setFilenameLabel(decodeURI(filename));\n } else {\n\n this.setFilenameLabel(decodeURI(this.currentUrl));\n }\n }\n\n /**\n * Set the string for the URL label element.\n *\n * @param {string} label - The label text to set.\n */\n setFilenameLabel(label) {\n const urlLabelEle = this.root.querySelector(Selectors.IMAGE.elements.fileNameLabel);\n if (urlLabelEle) {\n urlLabelEle.innerHTML = label;\n urlLabelEle.setAttribute(\"title\", label);\n }\n }\n\n toggleAriaInvalid(selectors, predicate) {\n selectors.forEach((selector) => {\n const elements = this.root.querySelectorAll(selector);\n elements.forEach((element) => element.setAttribute('aria-invalid', predicate));\n });\n }\n\n hasErrorUrlField() {\n const urlError = this.currentUrl === '';\n if (urlError) {\n showElements(Selectors.IMAGE.elements.urlWarning, this.root);\n } else {\n hideElements(Selectors.IMAGE.elements.urlWarning, this.root);\n }\n this.toggleAriaInvalid([Selectors.IMAGE.elements.url], urlError);\n\n return urlError;\n }\n\n hasErrorAltField() {\n const alt = this.root.querySelector(Selectors.IMAGE.elements.alt).value;\n const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation).checked;\n const imageAltError = alt === '' && !presentation;\n if (imageAltError) {\n showElements(Selectors.IMAGE.elements.altWarning, this.root);\n } else {\n hideElements(Selectors.IMAGE.elements.urlWaaltWarningrning, this.root);\n }\n this.toggleAriaInvalid([Selectors.IMAGE.elements.alt, Selectors.IMAGE.elements.presentation], imageAltError);\n\n return imageAltError;\n }\n\n updateWarning() {\n const urlError = this.hasErrorUrlField();\n const imageAltError = this.hasErrorAltField();\n\n return urlError || imageAltError;\n }\n\n getImageContext() {\n // Check if there are any accessibility issues.\n if (this.updateWarning()) {\n return null;\n }\n\n const classList = [];\n const constrain = this.root.querySelector(Selectors.IMAGE.elements.constrain).checked;\n const sizeOriginal = this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked;\n if (constrain || sizeOriginal) {\n // If the Auto size checkbox is checked or the Original size is checked, then apply the responsive class.\n classList.push(Selectors.IMAGE.styles.responsive);\n } else {\n // Otherwise, remove it.\n classList.pop(Selectors.IMAGE.styles.responsive);\n }\n\n return {\n url: this.currentUrl,\n alt: this.root.querySelector(Selectors.IMAGE.elements.alt).value,\n width: this.root.querySelector(Selectors.IMAGE.elements.width).value,\n height: this.root.querySelector(Selectors.IMAGE.elements.height).value,\n presentation: this.root.querySelector(Selectors.IMAGE.elements.presentation).checked,\n customStyle: this.root.querySelector(Selectors.IMAGE.elements.customStyle).value,\n classlist: classList.join(' '),\n };\n }\n\n setImage() {\n const pendingPromise = new Pending('tiny_media:setImage');\n const url = this.currentUrl;\n if (url === '') {\n return;\n }\n\n // Check if there are any accessibility issues.\n if (this.updateWarning()) {\n pendingPromise.resolve();\n return;\n }\n\n // Check for invalid width or height.\n const width = this.root.querySelector(Selectors.IMAGE.elements.width).value;\n if (!isPercentageValue(width) && isNaN(parseInt(width, 10))) {\n this.root.querySelector(Selectors.IMAGE.elements.width).focus();\n pendingPromise.resolve();\n return;\n }\n\n const height = this.root.querySelector(Selectors.IMAGE.elements.height).value;\n if (!isPercentageValue(height) && isNaN(parseInt(height, 10))) {\n this.root.querySelector(Selectors.IMAGE.elements.height).focus();\n pendingPromise.resolve();\n return;\n }\n\n Templates.render('tiny_media/image', this.getImageContext())\n .then((html) => {\n this.editor.insertContent(html);\n this.currentModal.destroy();\n pendingPromise.resolve();\n\n return html;\n })\n .catch(error => {\n window.console.log(error);\n });\n }\n\n /**\n * Deletes the image after confirming with the user and loads the insert image page.\n */\n deleteImage() {\n Notification.deleteCancelPromise(\n getString('deleteimage', 'tiny_media'),\n getString('deleteimagewarning', 'tiny_media'),\n ).then(() => {\n hideElements(Selectors.IMAGE.elements.altWarning, this.root);\n // Removing the image in the preview will bring the user to the insert page.\n this.loadInsertImage();\n return;\n }).catch(error => {\n window.console.log(error);\n });\n }\n\n registerEventListeners() {\n const submitAction = this.root.querySelector(Selectors.IMAGE.actions.submit);\n submitAction.addEventListener('click', (e) => {\n e.preventDefault();\n this.setImage();\n });\n\n const deleteImageEle = this.root.querySelector(Selectors.IMAGE.actions.deleteImage);\n deleteImageEle.addEventListener('click', () => {\n this.deleteImage();\n });\n deleteImageEle.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\") {\n this.deleteImage();\n }\n });\n\n this.root.addEventListener('change', (e) => {\n const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);\n if (presentationEle) {\n this.presentationChanged();\n }\n\n const constrainEle = e.target.closest(Selectors.IMAGE.elements.constrain);\n if (constrainEle) {\n this.autoAdjustSize();\n }\n\n const sizeOriginalEle = e.target.closest(Selectors.IMAGE.elements.sizeOriginal);\n if (sizeOriginalEle) {\n this.sizeChecked('original');\n }\n\n const sizeCustomEle = e.target.closest(Selectors.IMAGE.elements.sizeCustom);\n if (sizeCustomEle) {\n this.sizeChecked('custom');\n }\n });\n\n this.root.addEventListener('blur', (e) => {\n if (e.target.nodeType === Node.ELEMENT_NODE) {\n\n const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);\n if (presentationEle) {\n this.presentationChanged();\n }\n }\n }, true);\n\n // Character count.\n this.root.addEventListener('keyup', (e) => {\n const altEle = e.target.closest(Selectors.IMAGE.elements.alt);\n if (altEle) {\n this.handleKeyupCharacterCount();\n }\n });\n\n this.root.addEventListener('input', (e) => {\n const widthEle = e.target.closest(Selectors.IMAGE.elements.width);\n if (widthEle) {\n // Avoid empty value.\n widthEle.value = widthEle.value === \"\" ? 0 : Number(widthEle.value);\n this.autoAdjustSize();\n }\n\n const heightEle = e.target.closest(Selectors.IMAGE.elements.height);\n if (heightEle) {\n // Avoid empty value.\n heightEle.value = heightEle.value === \"\" ? 0 : Number(heightEle.value);\n this.autoAdjustSize(true);\n }\n });\n }\n\n handleKeyupCharacterCount() {\n const alt = this.root.querySelector(Selectors.IMAGE.elements.alt).value;\n const current = this.root.querySelector('#currentcount');\n current.innerHTML = alt.length;\n }\n\n /**\n * Calculates the dimensions to fit a square into a specified box while maintaining aspect ratio.\n *\n * @param {number} squareWidth - The width of the square.\n * @param {number} squareHeight - The height of the square.\n * @param {number} boxWidth - The width of the box.\n * @param {number} boxHeight - The height of the box.\n * @returns {Object} An object with the new width and height of the square to fit in the box.\n */\n fitSquareIntoBox = (squareWidth, squareHeight, boxWidth, boxHeight) => {\n if (squareWidth < boxWidth && squareHeight < boxHeight) {\n // If the square is smaller than the box, keep its dimensions.\n return {\n width: squareWidth,\n height: squareHeight,\n };\n }\n // Calculate the scaling factor based on the minimum scaling required to fit in the box.\n const widthScaleFactor = boxWidth / squareWidth;\n const heightScaleFactor = boxHeight / squareHeight;\n const minScaleFactor = Math.min(widthScaleFactor, heightScaleFactor);\n // Scale the square's dimensions based on the aspect ratio and the minimum scaling factor.\n const newWidth = squareWidth * minScaleFactor;\n const newHeight = squareHeight * minScaleFactor;\n return {\n width: newWidth,\n height: newHeight,\n };\n };\n}\n"],"names":["constructor","root","editor","currentModal","canShowFilePicker","canShowDropZone","currentUrl","image","WIDTH","HEIGHT","setTitle","imageTypeChecked","presentationChanged","storeImageDimensions","this","setImageDimensions","registerEventListeners","async","templateContext","elementid","id","showfilepicker","showdropzone","Promise","all","then","ImageInsert","init","catch","error","window","console","log","imagePreviewBox","querySelector","Selectors","IMAGE","elements","previewBox","preview","widthField","width","heightField","height","updateImageDimensions","boxWidth","clientWidth","boxHeight","clientHeight","dimensions","fitSquareIntoBox","value","style","getRoot","on","ModalEvents","shown","squareWidth","squareHeight","widthScaleFactor","heightScaleFactor","minScaleFactor","Math","min","rawImageDimensions","DEFAULTS","currentWidth","element","getCurrentWidth","currentHeight","getCurrentHeight","setAttribute","src","display","constrain","checked","disabled","widthRatio","round","parseInt","heightRatio","sizeChecked","setSelectedSize","Number","option","widthInput","heightInput","sizeOriginalChecked","sizeCustomChecked","autoAdjustSize","forceHeight","normalizeFieldData","fieldData","isPercentageValue","field","percentValue","pixelSize","type","getKeyField","currentValue","keyField","relativeField","sizeOriginal","sizeCustom","properties","presentation","alt","handleKeyupCharacterCount","isExternalUrl","RegExp","Config","wwwroot","test","url","setFilenameLabel","decodeURI","filename","split","pop","label","urlLabelEle","fileNameLabel","innerHTML","toggleAriaInvalid","selectors","predicate","forEach","selector","querySelectorAll","hasErrorUrlField","urlError","urlWarning","hasErrorAltField","imageAltError","altWarning","urlWaaltWarningrning","updateWarning","getImageContext","classList","push","styles","responsive","customStyle","classlist","join","setImage","pendingPromise","Pending","resolve","isNaN","focus","render","html","insertContent","destroy","deleteImage","deleteCancelPromise","loadInsertImage","actions","submit","addEventListener","e","preventDefault","deleteImageEle","key","target","closest","nodeType","Node","ELEMENT_NODE","widthEle","heightEle","length"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"imagedetails.min.js","sources":["../src/imagedetails.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny media plugin image details class for Moodle.\n *\n * @module tiny_media/imagedetails\n * @copyright 2024 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from './selectors';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport {ImageInsert} from 'tiny_media/imageinsert';\nimport {\n bodyImageInsert,\n footerImageInsert,\n showElements,\n hideElements,\n isPercentageValue,\n} from 'tiny_media/imagehelpers';\n\nexport class ImageDetails {\n DEFAULTS = {\n WIDTH: 160,\n HEIGHT: 160,\n };\n\n rawImageDimensions = null;\n\n constructor(\n root,\n editor,\n currentModal,\n canShowFilePicker,\n canShowDropZone,\n currentUrl,\n image,\n ) {\n this.root = root;\n this.editor = editor;\n this.currentModal = currentModal;\n this.canShowFilePicker = canShowFilePicker;\n this.canShowDropZone = canShowDropZone;\n this.currentUrl = currentUrl;\n this.image = image;\n this.toggleMaxlengthFeedbackSuffix = false;\n }\n\n init = function() {\n this.currentModal.setTitle(getString('imagedetails', 'tiny_media'));\n this.imageTypeChecked();\n this.presentationChanged();\n this.storeImageDimensions(this.image);\n this.setImageDimensions();\n this.registerEventListeners();\n };\n\n /**\n * Loads and displays a preview image based on the provided URL, and handles image loading events.\n */\n loadInsertImage = async function() {\n const templateContext = {\n elementid: this.editor.id,\n showfilepicker: this.canShowFilePicker,\n showdropzone: this.canShowDropZone,\n };\n\n Promise.all([bodyImageInsert(templateContext, this.root), footerImageInsert(templateContext, this.root)])\n .then(() => {\n const imageinsert = new ImageInsert(\n this.root,\n this.editor,\n this.currentModal,\n this.canShowFilePicker,\n this.canShowDropZone,\n );\n imageinsert.init();\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n };\n\n storeImageDimensions(image) {\n // Store dimensions of the raw image, falling back to defaults for images without dimensions (e.g. SVG).\n this.rawImageDimensions = {\n width: image.width || this.DEFAULTS.WIDTH,\n height: image.height || this.DEFAULTS.HEIGHT,\n };\n\n const getCurrentWidth = (element) => {\n if (element.value === '') {\n element.value = this.rawImageDimensions.width;\n }\n return element.value;\n };\n\n const getCurrentHeight = (element) => {\n if (element.value === '') {\n element.value = this.rawImageDimensions.height;\n }\n return element.value;\n };\n\n const widthInput = this.root.querySelector(Selectors.IMAGE.elements.width);\n const currentWidth = getCurrentWidth(widthInput);\n\n const heightInput = this.root.querySelector(Selectors.IMAGE.elements.height);\n const currentHeight = getCurrentHeight(heightInput);\n\n const preview = this.root.querySelector(Selectors.IMAGE.elements.preview);\n preview.setAttribute('src', image.src);\n preview.style.display = '';\n\n // Ensure the checkbox always in unchecked status when an image loads at first.\n const constrain = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n if (isPercentageValue(currentWidth) && isPercentageValue(currentHeight)) {\n constrain.checked = currentWidth === currentHeight;\n } else if (image.width === 0 || image.height === 0) {\n // If we don't have both dimensions of the image, we can't auto-size it, so disable control.\n constrain.disabled = 'disabled';\n } else {\n // This is the same as comparing to 3 decimal places.\n const widthRatio = Math.round(100 * parseInt(currentWidth, 10) / image.width);\n const heightRatio = Math.round(100 * parseInt(currentHeight, 10) / image.height);\n constrain.checked = widthRatio === heightRatio;\n }\n\n /**\n * Sets the selected size option based on current width and height values.\n *\n * @param {number} currentWidth - The current width value.\n * @param {number} currentHeight - The current height value.\n */\n const setSelectedSize = (currentWidth, currentHeight) => {\n if (this.rawImageDimensions.width === currentWidth &&\n this.rawImageDimensions.height === currentHeight\n ) {\n this.currentWidth = this.rawImageDimensions.width;\n this.currentHeight = this.rawImageDimensions.height;\n this.sizeChecked('original');\n } else {\n this.currentWidth = currentWidth;\n this.currentHeight = currentHeight;\n this.sizeChecked('custom');\n }\n };\n\n setSelectedSize(Number(currentWidth), Number(currentHeight));\n }\n\n /**\n * Handles the selection of image size options and updates the form inputs accordingly.\n *\n * @param {string} option - The selected image size option (\"original\" or \"custom\").\n */\n sizeChecked(option) {\n const widthInput = this.root.querySelector(Selectors.IMAGE.elements.width);\n const heightInput = this.root.querySelector(Selectors.IMAGE.elements.height);\n if (option === \"original\") {\n this.sizeOriginalChecked();\n widthInput.value = this.rawImageDimensions.width;\n heightInput.value = this.rawImageDimensions.height;\n } else if (option === \"custom\") {\n this.sizeCustomChecked();\n widthInput.value = this.currentWidth;\n heightInput.value = this.currentHeight;\n\n // If the current size is equal to the original size, then check the Keep proportion checkbox.\n if (this.currentWidth === this.rawImageDimensions.width && this.currentHeight === this.rawImageDimensions.height) {\n const constrainField = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n constrainField.checked = true;\n }\n }\n this.autoAdjustSize();\n }\n\n autoAdjustSize(forceHeight = false) {\n // If we do not know the image size, do not do anything.\n if (!this.rawImageDimensions) {\n return;\n }\n\n const widthField = this.root.querySelector(Selectors.IMAGE.elements.width);\n const heightField = this.root.querySelector(Selectors.IMAGE.elements.height);\n\n const normalizeFieldData = (fieldData) => {\n fieldData.isPercentageValue = !!isPercentageValue(fieldData.field.value);\n if (fieldData.isPercentageValue) {\n fieldData.percentValue = parseInt(fieldData.field.value, 10);\n fieldData.pixelSize = this.rawImageDimensions[fieldData.type] / 100 * fieldData.percentValue;\n } else {\n fieldData.pixelSize = parseInt(fieldData.field.value, 10);\n fieldData.percentValue = fieldData.pixelSize / this.rawImageDimensions[fieldData.type] * 100;\n }\n\n return fieldData;\n };\n\n const getKeyField = () => {\n const getValue = () => {\n if (forceHeight) {\n return {\n field: heightField,\n type: 'height',\n };\n } else {\n return {\n field: widthField,\n type: 'width',\n };\n }\n };\n\n const currentValue = getValue();\n if (currentValue.field.value === '') {\n currentValue.field.value = this.rawImageDimensions[currentValue.type];\n }\n\n return normalizeFieldData(currentValue);\n };\n\n const getRelativeField = () => {\n if (forceHeight) {\n return normalizeFieldData({\n field: widthField,\n type: 'width',\n });\n } else {\n return normalizeFieldData({\n field: heightField,\n type: 'height',\n });\n }\n };\n\n // Now update with the new values.\n const constrainField = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n if (constrainField.checked) {\n const keyField = getKeyField();\n const relativeField = getRelativeField();\n // We are keeping the image in proportion.\n // Calculate the size for the relative field.\n if (keyField.isPercentageValue) {\n // In proportion, so the percentages are the same.\n relativeField.field.value = keyField.field.value;\n relativeField.percentValue = keyField.percentValue;\n } else {\n relativeField.pixelSize = Math.round(\n keyField.pixelSize / this.rawImageDimensions[keyField.type] * this.rawImageDimensions[relativeField.type]\n );\n relativeField.field.value = relativeField.pixelSize;\n }\n }\n\n // Store the custom width and height to reuse.\n this.currentWidth = Number(widthField.value) !== this.rawImageDimensions.width ? widthField.value : this.currentWidth;\n this.currentHeight = Number(heightField.value) !== this.rawImageDimensions.height ? heightField.value : this.currentHeight;\n }\n\n /**\n * Sets the dimensions of the image preview element based on user input and constraints.\n */\n setImageDimensions = () => {\n const imagePreviewBox = this.root.querySelector(Selectors.IMAGE.elements.previewBox);\n const image = this.root.querySelector(Selectors.IMAGE.elements.preview);\n const widthField = this.root.querySelector(Selectors.IMAGE.elements.width);\n const heightField = this.root.querySelector(Selectors.IMAGE.elements.height);\n\n const updateImageDimensions = () => {\n // Get the latest dimensions of the preview box for responsiveness.\n const boxWidth = imagePreviewBox.clientWidth;\n const boxHeight = imagePreviewBox.clientHeight;\n // Get the new width and height for the image.\n const dimensions = this.fitSquareIntoBox(widthField.value, heightField.value, boxWidth, boxHeight);\n image.style.width = `${dimensions.width}px`;\n image.style.height = `${dimensions.height}px`;\n };\n // If the client size is zero, then get the new dimensions once the modal is shown.\n if (imagePreviewBox.clientWidth === 0) {\n // Call the shown event.\n this.currentModal.getRoot().on(ModalEvents.shown, () => {\n updateImageDimensions();\n });\n } else {\n updateImageDimensions();\n }\n };\n\n /**\n * Handles the selection of the \"Original Size\" option and updates the form elements accordingly.\n */\n sizeOriginalChecked() {\n this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked = true;\n this.root.querySelector(Selectors.IMAGE.elements.sizeCustom).checked = false;\n hideElements(Selectors.IMAGE.elements.properties, this.root);\n }\n\n /**\n * Handles the selection of the \"Custom Size\" option and updates the form elements accordingly.\n */\n sizeCustomChecked() {\n this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked = false;\n this.root.querySelector(Selectors.IMAGE.elements.sizeCustom).checked = true;\n showElements(Selectors.IMAGE.elements.properties, this.root);\n }\n\n /**\n * Handles changes in the image presentation checkbox and enables/disables the image alt text input accordingly.\n */\n async presentationChanged() {\n const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation);\n const alt = this.root.querySelector(Selectors.IMAGE.elements.alt);\n alt.disabled = presentation.checked;\n\n // Counting the image description characters.\n await this.handleKeyupCharacterCount();\n }\n\n /**\n * This function checks whether an image URL is local (within the same website's domain) or external (from an external source).\n * Depending on the result, it dynamically updates the visibility and content of HTML elements in a user interface.\n * If the image is local then we only show it's filename.\n * If the image is external then it will show full URL and it can be updated.\n */\n imageTypeChecked() {\n const regex = new RegExp(`${Config.wwwroot}`);\n\n // True if the URL is from external, otherwise false.\n const isExternalUrl = regex.test(this.currentUrl) === false;\n\n // Hide the URL input.\n hideElements(Selectors.IMAGE.elements.url, this.root);\n\n if (!isExternalUrl) {\n // Split the URL by '/' to get an array of segments.\n const segments = this.currentUrl.split('/');\n // Get the last segment, which should be the filename.\n const filename = segments.pop().split('?')[0];\n // Show the file name.\n this.setFilenameLabel(decodeURI(filename));\n } else {\n\n this.setFilenameLabel(decodeURI(this.currentUrl));\n }\n }\n\n /**\n * Set the string for the URL label element.\n *\n * @param {string} label - The label text to set.\n */\n setFilenameLabel(label) {\n const urlLabelEle = this.root.querySelector(Selectors.IMAGE.elements.fileNameLabel);\n if (urlLabelEle) {\n urlLabelEle.innerHTML = label;\n urlLabelEle.setAttribute(\"title\", label);\n }\n }\n\n toggleAriaInvalid(selectors, predicate) {\n selectors.forEach((selector) => {\n const elements = this.root.querySelectorAll(selector);\n elements.forEach((element) => element.setAttribute('aria-invalid', predicate));\n });\n }\n\n hasErrorUrlField() {\n const urlError = this.currentUrl === '';\n if (urlError) {\n showElements(Selectors.IMAGE.elements.urlWarning, this.root);\n } else {\n hideElements(Selectors.IMAGE.elements.urlWarning, this.root);\n }\n this.toggleAriaInvalid([Selectors.IMAGE.elements.url], urlError);\n\n return urlError;\n }\n\n hasErrorAltField() {\n const alt = this.root.querySelector(Selectors.IMAGE.elements.alt).value;\n const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation).checked;\n const imageAltError = alt === '' && !presentation;\n if (imageAltError) {\n showElements(Selectors.IMAGE.elements.altWarning, this.root);\n } else {\n hideElements(Selectors.IMAGE.elements.urlWaaltWarningrning, this.root);\n }\n this.toggleAriaInvalid([Selectors.IMAGE.elements.alt, Selectors.IMAGE.elements.presentation], imageAltError);\n\n return imageAltError;\n }\n\n updateWarning() {\n const urlError = this.hasErrorUrlField();\n const imageAltError = this.hasErrorAltField();\n\n return urlError || imageAltError;\n }\n\n getImageContext() {\n // Check if there are any accessibility issues.\n if (this.updateWarning()) {\n return null;\n }\n\n const classList = [];\n const constrain = this.root.querySelector(Selectors.IMAGE.elements.constrain).checked;\n const sizeOriginal = this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked;\n if (constrain || sizeOriginal) {\n // If the Auto size checkbox is checked or the Original size is checked, then apply the responsive class.\n classList.push(Selectors.IMAGE.styles.responsive);\n } else {\n // Otherwise, remove it.\n classList.pop(Selectors.IMAGE.styles.responsive);\n }\n\n return {\n url: this.currentUrl,\n alt: this.root.querySelector(Selectors.IMAGE.elements.alt).value,\n width: this.root.querySelector(Selectors.IMAGE.elements.width).value,\n height: this.root.querySelector(Selectors.IMAGE.elements.height).value,\n presentation: this.root.querySelector(Selectors.IMAGE.elements.presentation).checked,\n customStyle: this.root.querySelector(Selectors.IMAGE.elements.customStyle).value,\n classlist: classList.join(' '),\n };\n }\n\n setImage() {\n const pendingPromise = new Pending('tiny_media:setImage');\n const url = this.currentUrl;\n if (url === '') {\n return;\n }\n\n // Check if there are any accessibility issues.\n if (this.updateWarning()) {\n pendingPromise.resolve();\n return;\n }\n\n // Check for invalid width or height.\n const width = this.root.querySelector(Selectors.IMAGE.elements.width).value;\n if (!isPercentageValue(width) && isNaN(parseInt(width, 10))) {\n this.root.querySelector(Selectors.IMAGE.elements.width).focus();\n pendingPromise.resolve();\n return;\n }\n\n const height = this.root.querySelector(Selectors.IMAGE.elements.height).value;\n if (!isPercentageValue(height) && isNaN(parseInt(height, 10))) {\n this.root.querySelector(Selectors.IMAGE.elements.height).focus();\n pendingPromise.resolve();\n return;\n }\n\n Templates.render('tiny_media/image', this.getImageContext())\n .then((html) => {\n this.editor.insertContent(html);\n this.currentModal.destroy();\n pendingPromise.resolve();\n\n return html;\n })\n .catch(error => {\n window.console.log(error);\n });\n }\n\n /**\n * Deletes the image after confirming with the user and loads the insert image page.\n */\n deleteImage() {\n Notification.deleteCancelPromise(\n getString('deleteimage', 'tiny_media'),\n getString('deleteimagewarning', 'tiny_media'),\n ).then(() => {\n hideElements(Selectors.IMAGE.elements.altWarning, this.root);\n // Removing the image in the preview will bring the user to the insert page.\n this.loadInsertImage();\n return;\n }).catch(error => {\n window.console.log(error);\n });\n }\n\n registerEventListeners() {\n const submitAction = this.root.querySelector(Selectors.IMAGE.actions.submit);\n submitAction.addEventListener('click', (e) => {\n e.preventDefault();\n this.setImage();\n });\n\n const deleteImageEle = this.root.querySelector(Selectors.IMAGE.actions.deleteImage);\n deleteImageEle.addEventListener('click', () => {\n this.deleteImage();\n });\n deleteImageEle.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\") {\n this.deleteImage();\n }\n });\n\n this.root.addEventListener('change', async(e) => {\n const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);\n if (presentationEle) {\n await this.presentationChanged();\n }\n\n const constrainEle = e.target.closest(Selectors.IMAGE.elements.constrain);\n if (constrainEle) {\n this.autoAdjustSize();\n }\n\n const sizeOriginalEle = e.target.closest(Selectors.IMAGE.elements.sizeOriginal);\n if (sizeOriginalEle) {\n this.sizeChecked('original');\n }\n\n const sizeCustomEle = e.target.closest(Selectors.IMAGE.elements.sizeCustom);\n if (sizeCustomEle) {\n this.sizeChecked('custom');\n }\n });\n\n this.root.addEventListener('blur', async(e) => {\n if (e.target.nodeType === Node.ELEMENT_NODE) {\n\n const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);\n if (presentationEle) {\n await this.presentationChanged();\n }\n }\n }, true);\n\n // Character count.\n this.root.addEventListener('keyup', async(e) => {\n const altEle = e.target.closest(Selectors.IMAGE.elements.alt);\n if (altEle) {\n await this.handleKeyupCharacterCount();\n }\n });\n\n this.root.addEventListener('input', (e) => {\n const widthEle = e.target.closest(Selectors.IMAGE.elements.width);\n if (widthEle) {\n // Avoid empty value.\n widthEle.value = widthEle.value === \"\" ? 0 : Number(widthEle.value);\n this.autoAdjustSize();\n }\n\n const heightEle = e.target.closest(Selectors.IMAGE.elements.height);\n if (heightEle) {\n // Avoid empty value.\n heightEle.value = heightEle.value === \"\" ? 0 : Number(heightEle.value);\n this.autoAdjustSize(true);\n }\n });\n }\n\n async handleKeyupCharacterCount() {\n const altField = this.root.querySelector(Selectors.IMAGE.elements.alt);\n const alt = altField.value;\n const current = this.root.querySelector('#currentcount');\n current.innerHTML = alt.length;\n const maxLength = altField.getAttribute('maxlength');\n const maxLengthFeedback = document.getElementById('maxlength_feedback');\n if (alt.length >= maxLength) {\n maxLengthFeedback.textContent = await getString('maxlengthreached', 'core', maxLength);\n\n // Clever (or hacky?;p) way to ensure that the feedback message is announced to screen readers.\n const suffix = this.toggleMaxlengthFeedbackSuffix ? '' : '.';\n maxLengthFeedback.textContent += suffix;\n this.toggleMaxlengthFeedbackSuffix = !this.toggleMaxlengthFeedbackSuffix;\n\n // Clear the feedback message after 4 seconds. This is similar to the default timeout of toast messages\n // before disappearing from view. It is important to clear the message to prevent screen reader users from navigating\n // into this region and avoiding confusion.\n setTimeout(() => {\n maxLengthFeedback.textContent = '';\n }, 4000);\n }\n }\n\n /**\n * Calculates the dimensions to fit a square into a specified box while maintaining aspect ratio.\n *\n * @param {number} squareWidth - The width of the square.\n * @param {number} squareHeight - The height of the square.\n * @param {number} boxWidth - The width of the box.\n * @param {number} boxHeight - The height of the box.\n * @returns {Object} An object with the new width and height of the square to fit in the box.\n */\n fitSquareIntoBox = (squareWidth, squareHeight, boxWidth, boxHeight) => {\n if (squareWidth < boxWidth && squareHeight < boxHeight) {\n // If the square is smaller than the box, keep its dimensions.\n return {\n width: squareWidth,\n height: squareHeight,\n };\n }\n // Calculate the scaling factor based on the minimum scaling required to fit in the box.\n const widthScaleFactor = boxWidth / squareWidth;\n const heightScaleFactor = boxHeight / squareHeight;\n const minScaleFactor = Math.min(widthScaleFactor, heightScaleFactor);\n // Scale the square's dimensions based on the aspect ratio and the minimum scaling factor.\n const newWidth = squareWidth * minScaleFactor;\n const newHeight = squareHeight * minScaleFactor;\n return {\n width: newWidth,\n height: newHeight,\n };\n };\n}\n"],"names":["constructor","root","editor","currentModal","canShowFilePicker","canShowDropZone","currentUrl","image","WIDTH","HEIGHT","setTitle","imageTypeChecked","presentationChanged","storeImageDimensions","this","setImageDimensions","registerEventListeners","async","templateContext","elementid","id","showfilepicker","showdropzone","Promise","all","then","ImageInsert","init","catch","error","window","console","log","imagePreviewBox","querySelector","Selectors","IMAGE","elements","previewBox","preview","widthField","width","heightField","height","updateImageDimensions","boxWidth","clientWidth","boxHeight","clientHeight","dimensions","fitSquareIntoBox","value","style","getRoot","on","ModalEvents","shown","squareWidth","squareHeight","widthScaleFactor","heightScaleFactor","minScaleFactor","Math","min","toggleMaxlengthFeedbackSuffix","rawImageDimensions","DEFAULTS","currentWidth","element","getCurrentWidth","currentHeight","getCurrentHeight","setAttribute","src","display","constrain","checked","disabled","widthRatio","round","parseInt","heightRatio","sizeChecked","setSelectedSize","Number","option","widthInput","heightInput","sizeOriginalChecked","sizeCustomChecked","autoAdjustSize","forceHeight","normalizeFieldData","fieldData","isPercentageValue","field","percentValue","pixelSize","type","getKeyField","currentValue","keyField","relativeField","sizeOriginal","sizeCustom","properties","presentation","alt","handleKeyupCharacterCount","isExternalUrl","RegExp","Config","wwwroot","test","url","setFilenameLabel","decodeURI","filename","split","pop","label","urlLabelEle","fileNameLabel","innerHTML","toggleAriaInvalid","selectors","predicate","forEach","selector","querySelectorAll","hasErrorUrlField","urlError","urlWarning","hasErrorAltField","imageAltError","altWarning","urlWaaltWarningrning","updateWarning","getImageContext","classList","push","styles","responsive","customStyle","classlist","join","setImage","pendingPromise","Pending","resolve","isNaN","focus","render","html","insertContent","destroy","deleteImage","deleteCancelPromise","loadInsertImage","actions","submit","addEventListener","e","preventDefault","deleteImageEle","key","target","closest","nodeType","Node","ELEMENT_NODE","widthEle","heightEle","altField","length","maxLength","getAttribute","maxLengthFeedback","document","getElementById","textContent","suffix","setTimeout"],"mappings":"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"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/media/amd/src/imagedetails.js b/lib/editor/tiny/plugins/media/amd/src/imagedetails.js
index c20754bc81c..91e58f7bb5a 100644
--- a/lib/editor/tiny/plugins/media/amd/src/imagedetails.js
+++ b/lib/editor/tiny/plugins/media/amd/src/imagedetails.js
@@ -61,6 +61,7 @@ export class ImageDetails {
this.canShowDropZone = canShowDropZone;
this.currentUrl = currentUrl;
this.image = image;
+ this.toggleMaxlengthFeedbackSuffix = false;
}
init = function() {
@@ -326,13 +327,13 @@ export class ImageDetails {
/**
* Handles changes in the image presentation checkbox and enables/disables the image alt text input accordingly.
*/
- presentationChanged() {
+ async presentationChanged() {
const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation);
const alt = this.root.querySelector(Selectors.IMAGE.elements.alt);
alt.disabled = presentation.checked;
// Counting the image description characters.
- this.handleKeyupCharacterCount();
+ await this.handleKeyupCharacterCount();
}
/**
@@ -519,10 +520,10 @@ export class ImageDetails {
}
});
- this.root.addEventListener('change', (e) => {
+ this.root.addEventListener('change', async(e) => {
const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);
if (presentationEle) {
- this.presentationChanged();
+ await this.presentationChanged();
}
const constrainEle = e.target.closest(Selectors.IMAGE.elements.constrain);
@@ -541,21 +542,21 @@ export class ImageDetails {
}
});
- this.root.addEventListener('blur', (e) => {
+ this.root.addEventListener('blur', async(e) => {
if (e.target.nodeType === Node.ELEMENT_NODE) {
const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);
if (presentationEle) {
- this.presentationChanged();
+ await this.presentationChanged();
}
}
}, true);
// Character count.
- this.root.addEventListener('keyup', (e) => {
+ this.root.addEventListener('keyup', async(e) => {
const altEle = e.target.closest(Selectors.IMAGE.elements.alt);
if (altEle) {
- this.handleKeyupCharacterCount();
+ await this.handleKeyupCharacterCount();
}
});
@@ -576,10 +577,28 @@ export class ImageDetails {
});
}
- handleKeyupCharacterCount() {
- const alt = this.root.querySelector(Selectors.IMAGE.elements.alt).value;
+ async handleKeyupCharacterCount() {
+ const altField = this.root.querySelector(Selectors.IMAGE.elements.alt);
+ const alt = altField.value;
const current = this.root.querySelector('#currentcount');
current.innerHTML = alt.length;
+ const maxLength = altField.getAttribute('maxlength');
+ const maxLengthFeedback = document.getElementById('maxlength_feedback');
+ if (alt.length >= maxLength) {
+ maxLengthFeedback.textContent = await getString('maxlengthreached', 'core', maxLength);
+
+ // Clever (or hacky?;p) way to ensure that the feedback message is announced to screen readers.
+ const suffix = this.toggleMaxlengthFeedbackSuffix ? '' : '.';
+ maxLengthFeedback.textContent += suffix;
+ this.toggleMaxlengthFeedbackSuffix = !this.toggleMaxlengthFeedbackSuffix;
+
+ // Clear the feedback message after 4 seconds. This is similar to the default timeout of toast messages
+ // before disappearing from view. It is important to clear the message to prevent screen reader users from navigating
+ // into this region and avoiding confusion.
+ setTimeout(() => {
+ maxLengthFeedback.textContent = '';
+ }, 4000);
+ }
}
/**
diff --git a/lib/editor/tiny/plugins/media/templates/insert_image_modal_details.mustache b/lib/editor/tiny/plugins/media/templates/insert_image_modal_details.mustache
index a589371be0d..833dacc42fd 100644
--- a/lib/editor/tiny/plugins/media/templates/insert_image_modal_details.mustache
+++ b/lib/editor/tiny/plugins/media/templates/insert_image_modal_details.mustache
@@ -56,6 +56,7 @@
0
/ 125
+