Rev 1 | AutorÃa | Comparar con el anterior | Ultima modificación | Ver Log |
{"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny media plugin image details class for Moodle.\n *\n * @module tiny_media/imagedetails\n * @copyright 2024 Meirza <meirza.arson@moodle.com>\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 {MediaBase} from './mediabase';\nimport {\n body,\n footer,\n hideElements,\n showElements,\n isPercentageValue,\n} from './helpers';\n\nexport class ImageDetails extends MediaBase {\n DEFAULTS = {\n WIDTH: 160,\n HEIGHT: 160,\n };\n\n selectorType = Selectors.IMAGE.type;\n\n mediaDimensions = null;\n\n constructor(\n root,\n editor,\n currentModal,\n canShowFilePicker,\n canShowDropZone,\n currentUrl,\n image,\n ) {\n super();\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 bodyTemplate: Selectors.IMAGE.template.body.insertImageBody,\n footerTemplate: Selectors.IMAGE.template.footer.insertImageFooter,\n selecto
r: Selectors.IMAGE.type,\n };\n\n Promise.all([body(templateContext, this.root), footer(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.mediaDimensions = {\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.mediaDimensions.width;\n }\n return element.value;\n };\n\n const getCurrentHeight = (element) => {\n if (element.value === '') {\n element.value = this.mediaDimensions.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) && isP
ercentageValue(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.mediaDimension
s.width === currentWidth &&\n this.mediaDimensions.height === currentHeight\n ) {\n this.currentWidth = this.mediaDimensions.width;\n this.currentHeight = this.mediaDimensions.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 * 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.querySele
ctor(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 changes in the ima
ge 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(\"titl
e\", 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.altWarning, 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 appl
y 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 retur
n 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 = widt
hEle.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) w
ay 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 b
ox.\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 r
eturn {\n width: newWidth,\n height: newHeight,\n };\n };\n}\n"],"names":["ImageDetails","MediaBase","constructor","root","editor","currentModal","canShowFilePicker","canShowDropZone","currentUrl","image","WIDTH","HEIGHT","Selectors","IMAGE","type","setTitle","imageTypeChecked","presentationChanged","storeImageDimensions","this","setImageDimensions","registerEventListeners","async","templateContext","elementid","id","showfilepicker","showdropzone","bodyTemplate","template","body","insertImageBody","footerTemplate","footer","insertImageFooter","selector","Promise","all","then","ImageInsert","init","catch","error","window","console","log","imagePreviewBox","querySelector","elements","previewBox","preview","widthField","width","heightField","height","updateImageDimensions","boxWidth","clientWidth","boxHeight","clientHeight","dimensions","fitSquareIntoBox","value","style","getRoot","on","ModalEvents","shown","squareWidth","squareHeight","widthScaleFactor","heightScaleFactor","minScale
Factor","Math","min","toggleMaxlengthFeedbackSuffix","mediaDimensions","DEFAULTS","currentWidth","element","getCurrentWidth","currentHeight","getCurrentHeight","setAttribute","src","display","constrain","checked","disabled","widthRatio","round","parseInt","heightRatio","sizeChecked","setSelectedSize","Number","presentation","alt","handleKeyupCharacterCount","isExternalUrl","RegExp","Config","wwwroot","test","url","setFilenameLabel","decodeURI","filename","split","pop","label","urlLabelEle","fileNameLabel","innerHTML","toggleAriaInvalid","selectors","predicate","forEach","querySelectorAll","hasErrorUrlField","urlError","urlWarning","hasErrorAltField","imageAltError","altWarning","updateWarning","getImageContext","classList","sizeOriginal","push","styles","responsive","customStyle","classlist","join","setImage","pendingPromise","Pending","resolve","isNaN","focus","render","html","insertContent","destroy","deleteImage","deleteCancelPromise","loadInsertImage","actions","submit","addEventListener","e","preventDef
ault","deleteImageEle","key","target","closest","autoAdjustSize","sizeCustom","nodeType","Node","ELEMENT_NODE","widthEle","heightEle","altField","length","maxLength","getAttribute","maxLengthFeedback","document","getElementById","textContent","suffix","setTimeout"],"mappings":"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"}