Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"version":3,"file":"embed.min.js","sources":["../src/embed.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 class for Moodle.\n *\n * @module      tiny_media/embed\n * @copyright   2022 Huong Nguyen <huongnv13@gmail.com>\n * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {\n    getString,\n    getStrings,\n} from 'core/str';\nimport * as ModalEvents from 'core/modal_events';\nimport {displayFilepicker} from 'editor_tiny/utils';\nimport {getCurrentLanguage, getMoodleLang} from 'editor_tiny/options';\nimport {component} from \"./common\";\nimport EmbedModal from './embedmodal';\nimport Selectors from './selectors';\nimport {getEmbedPermissions} from './options';\nimport {getFilePicker} from 'editor_tiny/options';\n\nexport default class MediaEmbed {\n    editor = null;\n    canShowFilePicker = false;\n    canShowFilePickerPoster = false;\n    canShowFilePickerTrack = false;\n\n    /**\n     * @property {Object} The names of the alignment options.\n     */\n    helpStrings = null;\n\n    /**\n     * @property {boolean} Indicate that the user is updating the media or not.\n     */\n    isUpdating = false;\n\n    /**\n     * @property {Object} The currently selected media.\n     */\n    selectedMedia = null;\n\n    constructor(editor) {\n        const permissions = getEmbedPermissions(editor);\n\n        // Indicates whether the file picker can be shown.\n        this.canShowFilePicker = permissions.filepicker && (typeof getFilePicker(editor, 'media') !== 'undefined');\n        this.canShowFilePickerPoster = permissions.filepicker && (typeof getFilePicker(editor, 'image') !== 'undefined');\n        this.canShowFilePickerTrack = permissions.filepicker && (typeof getFilePicker(editor, 'subtitle') !== 'undefined');\n\n        this.editor = editor;\n    }\n\n    async getHelpStrings() {\n        if (!this.helpStrings) {\n            const [addSource, tracks, subtitles, captions, descriptions, chapters, metadata] = await getStrings([\n                'addsource_help',\n                'tracks_help',\n                'subtitles_help',\n                'captions_help',\n                'descriptions_help',\n                'chapters_help',\n                'metadata_help',\n            ].map((key) => ({\n                key,\n                component,\n            })));\n\n            this.helpStrings = {addSource, tracks, subtitles, captions, descriptions, chapters, metadata};\n        }\n\n        return this.helpStrings;\n    }\n\n    async getTemplateContext(data) {\n        const languages = this.prepareMoodleLang();\n\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            showfilepicker: this.canShowFilePicker,\n            showfilepickerposter: this.canShowFilePickerPoster,\n            showfilepickertrack: this.canShowFilePickerTrack,\n            langsinstalled: languages.installed,\n            langsavailable: languages.available,\n            link: true,\n            video: false,\n            audio: false,\n            isupdating: this.isUpdating,\n        }, data, helpIcons);\n    }\n\n    async displayDialogue() {\n        this.selectedMedia = this.getSelectedMedia();\n        const data = Object.assign({}, this.getCurrentEmbedData());\n        this.isUpdating = Object.keys(data).length !== 0;\n\n        this.currentModal = await EmbedModal.create({\n            title: getString('createmedia', 'tiny_media'),\n            templateContext: await this.getTemplateContext(data),\n        });\n\n        await this.registerEventListeners(this.currentModal);\n    }\n\n    getCurrentEmbedData() {\n        const properties = this.getMediumProperties();\n        if (!properties) {\n            return {};\n        }\n\n        const processedProperties = {};\n        processedProperties[properties.type.toLowerCase()] = properties;\n        processedProperties.link = false;\n\n        return processedProperties;\n    }\n\n    getSelectedMedia() {\n        const mediaElm = this.editor.selection.getNode();\n\n        if (!mediaElm) {\n            return null;\n        }\n\n        if (mediaElm.nodeName.toLowerCase() === 'video' || mediaElm.nodeName.toLowerCase() === 'audio') {\n            return mediaElm;\n        }\n\n        if (mediaElm.querySelector('video')) {\n            return mediaElm.querySelector('video');\n        }\n\n        if (mediaElm.querySelector('audio')) {\n            return mediaElm.querySelector('audio');\n        }\n\n        return null;\n    }\n\n    getMediumProperties() {\n        const boolAttr = (elem, attr) => {\n            // As explained in MDL-64175, some OS (like Ubuntu), are removing the value for these attributes.\n            // So in order to check if attr=\"true\", we need to check if the attribute exists and if the value is empty or true.\n            return (elem.hasAttribute(attr) && (elem.getAttribute(attr) || elem.getAttribute(attr) === ''));\n        };\n\n        const tracks = {\n            subtitles: [],\n            captions: [],\n            descriptions: [],\n            chapters: [],\n            metadata: []\n        };\n        const sources = [];\n\n        const medium = this.selectedMedia;\n        if (!medium) {\n            return null;\n        }\n        medium.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                defaultTrack: boolAttr(track, 'default')\n            });\n        });\n\n        medium.querySelectorAll('source').forEach((source) => {\n            sources.push(source.src);\n        });\n\n        return {\n            type: medium.nodeName.toLowerCase() === 'video' ? Selectors.EMBED.mediaTypes.video : Selectors.EMBED.mediaTypes.audio,\n            sources,\n            poster: medium.getAttribute('poster'),\n            title: medium.getAttribute('title'),\n            width: medium.getAttribute('width'),\n            height: medium.getAttribute('height'),\n            autoplay: boolAttr(medium, 'autoplay'),\n            loop: boolAttr(medium, 'loop'),\n            muted: boolAttr(medium, 'muted'),\n            controls: boolAttr(medium, 'controls'),\n            tracks,\n        };\n    }\n\n    prepareMoodleLang() {\n        const moodleLangs = getMoodleLang(this.editor);\n        const currentLanguage = getCurrentLanguage(this.editor);\n\n        const installed = Object.entries(moodleLangs.installed).map(([lang, code]) => ({\n            lang,\n            code,\n            \"default\": lang === currentLanguage,\n        }));\n\n        const available = Object.entries(moodleLangs.available).map(([lang, code]) => ({\n            lang,\n            code,\n            \"default\": lang === currentLanguage,\n        }));\n\n        return {\n            installed,\n            available,\n        };\n    }\n\n    getMoodleLangObj(subtitleLang) {\n        const {available} = getMoodleLang(this.editor);\n\n        if (available[subtitleLang]) {\n            return {\n                lang: subtitleLang,\n                code: available[subtitleLang],\n            };\n        }\n\n        return null;\n    }\n\n    filePickerCallback(params, element, fpType) {\n        if (params.url !== '') {\n            const tabPane = element.closest('.tab-pane');\n            element.closest(Selectors.EMBED.elements.source).querySelector(Selectors.EMBED.elements.url).value = params.url;\n\n            if (tabPane.id === this.editor.getElement().id + '_' + Selectors.EMBED.mediaTypes.link.toLowerCase()) {\n                tabPane.querySelector(Selectors.EMBED.elements.name).value = params.file;\n            }\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 = this.getMoodleLangObj(subtitleLang);\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        }\n    }\n\n    addMediaSourceComponent(element, callback) {\n        const sourceElement = element.closest(Selectors.EMBED.elements.source + Selectors.EMBED.elements.mediaSource);\n        const clone = sourceElement.cloneNode(true);\n\n        sourceElement.querySelector('.removecomponent-wrapper').classList.remove('hidden');\n        sourceElement.querySelector('.addcomponent-wrapper').classList.add('hidden');\n\n        sourceElement.parentNode.insertBefore(clone, sourceElement.nextSibling);\n\n        if (callback) {\n            callback(clone);\n        }\n    }\n\n    removeMediaSourceComponent(element) {\n        const sourceElement = element.closest(Selectors.EMBED.elements.source + Selectors.EMBED.elements.mediaSource);\n        sourceElement.remove();\n    }\n\n    addTrackComponent(element, callback) {\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\n        trackElement.parentNode.insertBefore(clone, trackElement.nextSibling);\n\n        if (callback) {\n            callback(clone);\n        }\n    }\n\n    removeTrackComponent(element) {\n        const sourceElement = element.closest(Selectors.EMBED.elements.track);\n        sourceElement.remove();\n    }\n\n    getMediumTypeFromTabPane(tabPane) {\n        return tabPane.getAttribute('data-medium-type');\n    }\n\n    getTrackTypeFromTabPane(tabPane) {\n        return tabPane.getAttribute('data-track-kind');\n    }\n\n    getMediaHTML(form) {\n        const mediumType = this.getMediumTypeFromTabPane(form.querySelector('.root.tab-content > .tab-pane.active'));\n        const tabContent = form.querySelector(Selectors.EMBED.elements[mediumType.toLowerCase() + 'Pane']);\n\n        return this['getMediaHTML' + mediumType[0].toUpperCase() + mediumType.substr(1)](tabContent);\n    }\n\n    getMediaHTMLLink(tab) {\n        const context = {\n            url: tab.querySelector(Selectors.EMBED.elements.url).value,\n            name: tab.querySelector(Selectors.EMBED.elements.name).value || false\n        };\n\n        return context.url ? Templates.renderForPromise('tiny_media/embed_media_link', context) : '';\n    }\n\n    getMediaHTMLVideo(tab) {\n        const context = this.getContextForMediaHTML(tab);\n        context.width = tab.querySelector(Selectors.EMBED.elements.width).value || false;\n        context.height = tab.querySelector(Selectors.EMBED.elements.height).value || false;\n        context.poster = tab.querySelector(\n            `${Selectors.EMBED.elements.posterSource} ${Selectors.EMBED.elements.url}`\n        ).value || false;\n\n        return context.sources.length ? Templates.renderForPromise('tiny_media/embed_media_video', context) : '';\n    }\n\n    getMediaHTMLAudio(tab) {\n        const context = this.getContextForMediaHTML(tab);\n\n        return context.sources.length ? Templates.renderForPromise('tiny_media/embed_media_audio', context) : '';\n    }\n\n    getContextForMediaHTML(tab) {\n        const tracks = Array.from(tab.querySelectorAll(Selectors.EMBED.elements.track)).map(track => ({\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 ||\n                track.querySelector(Selectors.EMBED.elements.trackLang).value,\n            srclang: track.querySelector(Selectors.EMBED.elements.trackLang).value,\n            defaultTrack: track.querySelector(Selectors.EMBED.elements.trackDefault).checked ? \"true\" : null\n        })).filter((track) => !!track.track);\n\n        const sources = Array.from(tab.querySelectorAll(Selectors.EMBED.elements.mediaSource + ' '\n            + Selectors.EMBED.elements.url))\n                .filter((source) => !!source.value)\n                .map((source) => source.value);\n\n        return {\n            sources,\n            description: tab.querySelector(Selectors.EMBED.elements.mediaSource + ' '\n                + Selectors.EMBED.elements.url).value || false,\n            tracks,\n            showControls: tab.querySelector(Selectors.EMBED.elements.mediaControl).checked,\n            autoplay: tab.querySelector(Selectors.EMBED.elements.mediaAutoplay).checked,\n            muted: tab.querySelector(Selectors.EMBED.elements.mediaMute).checked,\n            loop: tab.querySelector(Selectors.EMBED.elements.mediaLoop).checked,\n            title: tab.querySelector(Selectors.EMBED.elements.title).value || false\n        };\n    }\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    async clickHandler(e) {\n        const element = e.target;\n\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.filePickerCallback(params, element, fpType);\n        }\n\n        const addComponentSourceAction = element.closest(Selectors.EMBED.elements.mediaSource + ' .addcomponent');\n        if (addComponentSourceAction) {\n            e.preventDefault();\n            this.addMediaSourceComponent(element);\n        }\n\n        const removeComponentSourceAction = element.closest(Selectors.EMBED.elements.mediaSource + ' .removecomponent');\n        if (removeComponentSourceAction) {\n            e.preventDefault();\n            this.removeMediaSourceComponent(element);\n        }\n\n        const addComponentTrackAction = element.closest(Selectors.EMBED.elements.track + ' .addcomponent');\n        if (addComponentTrackAction) {\n            e.preventDefault();\n            this.addTrackComponent(element);\n        }\n\n        const removeComponentTrackAction = 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('.root.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    async handleDialogueSubmission(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    async registerEventListeners(modal) {\n        await modal.getBody();\n        const $root = modal.getRoot();\n        const root = $root[0];\n        if (this.canShowFilePicker || this.canShowFilePickerPoster || this.canShowFilePickerTrack) {\n            root.addEventListener('click', this.clickHandler.bind(this));\n        }\n\n        $root.on(ModalEvents.save, this.handleDialogueSubmission.bind(this));\n        $root.on(ModalEvents.hidden, () => {\n            this.currentModal.destroy();\n        });\n        $root.on(ModalEvents.shown, () => {\n            root.querySelectorAll(Selectors.EMBED.elements.trackLang).forEach((dropdown) => {\n                const defaultVal = dropdown.getAttribute('data-value');\n                if (defaultVal) {\n                    dropdown.value = defaultVal;\n                }\n            });\n        });\n    }\n}\n"],"names":["constructor","editor","permissions","canShowFilePicker","filepicker","canShowFilePickerPoster","canShowFilePickerTrack","this","helpStrings","addSource","tracks","subtitles","captions","descriptions","chapters","metadata","map","key","component","data","languages","prepareMoodleLang","helpIcons","Array","from","Object","entries","getHelpStrings","forEach","_ref","text","toLowerCase","assign","elementid","getElement","id","showfilepicker","showfilepickerposter","showfilepickertrack","langsinstalled","installed","langsavailable","available","link","video","audio","isupdating","isUpdating","selectedMedia","getSelectedMedia","getCurrentEmbedData","keys","length","currentModal","EmbedModal","create","title","templateContext","getTemplateContext","registerEventListeners","properties","getMediumProperties","processedProperties","type","mediaElm","selection","getNode","nodeName","querySelector","boolAttr","elem","attr","hasAttribute","getAttribute","sources","medium","querySelectorAll","track","push","src","srclang","label","defaultTrack","source","Selectors","EMBED","mediaTypes","poster","width","height","autoplay","loop","muted","controls","moodleLangs","currentLanguage","_ref2","lang","code","_ref3","getMoodleLangObj","subtitleLang","filePickerCallback","params","element","fpType","url","tabPane","closest","elements","value","name","file","split","slice","langObj","trackLabel","trim","trackLang","addMediaSourceComponent","callback","sourceElement","mediaSource","clone","cloneNode","classList","remove","add","parentNode","insertBefore","nextSibling","removeMediaSourceComponent","addTrackComponent","trackElement","removeTrackComponent","getMediumTypeFromTabPane","getTrackTypeFromTabPane","getMediaHTML","form","mediumType","tabContent","toUpperCase","substr","getMediaHTMLLink","tab","context","Templates","renderForPromise","getMediaHTMLVideo","getContextForMediaHTML","posterSource","getMediaHTMLAudio","trackSource","kind","trackDefault","checked","filter","description","showControls","mediaControl","mediaAutoplay","mediaMute","mediaLoop","getFilepickerTypeFromElement","e","target","actions","mediaBrowser","preventDefault","trackDefaultAction","getKind","el","parentElement","select","event","modal","html","getRoot","outerHTML","insertContent","getBody","$root","root","addEventListener","clickHandler","bind","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","shown","dropdown","defaultVal"],"mappings":"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"}