AutorÃa | Ultima modificación | Ver Log |
{"version":3,"file":"embedhandler.min.js","sources":["../../src/embed/embedhandler.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 embed handler class.\n *\n * This handles anything that embed requires like:\n * - Calling the media preview in embedPreview.\n * - Loading the embed insert.\n * - Getting selected media data
.\n * - Handles url and repository uploads.\n * - Reset embed insert when embed preview is deleted.\n * - Handles media embedding into tiny and etc.\n *\n * @module tiny_media/embed/embedhandler\n * @copyright 2024 Stevani Andolo <stevani@hotmail.com.au>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Selectors from \"../selectors\";\nimport {EmbedInsert} from './embedinsert';\nimport {\n body,\n footer,\n setPropertiesFromData,\n isValidUrl,\n stopMediaLoading,\n startMediaLoading,\n} from '../helpers';\nimport * as ModalEvents from 'core/modal_events';\nimport {displayFilepicker} from 'editor_tiny/utils';\nimport {\n insertMediaTemplateContext,\n getHelpStrings,\n prepareMoodleLang,\n getMoodleLangObj,\n hasAudioVideoAttr,\n insertMediaThumbnailTemplateContext,\n} from \"./embedhelpers\";\nimport Templates from 'core/templates';\nimport {EmbedThumbnailInsert} from './embedthumbnailinsert';\n\nexport class EmbedHandler {\n\n
constructor(data) {\n setPropertiesFromData(this, data); // Creates dynamic properties based on \"data\" param.\n }\n\n /**\n * Load the media insert dialogue.\n *\n * @param {object} templateContext Object template context\n */\n loadTemplatePromise = (templateContext) => {\n templateContext.elementid = this.editor.id;\n templateContext.bodyTemplate = Selectors.EMBED.template.body.insertMediaBody;\n templateContext.footerTemplate = Selectors.EMBED.template.footer.insertMediaFooter;\n templateContext.selector = Selectors.EMBED.type;\n\n Promise.all([body(templateContext, this.root), footer(templateContext, this.root)])\n .then(() => {\n (new EmbedInsert(this)).init();\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n };\n\n /**\n * Load the media thumbnail insert dialogue.\n *\n * @param {object} templateContext
Object template context\n * @param {HTMLElement} root\n * @param {object} mediaData\n */\n loadInsertThumbnailTemplatePromise = async(templateContext, root, mediaData) => {\n Promise.all([body(templateContext, root.root), footer(templateContext, root.root)])\n .then(() => {\n if (!this.currentModal.insertMediaModal) {\n this.currentModal.insertMediaModal = this.currentModal;\n }\n\n if (root.uploadThumbnailModal) {\n this.currentModal.uploadThumbnailModal = root.uploadThumbnailModal;\n }\n\n this.thumbnailModalRoot = root.root;\n (new EmbedThumbnailInsert(this)).init(mediaData);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n };\n\n /**\n * Loads the media preview dialogue.\n *\n * @param {object} embedPreview Object of embedPreview\n * @para
m {object} templateContext Object of template context\n */\n loadMediaDetails = async(embedPreview, templateContext) => {\n Promise.all([body(templateContext, this.root), footer(templateContext, this.root)])\n .then(() => {\n embedPreview.init();\n return;\n })\n .catch(error => {\n stopMediaLoading(this.root, Selectors.EMBED.type);\n window.console.log(error);\n });\n };\n\n /**\n * Reset the media/thumbnail insert modal form.\n *\n * @param {boolean} isMediaInsert Is current state media insert or thumbnail insert?\n */\n resetUploadForm = (isMediaInsert = true) => {\n if (isMediaInsert) {\n this.newMediaLink = false;\n this.fetchedMediaLinkTitle = null;\n this.resetCurrentMediaData();\n this.loadTemplatePromise(insertMediaTemplateContext(this));\n } else {\n this.loadInsertThumbnailTemplatePromise(
\n insertMediaThumbnailTemplateContext(this), // Get template context for creating media thumbnail.\n {root: this.thumbnailModalRoot}, // Required root elements.\n this.mediaData // Get current media data.\n );\n }\n };\n\n /**\n * Get selected media data.\n *\n * @returns {null|object}\n */\n getMediaProperties = () => {\n const media = this.selectedMedia;\n if (!media) {\n return null;\n }\n\n const tracks = {\n subtitles: [],\n captions: [],\n descriptions: [],\n chapters: [],\n metadata: []\n };\n const sources = [];\n\n media.querySelectorAll('track').forEach((track) => {\n tracks[track.getAttribute('kind')].push({\n src: track.getAttribute('src'),\n srclang: track.getAttribute('srclang'),\n label: track.getAttribute('label'),\n defau
ltTrack: hasAudioVideoAttr(track, 'default')\n });\n });\n\n media.querySelectorAll('source').forEach((source) => {\n sources.push(source.src);\n });\n const title = media.getAttribute('title') ?? media.textContent;\n\n return {\n type: this.mediaType,\n sources,\n poster: media.getAttribute('poster'),\n title: title ? title.trim() : false,\n width: media.getAttribute('width'),\n height: media.getAttribute('height'),\n autoplay: hasAudioVideoAttr(media, 'autoplay'),\n loop: hasAudioVideoAttr(media, 'loop'),\n muted: hasAudioVideoAttr(media, 'muted'),\n controls: hasAudioVideoAttr(media, 'controls'),\n tracks,\n };\n };\n\n /**\n * Get selected media data.\n *\n * @returns {object}\n */\n getCurrentEmbedData = () => {\n const properties = this.getMediaProperties();\n if (!properties || thi
s.newMediaLink) {\n return {media: {}};\n }\n\n const processedProperties = {};\n processedProperties.media = properties;\n processedProperties.link = false;\n\n return processedProperties;\n };\n\n /**\n * Get help strings for media subtitles and captions.\n *\n * @returns {null|object}\n */\n getHelpStrings = async() => {\n if (!this.helpStrings) {\n this.helpStrings = await getHelpStrings();\n }\n\n return this.helpStrings;\n };\n\n /**\n * Set template context for insert media dialogue.\n *\n * @param {object} data Object of media data\n * @returns {object}\n */\n getTemplateContext = async(data) => {\n const languages = prepareMoodleLang(this.editor);\n const helpIcons = Array.from(Object.entries(await this.getHelpStrings())).forEach(([key, text]) => {\n data[`${key.toLowerCase()}helpicon`] = {text};\n });\n\n return Object.assign({},
{\n elementid: this.editor.getElement().id,\n showFilePickerTrack: this.canShowFilePickerTrack,\n langsInstalled: languages.installed,\n langsAvailable: languages.available,\n media: true,\n isUpdating: this.isUpdating,\n }, data, helpIcons);\n };\n\n /**\n * Set and get media template context.\n *\n * @param {null|object} data Null or object of media data\n * @returns {Promise<object>} A promise that resolves template context.\n */\n getMediaTemplateContext = async(data = null) => {\n if (!data) {\n data = Object.assign({}, this.getCurrentEmbedData());\n } else {\n if (data.hasOwnProperty('isUpdating')) {\n this.isUpdating = data.isUpdating;\n } else {\n this.isUpdating = Object.keys(data).length > 1;\n }\n }\n return await this.getTemplateContext(data);\n };\n\n /**\n * Handles changes in the med
ia URL input field and loads a preview of the media if the URL has changed.\n */\n urlChanged() {\n const url = this.root.querySelector(Selectors.EMBED.elements.fromUrl).value;\n if (url && url !== this.currentUrl) {\n // Set to null on new url change.\n this.mediaType = null;\n\n // Flag as new media link insert.\n this.newMediaLink = true;\n this.loadMediaPreview(url);\n }\n }\n\n /**\n * Load the media preview dialogue.\n *\n * @param {string} url String of media url\n */\n loadMediaPreview = (url) => {\n (new EmbedInsert(this)).loadMediaPreview(url);\n };\n\n /**\n * Callback for file picker that previews the media or add the captions and subtitles.\n *\n * @param {object} params Object of media url and etc\n * @param {html} element Selected element.\n * @param {string} fpType Caption type.\n */\n trackFilePickerCallback(params, element, fpType) {\n if
(params.url !== '') {\n const tabPane = element.closest('.tab-pane');\n if (tabPane) {\n element.closest(Selectors.EMBED.elements.source).querySelector(Selectors.EMBED.elements.url).value = params.url;\n\n if (fpType === 'subtitle') {\n // If the file is subtitle file. We need to match the language and label for that file.\n const subtitleLang = params.file.split('.vtt')[0].split('-').slice(-1)[0];\n const langObj = getMoodleLangObj(subtitleLang, this.editor);\n if (langObj) {\n const track = element.closest(Selectors.EMBED.elements.track);\n track.querySelector(Selectors.EMBED.elements.trackLabel).value = langObj.lang.trim();\n track.querySelector(Selectors.EMBED.elements.trackLang).value = langObj.code;\n }\n }\n } else {\n // Flag as new file upload.\n
this.newFileUpload = true;\n this.resetCurrentMediaData();\n this.loadMediaPreview(params.url);\n }\n }\n }\n\n /**\n * Reset current media data.\n */\n resetCurrentMediaData = () => {\n // Reset the value of the following props.\n this.media = {};\n this.mediaType = null;\n this.selectedMedia = null;\n };\n\n /**\n * Add new html track element.\n *\n * @param {html} element\n */\n addTrackComponent(element) {\n const trackElement = element.closest(Selectors.EMBED.elements.track);\n const clone = trackElement.cloneNode(true);\n\n trackElement.querySelector('.removecomponent-wrapper').classList.remove('hidden');\n trackElement.querySelector('.addcomponent-wrapper').classList.add('hidden');\n trackElement.parentNode.insertBefore(clone, trackElement.nextSibling);\n }\n\n /**\n * Remove added html track element.\n *\n * @param {
html} element\n */\n removeTrackComponent(element) {\n const sourceElement = element.closest(Selectors.EMBED.elements.track);\n sourceElement.remove();\n }\n\n /**\n * Get picker type based on the selected element.\n *\n * @param {html} element Selected element\n * @returns {string}\n */\n getFilePickerTypeFromElement = (element) => {\n if (element.closest(Selectors.EMBED.elements.posterSource)) {\n return 'image';\n }\n if (element.closest(Selectors.EMBED.elements.trackSource)) {\n return 'subtitle';\n }\n\n return 'media';\n };\n\n /**\n * Get captions/subtitles type.\n *\n * @param {html} tabPane\n * @returns {string}\n */\n getTrackTypeFromTabPane = (tabPane) => {\n return tabPane.getAttribute('data-track-kind');\n };\n\n /**\n * Handle click events.\n *\n * @param {html} e Selected element\n */\n clickHandler = async(e) => {\n cons
t element = e.target;\n\n // Handle repository browsing.\n const mediaBrowser = element.closest(Selectors.EMBED.actions.mediaBrowser);\n if (mediaBrowser) {\n e.preventDefault();\n const fpType = this.getFilePickerTypeFromElement(element);\n const params = await displayFilepicker(this.editor, fpType);\n this.trackFilePickerCallback(params, element, fpType);\n }\n\n // Handles add media url.\n const addUrlEle = e.target.closest(Selectors.EMBED.actions.addUrl);\n if (addUrlEle) {\n startMediaLoading(this.root, Selectors.EMBED.type);\n this.urlChanged();\n }\n\n // Handles adding tracks.\n const addComponentTrackAction = element.closest(Selectors.EMBED.elements.track + ' .addcomponent');\n if (addComponentTrackAction) {\n e.preventDefault();\n this.addTrackComponent(element);\n }\n\n // Handles removing added tracks.\n const rem
oveComponentTrackAction = element.closest(Selectors.EMBED.elements.track + ' .removecomponent');\n if (removeComponentTrackAction) {\n e.preventDefault();\n this.removeTrackComponent(element);\n }\n\n // Only allow one track per tab to be selected as \"default\".\n const trackDefaultAction = element.closest(Selectors.EMBED.elements.trackDefault);\n if (trackDefaultAction && trackDefaultAction.checked) {\n const getKind = (el) => this.getTrackTypeFromTabPane(el.parentElement.closest('.tab-pane'));\n\n element.parentElement\n .closest('.tab-content')\n .querySelectorAll(Selectors.EMBED.elements.trackDefault)\n .forEach((select) => {\n if (select !== element && getKind(element) === getKind(select)) {\n select.checked = false;\n }\n });\n }\n };\n\n /**\n * Enables or disables the URL-related button
s in the footer based on the current URL and input value.\n *\n * @param {html} input Url input field\n * @param {object} root\n */\n toggleUrlButton(input, root) {\n const url = input.value;\n const addUrl = root.querySelector(Selectors.EMBED.actions.addUrl);\n addUrl.disabled = !(url !== \"\" && isValidUrl(url));\n }\n\n /**\n * Get media html to be inserted or updated into tiny.\n *\n * @param {html} form Selected element\n * @returns {string} String of html\n */\n getMediaHTML = (form) => {\n this.mediaType = this.root.querySelector(Selectors.EMBED.elements.mediaPreviewContainer).dataset.mediaType;\n const tabContent = form.querySelector('.tab-content');\n const callback = 'getMediaHTML' + this.mediaType[0].toUpperCase() + this.mediaType.substr(1);\n return this[callback](tabContent);\n };\n\n /**\n * Get media as link.\n *\n * @returns {string} String of html.\n */\n getMediaHTMLLink
() {\n const mediaPreviewContainer = document.querySelector(Selectors.EMBED.elements.mediaPreviewContainer);\n const context = {\n name: document.querySelector(Selectors.EMBED.elements.title).value ?? mediaPreviewContainer.dataset.originalUrl,\n url: mediaPreviewContainer.dataset.originalUrl || false\n };\n\n return context.url ? Templates.renderForPromise('tiny_media/embed/embed_media_link', context) : '';\n }\n\n /**\n * Get media as video.\n *\n * @param {html} tab Selected element\n * @returns {string} String of html.\n */\n getMediaHTMLVideo = (tab) => {\n const details = document.querySelector(Selectors.EMBED.elements.mediaDetailsBody);\n const context = this.getContextForMediaHTML(tab, details);\n context.width = details.querySelector(Selectors.EMBED.elements.width).value || false;\n context.height = details.querySelector(Selectors.EMBED.elements.height).value || false;\n\n const mediaPrev
iewContainer = details.querySelector(Selectors.EMBED.elements.mediaPreviewContainer);\n context.poster = mediaPreviewContainer.dataset.mediaPoster || false;\n return context.sources ? Templates.renderForPromise('tiny_media/embed/embed_media_video', context) : '';\n };\n\n /**\n * Get media as audio.\n *\n * @param {html} tab Selected element\n * @returns {string} String of html.\n */\n getMediaHTMLAudio = (tab) => {\n const details = document.querySelector(Selectors.EMBED.elements.mediaDetailsBody);\n const context = this.getContextForMediaHTML(tab, details);\n return context.sources.length ? Templates.renderForPromise('tiny_media/embed/embed_media_audio', context) : '';\n };\n\n /**\n * Get previewed media data.\n *\n * @param {html} tab Selected element\n * @param {html} details Selected element\n * @returns {object}\n */\n getContextForMediaHTML = (tab, details) => {\n const tracks = Array.from(tab.quer
ySelectorAll(Selectors.EMBED.elements.track)).map(track => {\n const langTrack = track.querySelector(Selectors.EMBED.elements.trackLang);\n const selectedLangTrack = langTrack.options[langTrack.selectedIndex];\n\n return {\n track: track.querySelector(Selectors.EMBED.elements.trackSource + ' ' + Selectors.EMBED.elements.url).value,\n kind: this.getTrackTypeFromTabPane(track.closest('.tab-pane')),\n label: track.querySelector(Selectors.EMBED.elements.trackLabel).value || langTrack.value,\n srclang: selectedLangTrack.dataset.languageCode ?? false,\n defaultTrack: track.querySelector(Selectors.EMBED.elements.trackDefault).checked ? \"true\" : null\n };\n }).filter((track) => !!track.track);\n\n const mediaPreviewContainer = details.querySelector(Selectors.EMBED.elements.mediaPreviewContainer);\n let sources = mediaPreviewContainer.dataset.originalUrl ?? null;\n\n //
Let's check if media has more than one sources.\n if (this.alternativeSources) {\n // Always update the first item in this.alternativeSources to the new one.\n this.alternativeSources[0] = sources;\n // Override the sources to have all the updated sources.\n sources = this.alternativeSources;\n }\n\n const title = details.querySelector(Selectors.EMBED.elements.title).value;\n // Remove data-original-url attribute once it's extracted.\n mediaPreviewContainer.removeAttribute('data-original-url');\n\n const templateContext = {\n sources,\n tracks,\n showControls: details.querySelector(Selectors.EMBED.elements.mediaControl).checked,\n autoplay: details.querySelector(Selectors.EMBED.elements.mediaAutoplay).checked,\n muted: details.querySelector(Selectors.EMBED.elements.mediaMute).checked,\n loop: details.querySelector(Selectors.EMBED.elements.mediaLoop).checked,\
n title: title !== '' ? title.trim() : false,\n };\n\n // Add description prop to templateContext if media type is \"link\".\n if (this.mediaType === 'link') {\n // Let's form an alternative title.\n templateContext.description = Array.isArray(sources) ? sources[0] : sources;\n }\n\n return templateContext;\n };\n\n /**\n * Handle the insert/update media in tiny editor.\n *\n * @param {event} event\n * @param {object} modal Object of current modal\n */\n handleDialogueSubmission = async(event, modal) => {\n const {html} = await this.getMediaHTML(modal.getRoot()[0]);\n if (html) {\n if (this.isUpdating) {\n this.selectedMedia.outerHTML = html;\n this.isUpdating = false;\n } else {\n this.editor.insertContent(html);\n }\n }\n };\n\n /**\n * Register insert media modal elements' events.\n */\n register
EventListeners = async() => {\n // Handles click events for insert media modal.\n if (this.canShowFilePickerTrack) {\n this.root.addEventListener('click', this.clickHandler.bind(this));\n }\n\n // Handles media adding using url input.\n this.root.addEventListener('input', (e) => {\n const urlEle = e.target.closest(Selectors.EMBED.elements.fromUrl);\n if (urlEle) {\n this.toggleUrlButton(urlEle, this.root);\n }\n });\n\n // Destroy created modal when it's closed.\n this.modalRoot.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n\n // Handles media insert to editor.\n this.modalRoot.on(ModalEvents.save, this.handleDialogueSubmission.bind(this));\n };\n}\n"],"names":["constructor","data","templateContext","elementid","this","editor","id","bodyTemplate","Selectors","EMBED","template","body","insertMediaBody","footerTemplate","footer","insertMedi
aFooter","selector","type","Promise","all","root","then","EmbedInsert","init","catch","error","window","console","log","async","mediaData","currentModal","insertMediaModal","uploadThumbnailModal","thumbnailModalRoot","EmbedThumbnailInsert","embedPreview","isMediaInsert","_this","newMediaLink","fetchedMediaLinkTitle","resetCurrentMediaData","loadTemplatePromise","loadInsertThumbnailTemplatePromise","media","selectedMedia","tracks","subtitles","captions","descriptions","chapters","metadata","sources","querySelectorAll","forEach","track","getAttribute","push","src","srclang","label","defaultTrack","source","title","textContent","mediaType","poster","trim","width","height","autoplay","loop","muted","controls","properties","getMediaProperties","processedProperties","link","helpStrings","languages","helpIcons","Array","from","Object","entries","getHelpStrings","_ref","key","text","toLowerCase","assign","getElement","showFilePickerTrack","canShowFilePickerTrack","langsInstalled","installed","langsAvailable","availa
ble","isUpdating","hasOwnProperty","keys","length","getCurrentEmbedData","getTemplateContext","url","loadMediaPreview","element","closest","elements","posterSource","trackSource","tabPane","e","target","actions","mediaBrowser","preventDefault","fpType","getFilePickerTypeFromElement","params","trackFilePickerCallback","addUrl","urlChanged","addTrackComponent","removeTrackComponent","trackDefaultAction","trackDefault","checked","getKind","el","getTrackTypeFromTabPane","parentElement","select","form","querySelector","mediaPreviewContainer","dataset","tabContent","toUpperCase","substr","tab","details","document","mediaDetailsBody","context","getContextForMediaHTML","value","mediaPoster","Templates","renderForPromise","map","langTrack","trackLang","selectedLangTrack","options","selectedIndex","kind","trackLabel","languageCode","filter","originalUrl","alternativeSources","removeAttribute","showControls","mediaControl","mediaAutoplay","mediaMute","mediaLoop","description","isArray","event","modal","html","getMediaH
TML","getRoot","outerHTML","insertContent","addEventListener","clickHandler","bind","urlEle","fromUrl","toggleUrlButton","modalRoot","on","ModalEvents","hidden","destroy","save","handleDialogueSubmission","currentUrl","subtitleLang","file","split","slice","langObj","lang","code","newFileUpload","trackElement","clone","cloneNode","classList","remove","add","parentNode","insertBefore","nextSibling","input","disabled","getMediaHTMLLink","name"],"mappings":"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"}