From cc3c4c08d0fe367e028d03a3a4c2bbdb155431d9 Mon Sep 17 00:00:00 2001 From: Stevani Andolo Date: Thu, 1 Aug 2024 05:19:10 +0800 Subject: [PATCH] MDL-78428 tiny_media: Implemented a new embed preview --- .../tiny/plugins/media/amd/build/embed.min.js | 2 +- .../plugins/media/amd/build/embed.min.js.map | 2 +- .../media/amd/build/embed/embedhandler.min.js | 2 +- .../amd/build/embed/embedhandler.min.js.map | 2 +- .../media/amd/build/embed/embedhelpers.min.js | 11 +- .../amd/build/embed/embedhelpers.min.js.map | 2 +- .../media/amd/build/embed/embedinsert.min.js | 2 +- .../amd/build/embed/embedinsert.min.js.map | 2 +- .../media/amd/build/embed/embedpreview.min.js | 3 + .../amd/build/embed/embedpreview.min.js.map | 1 + .../plugins/media/amd/build/helpers.min.js | 4 +- .../media/amd/build/helpers.min.js.map | 2 +- .../plugins/media/amd/build/mediabase.min.js | 3 + .../media/amd/build/mediabase.min.js.map | 1 + .../plugins/media/amd/build/selectors.min.js | 2 +- .../media/amd/build/selectors.min.js.map | 2 +- .../tiny/plugins/media/amd/src/embed.js | 36 ++- .../media/amd/src/embed/embedhandler.js | 197 +++++++++++++- .../media/amd/src/embed/embedhelpers.js | 166 ++++++++++++ .../media/amd/src/embed/embedinsert.js | 66 ++++- .../media/amd/src/embed/embedpreview.js | 243 ++++++++++++++++++ .../tiny/plugins/media/amd/src/helpers.js | 129 ++++++++++ .../tiny/plugins/media/amd/src/mediabase.js | 172 +++++++++++++ .../tiny/plugins/media/amd/src/selectors.js | 37 +++ .../tiny/plugins/media/lang/en/tiny_media.php | 8 +- lib/editor/tiny/plugins/media/styles.css | 14 +- 26 files changed, 1086 insertions(+), 25 deletions(-) create mode 100644 lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js create mode 100644 lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js.map create mode 100644 lib/editor/tiny/plugins/media/amd/build/mediabase.min.js create mode 100644 lib/editor/tiny/plugins/media/amd/build/mediabase.min.js.map create mode 100644 lib/editor/tiny/plugins/media/amd/src/embed/embedpreview.js create mode 100644 lib/editor/tiny/plugins/media/amd/src/mediabase.js diff --git a/lib/editor/tiny/plugins/media/amd/build/embed.min.js b/lib/editor/tiny/plugins/media/amd/build/embed.min.js index bc8c6997017..17a63ccf804 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed.min.js +++ b/lib/editor/tiny/plugins/media/amd/build/embed.min.js @@ -1,3 +1,3 @@ -define("tiny_media/embed",["exports","./embedmodal","./options","editor_tiny/options","./embed/embedhandler","./embed/embedhelpers"],(function(_exports,_embedmodal,_options,_options2,_embedhandler,_embedhelpers){var 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.default=void 0,_embedmodal=(obj=_embedmodal)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(editor){_defineProperty(this,"editor",null),_defineProperty(this,"canShowFilePicker",!1),_defineProperty(this,"canShowFilePickerPoster",!1),_defineProperty(this,"canShowFilePickerTrack",!1);const permissions=(0,_options.getEmbedPermissions)(editor),options=(0,_options2.getFilePicker)(editor,"media");this.canShowFilePicker=permissions.filepicker&&void 0!==(0,_options2.getFilePicker)(editor,"media"),this.canShowFilePickerPoster=permissions.filepicker&&void 0!==(0,_options2.getFilePicker)(editor,"image"),this.canShowFilePickerTrack=permissions.filepicker&&void 0!==(0,_options2.getFilePicker)(editor,"subtitle"),this.canShowDropZone=Object.values(options.repositories).some((repository=>"upload"===repository.type)),this.editor=editor}async displayDialogue(){this.currentModal=await _embedmodal.default.create(),this.root=this.currentModal.getRoot()[0];const mediaHandler=new _embedhandler.EmbedHandler(this);mediaHandler.loadTemplatePromise((0,_embedhelpers.insertMediaTemplateContext)(this)),mediaHandler.registerEventListeners(this.currentModal)}},_exports.default})); +define("tiny_media/embed",["exports","./embedmodal","./options","editor_tiny/options","./embed/embedhandler","./embed/embedhelpers","./embed/embedinsert"],(function(_exports,_embedmodal,_options,_options2,_embedhandler,_embedhelpers,_embedinsert){var 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.default=void 0,_embedmodal=(obj=_embedmodal)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(editor){_defineProperty(this,"editor",null),_defineProperty(this,"canShowFilePicker",!1),_defineProperty(this,"canShowFilePickerPoster",!1),_defineProperty(this,"canShowFilePickerTrack",!1),_defineProperty(this,"loadSelectedMedia",(()=>{let mediaSource=null;mediaSource="link"===this.mediaType?this.selectedMedia.href:this.selectedMedia.querySelector("source").src;const embedInsert=new _embedinsert.EmbedInsert(this);embedInsert.init(),embedInsert.loadMediaPreview(mediaSource),new _embedhandler.EmbedHandler(this).registerEventListeners(this.currentModal)}));const permissions=(0,_options.getEmbedPermissions)(editor),options=(0,_options2.getFilePicker)(editor,"media");this.canShowFilePicker=permissions.filepicker&&void 0!==(0,_options2.getFilePicker)(editor,"media"),this.canShowFilePickerPoster=permissions.filepicker&&void 0!==(0,_options2.getFilePicker)(editor,"image"),this.canShowFilePickerTrack=permissions.filepicker&&void 0!==(0,_options2.getFilePicker)(editor,"subtitle"),this.canShowDropZone=Object.values(options.repositories).some((repository=>"upload"===repository.type)),this.editor=editor}async displayDialogue(){const[mediaType,selectedMedia]=(0,_embedhelpers.getSelectedMediaElement)(this.editor);if(this.mediaType=mediaType,this.selectedMedia=selectedMedia,this.currentModal=await _embedmodal.default.create(),this.root=this.currentModal.getRoot()[0],this.selectedMedia)this.isUpdating=!0,this.loadSelectedMedia();else{const embedHandler=new _embedhandler.EmbedHandler(this);embedHandler.loadTemplatePromise((0,_embedhelpers.insertMediaTemplateContext)(this)),embedHandler.registerEventListeners(this.currentModal)}}},_exports.default})); //# sourceMappingURL=embed.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed.min.js.map b/lib/editor/tiny/plugins/media/amd/build/embed.min.js.map index ff390e2cd5c..bbafbe0a4b9 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed.min.js.map +++ b/lib/editor/tiny/plugins/media/amd/build/embed.min.js.map @@ -1 +1 @@ -{"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 .\n\n/**\n * Tiny Media plugin Embed class for Moodle.\n *\n * @module tiny_media/embed\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport EmbedModal from './embedmodal';\nimport {getEmbedPermissions} from './options';\nimport {getFilePicker} from 'editor_tiny/options';\nimport {EmbedHandler} from './embed/embedhandler';\nimport {insertMediaTemplateContext} from './embed/embedhelpers';\n\nexport default class MediaEmbed {\n editor = null;\n canShowFilePicker = false;\n canShowFilePickerPoster = false;\n canShowFilePickerTrack = false;\n\n constructor(editor) {\n const permissions = getEmbedPermissions(editor);\n const options = getFilePicker(editor, 'media');\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 this.canShowDropZone = Object.values(options.repositories).some(repository => repository.type === 'upload');\n this.editor = editor;\n }\n\n async displayDialogue() {\n this.currentModal = await EmbedModal.create();\n this.root = this.currentModal.getRoot()[0];\n\n const mediaHandler = new EmbedHandler(this);\n mediaHandler.loadTemplatePromise(insertMediaTemplateContext(this));\n mediaHandler.registerEventListeners(this.currentModal);\n }\n}\n"],"names":["constructor","editor","permissions","options","canShowFilePicker","filepicker","canShowFilePickerPoster","canShowFilePickerTrack","canShowDropZone","Object","values","repositories","some","repository","type","currentModal","EmbedModal","create","root","this","getRoot","mediaHandler","EmbedHandler","loadTemplatePromise","registerEventListeners"],"mappings":"8iBAmCIA,YAAYC,sCALH,gDACW,mDACM,kDACD,SAGfC,aAAc,gCAAoBD,QAClCE,SAAU,2BAAcF,OAAQ,cAGjCG,kBAAoBF,YAAYG,iBAAyD,KAAnC,2BAAcJ,OAAQ,cAC5EK,wBAA0BJ,YAAYG,iBAAyD,KAAnC,2BAAcJ,OAAQ,cAClFM,uBAAyBL,YAAYG,iBAA4D,KAAtC,2BAAcJ,OAAQ,iBACjFO,gBAAkBC,OAAOC,OAAOP,QAAQQ,cAAcC,MAAKC,YAAkC,WAApBA,WAAWC,YACpFb,OAASA,oCAITc,mBAAqBC,oBAAWC,cAChCC,KAAOC,KAAKJ,aAAaK,UAAU,SAElCC,aAAe,IAAIC,2BAAaH,MACtCE,aAAaE,qBAAoB,4CAA2BJ,OAC5DE,aAAaG,uBAAuBL,KAAKJ"} \ No newline at end of file +{"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 .\n\n/**\n * Tiny Media plugin Embed class for Moodle.\n *\n * @module tiny_media/embed\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport EmbedModal from './embedmodal';\nimport {getEmbedPermissions} from './options';\nimport {getFilePicker} from 'editor_tiny/options';\nimport {EmbedHandler} from './embed/embedhandler';\nimport {\n insertMediaTemplateContext,\n getSelectedMediaElement,\n} from './embed/embedhelpers';\nimport {EmbedInsert} from './embed/embedinsert';\n\nexport default class MediaEmbed {\n editor = null;\n canShowFilePicker = false;\n canShowFilePickerPoster = false;\n canShowFilePickerTrack = false;\n\n constructor(editor) {\n const permissions = getEmbedPermissions(editor);\n const options = getFilePicker(editor, 'media');\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 this.canShowDropZone = Object.values(options.repositories).some(repository => repository.type === 'upload');\n this.editor = editor;\n }\n\n async displayDialogue() {\n const [mediaType, selectedMedia] = getSelectedMediaElement(this.editor);\n this.mediaType = mediaType;\n this.selectedMedia = selectedMedia;\n this.currentModal = await EmbedModal.create();\n this.root = this.currentModal.getRoot()[0];\n\n if (this.selectedMedia) {\n // Preview the selected media.\n this.isUpdating = true;\n this.loadSelectedMedia();\n } else {\n const embedHandler = new EmbedHandler(this);\n embedHandler.loadTemplatePromise(insertMediaTemplateContext(this));\n embedHandler.registerEventListeners(this.currentModal);\n }\n }\n\n loadSelectedMedia = () => {\n let mediaSource = null;\n if (this.mediaType === 'link') {\n mediaSource = this.selectedMedia.href;\n } else {\n mediaSource = this.selectedMedia.querySelector('source').src;\n }\n\n // Load media preview.\n const embedInsert = new EmbedInsert(this);\n embedInsert.init();\n embedInsert.loadMediaPreview(mediaSource);\n (new EmbedHandler(this)).registerEventListeners(this.currentModal);\n };\n}\n"],"names":["constructor","editor","mediaSource","this","mediaType","selectedMedia","href","querySelector","src","embedInsert","EmbedInsert","init","loadMediaPreview","EmbedHandler","registerEventListeners","currentModal","permissions","options","canShowFilePicker","filepicker","canShowFilePickerPoster","canShowFilePickerTrack","canShowDropZone","Object","values","repositories","some","repository","type","EmbedModal","create","root","getRoot","isUpdating","loadSelectedMedia","embedHandler","loadTemplatePromise"],"mappings":"ilBAuCIA,YAAYC,sCALH,gDACW,mDACM,kDACD,6CAgCL,SACZC,YAAc,KAEdA,YADmB,SAAnBC,KAAKC,UACSD,KAAKE,cAAcC,KAEnBH,KAAKE,cAAcE,cAAc,UAAUC,UAIvDC,YAAc,IAAIC,yBAAYP,MACpCM,YAAYE,OACZF,YAAYG,iBAAiBV,iBACxBW,2BAAaV,MAAOW,uBAAuBX,KAAKY,uBAzC/CC,aAAc,gCAAoBf,QAClCgB,SAAU,2BAAchB,OAAQ,cAGjCiB,kBAAoBF,YAAYG,iBAAyD,KAAnC,2BAAclB,OAAQ,cAC5EmB,wBAA0BJ,YAAYG,iBAAyD,KAAnC,2BAAclB,OAAQ,cAClFoB,uBAAyBL,YAAYG,iBAA4D,KAAtC,2BAAclB,OAAQ,iBACjFqB,gBAAkBC,OAAOC,OAAOP,QAAQQ,cAAcC,MAAKC,YAAkC,WAApBA,WAAWC,YACpF3B,OAASA,qCAIPG,UAAWC,gBAAiB,yCAAwBF,KAAKF,gBAC3DG,UAAYA,eACZC,cAAgBA,mBAChBU,mBAAqBc,oBAAWC,cAChCC,KAAO5B,KAAKY,aAAaiB,UAAU,GAEpC7B,KAAKE,mBAEA4B,YAAa,OACbC,wBACF,OACGC,aAAe,IAAItB,2BAAaV,MACtCgC,aAAaC,qBAAoB,4CAA2BjC,OAC5DgC,aAAarB,uBAAuBX,KAAKY"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js b/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js index 4e7f46aa66f..95a00643906 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js @@ -1,3 +1,3 @@ -define("tiny_media/embed/embedhandler",["exports","../selectors","./embedinsert","../helpers","core/modal_events","editor_tiny/utils"],(function(_exports,_selectors,_embedinsert,_helpers,ModalEvents,_utils){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}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.EmbedHandler=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj},ModalEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalEvents);_exports.EmbedHandler=class{constructor(data){_defineProperty(this,"loadTemplatePromise",(templateContext=>{templateContext.elementid=this.editor.id,templateContext.bodyTemplate=_selectors.default.EMBED.template.body.insertMediaBody,templateContext.footerTemplate=_selectors.default.EMBED.template.footer.insertMediaFooter,templateContext.selector="EMBED",Promise.all([(0,_helpers.body)(templateContext,this.root),(0,_helpers.footer)(templateContext,this.root)]).then((()=>{new _embedinsert.EmbedInsert(this).init()})).catch((error=>{window.console.log(error)}))})),_defineProperty(this,"clickHandler",(async e=>{if(e.target.closest(_selectors.default.EMBED.actions.mediaBrowser)){e.preventDefault();const params=await(0,_utils.displayFilepicker)(this.editor,"media");this.trackFilePickerCallback(params)}e.target.closest(_selectors.default.EMBED.actions.addUrl)&&this.urlChanged()})),_defineProperty(this,"registerEventListeners",(async modal=>{await modal.getBody();const $root=modal.getRoot(),root=$root[0];this.canShowFilePickerTrack&&root.addEventListener("click",this.clickHandler.bind(this)),root.addEventListener("input",(e=>{const urlEle=e.target.closest(_selectors.default.EMBED.elements.fromUrl);urlEle&&this.toggleUrlButton(urlEle)})),$root.on(ModalEvents.hidden,(()=>{this.currentModal.destroy()}))})),(0,_helpers.setPropertiesFromData)(this,data)}urlChanged(){(0,_helpers.hideElements)(_selectors.default.EMBED.elements.urlWarning,this.root);const url=this.root.querySelector(_selectors.default.EMBED.elements.fromUrl).value;url&&url!==this.currentUrl&&window.console.log(url)}trackFilePickerCallback(params){""!==params.url&&window.console.log(params.url)}toggleUrlButton(input){const url=input.value;this.root.querySelector(_selectors.default.EMBED.actions.addUrl).disabled=!(""!==url&&(0,_helpers.isValidUrl)(url))}}})); +define("tiny_media/embed/embedhandler",["exports","../selectors","./embedinsert","../helpers","core/modal_events","editor_tiny/utils","./embedhelpers"],(function(_exports,_selectors,_embedinsert,_helpers,ModalEvents,_utils,_embedhelpers){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}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.EmbedHandler=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj},ModalEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalEvents);_exports.EmbedHandler=class{constructor(_data){var _this=this;_defineProperty(this,"loadTemplatePromise",(templateContext=>{templateContext.elementid=this.editor.id,templateContext.bodyTemplate=_selectors.default.EMBED.template.body.insertMediaBody,templateContext.footerTemplate=_selectors.default.EMBED.template.footer.insertMediaFooter,templateContext.selector="EMBED",Promise.all([(0,_helpers.body)(templateContext,this.root),(0,_helpers.footer)(templateContext,this.root)]).then((()=>{new _embedinsert.EmbedInsert(this).init()})).catch((error=>{window.console.log(error)}))})),_defineProperty(this,"loadMediaDetails",(async(embedPreview,templateContext)=>{Promise.all([(0,_helpers.body)(templateContext,this.root),(0,_helpers.footer)(templateContext,this.root)]).then((()=>{if((0,_helpers.hideElements)(_selectors.default.EMBED.elements.bodyTemplate,this.root),this.mediaData){this.currentModal.uploadThumbnailModal.destroy();const currentModal=this.currentModal.insertMediaModal;this.currentModal=currentModal.insertMediaModal}embedPreview.init()})).catch((error=>{this.mediaData||(0,_helpers.stopMediaLoading)(this.root,"EMBED"),window.console.log(error)}))})),_defineProperty(this,"resetUploadForm",(()=>{this.mediaType=null,this.loadTemplatePromise((0,_embedhelpers.insertMediaTemplateContext)(this))})),_defineProperty(this,"getMediumProperties",(()=>{const boolAttr=(elem,attr)=>elem.hasAttribute(attr)&&(elem.getAttribute(attr)||""===elem.getAttribute(attr)),medium=this.selectedMedia;if(!medium)return null;if("link"===this.mediaType){const urlParams=(0,_helpers.convertStringUrlToObject)(medium.href),mediaData={type:this.mediaType,title:medium.textContent.trim()};for(const param in urlParams){let prop=param;"mute"===param&&(prop="muted");const isTrue="true"===urlParams[param]||"1"===urlParams[param];mediaData[prop]=isTrue}return mediaData}{const tracks={subtitles:[],captions:[],descriptions:[],chapters:[],metadata:[]},sources=[];medium.querySelectorAll("track").forEach((track=>{tracks[track.getAttribute("kind")].push({src:track.getAttribute("src"),srclang:track.getAttribute("srclang"),label:track.getAttribute("label"),defaultTrack:boolAttr(track,"default")})})),medium.querySelectorAll("source").forEach((source=>{sources.push(source.src)}));const title=medium.getAttribute("title");return{type:this.mediaType,sources:sources,poster:medium.getAttribute("poster"),title:!!title&&title.trim(),width:medium.getAttribute("width"),height:medium.getAttribute("height"),autoplay:boolAttr(medium,"autoplay"),loop:boolAttr(medium,"loop"),muted:boolAttr(medium,"muted"),controls:boolAttr(medium,"controls"),tracks:tracks}}})),_defineProperty(this,"getCurrentEmbedData",(()=>{const properties=this.getMediumProperties();if(!properties)return{};const processedProperties={};return processedProperties.media=properties,processedProperties.link=!1,processedProperties})),_defineProperty(this,"getHelpStrings",(async()=>(this.helpStrings||(this.helpStrings=await(0,_embedhelpers.getHelpStrings)()),this.helpStrings))),_defineProperty(this,"getTemplateContext",(async data=>{const languages=(0,_embedhelpers.prepareMoodleLang)(this.editor),helpIcons=Array.from(Object.entries(await this.getHelpStrings())).forEach((_ref=>{let[key,text]=_ref;data["".concat(key.toLowerCase(),"helpicon")]={text:text}}));return Object.assign({},{elementid:this.editor.getElement().id,showFilePickerTrack:this.canShowFilePickerTrack,langsInstalled:languages.installed,langsAvailable:languages.available,media:!0,isUpdating:this.isUpdating},data,helpIcons)})),_defineProperty(this,"getMediaTemplateContext",(async function(){let data=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return data||(data=Object.assign({},_this.getCurrentEmbedData())),_this.isUpdating=0!==Object.keys(data).length,await _this.getTemplateContext(data)})),_defineProperty(this,"loadMediaPreview",(url=>{new _embedinsert.EmbedInsert(this).loadMediaPreview(url)})),_defineProperty(this,"clickHandler",(async e=>{if(e.target.closest(_selectors.default.EMBED.actions.mediaBrowser)){e.preventDefault();const params=await(0,_utils.displayFilepicker)(this.editor,"media");this.trackFilePickerCallback(params)}e.target.closest(_selectors.default.EMBED.actions.addUrl)&&this.urlChanged()})),_defineProperty(this,"registerEventListeners",(async modal=>{await modal.getBody();const $root=modal.getRoot(),root=$root[0];this.canShowFilePickerTrack&&root.addEventListener("click",this.clickHandler.bind(this)),root.addEventListener("input",(e=>{const urlEle=e.target.closest(_selectors.default.EMBED.elements.fromUrl);urlEle&&this.toggleUrlButton(urlEle)})),$root.on(ModalEvents.hidden,(()=>{this.currentModal.destroy()}))})),(0,_helpers.setPropertiesFromData)(this,_data)}urlChanged(){(0,_helpers.hideElements)(_selectors.default.EMBED.elements.urlWarning,this.root);const url=this.root.querySelector(_selectors.default.EMBED.elements.fromUrl).value;url&&url!==this.currentUrl&&this.loadMediaPreview(url)}trackFilePickerCallback(params){""!==params.url&&this.loadMediaPreview(params.url)}toggleUrlButton(input){const url=input.value;this.root.querySelector(_selectors.default.EMBED.actions.addUrl).disabled=!(""!==url&&(0,_helpers.isValidUrl)(url))}}})); //# sourceMappingURL=embedhandler.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js.map b/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js.map index 0792c442ce5..d056bef7e8e 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js.map +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedhandler.min.js.map @@ -1 +1 @@ -{"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 .\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 \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 hideElements,\n isValidUrl,\n} from '../helpers';\nimport * as ModalEvents from 'core/modal_events';\nimport {displayFilepicker} from 'editor_tiny/utils';\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 = 'EMBED';\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 * Handles changes in the media URL input field and loads a preview of the media if the URL has changed.\n */\n urlChanged() {\n hideElements(Selectors.EMBED.elements.urlWarning, this.root);\n const url = this.root.querySelector(Selectors.EMBED.elements.fromUrl).value;\n if (url && url !== this.currentUrl) {\n window.console.log(url);\n }\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 */\n trackFilePickerCallback(params) {\n if (params.url !== '') {\n window.console.log(params.url);\n }\n }\n\n /**\n * Handle click events.\n *\n * @param {html} e Selected element\n */\n clickHandler = async(e) => {\n const element = e.target;\n\n const mediaBrowser = element.closest(Selectors.EMBED.actions.mediaBrowser);\n if (mediaBrowser) {\n e.preventDefault();\n const params = await displayFilepicker(this.editor, 'media');\n this.trackFilePickerCallback(params);\n }\n\n const addUrlEle = e.target.closest(Selectors.EMBED.actions.addUrl);\n if (addUrlEle) {\n this.urlChanged();\n }\n };\n\n /**\n * Enables or disables the URL-related buttons in the footer based on the current URL and input value.\n *\n * @param {html} input Url input field\n */\n toggleUrlButton(input) {\n const url = input.value;\n const addUrl = this.root.querySelector(Selectors.EMBED.actions.addUrl);\n addUrl.disabled = !(url !== \"\" && isValidUrl(url));\n }\n\n registerEventListeners = async(modal) => {\n await modal.getBody();\n const $root = modal.getRoot();\n const root = $root[0];\n if (this.canShowFilePickerTrack) {\n root.addEventListener('click', this.clickHandler.bind(this));\n }\n\n root.addEventListener('input', (e) => {\n const urlEle = e.target.closest(Selectors.EMBED.elements.fromUrl);\n if (urlEle) {\n this.toggleUrlButton(urlEle);\n }\n });\n\n $root.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n };\n}\n"],"names":["constructor","data","templateContext","elementid","this","editor","id","bodyTemplate","Selectors","EMBED","template","body","insertMediaBody","footerTemplate","footer","insertMediaFooter","selector","Promise","all","root","then","EmbedInsert","init","catch","error","window","console","log","async","e","target","closest","actions","mediaBrowser","preventDefault","params","trackFilePickerCallback","addUrl","urlChanged","modal","getBody","$root","getRoot","canShowFilePickerTrack","addEventListener","clickHandler","bind","urlEle","elements","fromUrl","toggleUrlButton","on","ModalEvents","hidden","currentModal","destroy","urlWarning","url","querySelector","value","currentUrl","input","disabled"],"mappings":"m+CA6CIA,YAAYC,kDASWC,kBACnBA,gBAAgBC,UAAYC,KAAKC,OAAOC,GACxCJ,gBAAgBK,aAAeC,mBAAUC,MAAMC,SAASC,KAAKC,gBAC7DV,gBAAgBW,eAAiBL,mBAAUC,MAAMC,SAASI,OAAOC,kBACjEb,gBAAgBc,SAAW,QAE3BC,QAAQC,IAAI,EAAC,iBAAKhB,gBAAiBE,KAAKe,OAAO,mBAAOjB,gBAAiBE,KAAKe,QACvEC,MAAK,SACGC,yBAAYjB,MAAOkB,UAG3BC,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,kDA+BhBI,MAAAA,OACKC,EAAEC,OAEWC,QAAQvB,mBAAUC,MAAMuB,QAAQC,cAC3C,CACdJ,EAAEK,uBACIC,aAAe,4BAAkB/B,KAAKC,OAAQ,cAC/C+B,wBAAwBD,QAGfN,EAAEC,OAAOC,QAAQvB,mBAAUC,MAAMuB,QAAQK,cAElDC,+DAeYV,MAAAA,cACfW,MAAMC,gBACNC,MAAQF,MAAMG,UACdvB,KAAOsB,MAAM,GACfrC,KAAKuC,wBACLxB,KAAKyB,iBAAiB,QAASxC,KAAKyC,aAAaC,KAAK1C,OAG1De,KAAKyB,iBAAiB,SAAUf,UACtBkB,OAASlB,EAAEC,OAAOC,QAAQvB,mBAAUC,MAAMuC,SAASC,SACrDF,aACKG,gBAAgBH,WAI7BN,MAAMU,GAAGC,YAAYC,QAAQ,UACpBC,aAAaC,mDA9FAnD,KAAMH,MA2BhCqC,uCACiB9B,mBAAUC,MAAMuC,SAASQ,WAAYpD,KAAKe,YACjDsC,IAAMrD,KAAKe,KAAKuC,cAAclD,mBAAUC,MAAMuC,SAASC,SAASU,MAClEF,KAAOA,MAAQrD,KAAKwD,YACpBnC,OAAOC,QAAQC,IAAI8B,KAS3BrB,wBAAwBD,QACD,KAAfA,OAAOsB,KACPhC,OAAOC,QAAQC,IAAIQ,OAAOsB,KA8BlCP,gBAAgBW,aACNJ,IAAMI,MAAMF,MACHvD,KAAKe,KAAKuC,cAAclD,mBAAUC,MAAMuB,QAAQK,QACxDyB,WAAqB,KAARL,MAAc,uBAAWA"} \ No newline at end of file +{"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 .\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 \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 hideElements,\n isValidUrl,\n convertStringUrlToObject,\n stopMediaLoading,\n} from '../helpers';\nimport * as ModalEvents from 'core/modal_events';\nimport {displayFilepicker} from 'editor_tiny/utils';\nimport {\n insertMediaTemplateContext,\n getHelpStrings,\n prepareMoodleLang,\n} from \"./embedhelpers\";\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 = 'EMBED';\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 * Loads the media preview dialogue.\n *\n * @param {object} embedPreview Object of embedPreview\n * @param {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 // Hide the body template when preparing the media preview.\n hideElements(Selectors.EMBED.elements.bodyTemplate, this.root);\n\n if (this.mediaData) { // It came from mediaThumbnail and we should kill uploadThumbnailModal modal.\n this.currentModal.uploadThumbnailModal.destroy();\n const currentModal = this.currentModal.insertMediaModal;\n this.currentModal = currentModal.insertMediaModal;\n }\n embedPreview.init();\n return;\n })\n .catch(error => {\n if (!this.mediaData) { // It came from mediaThumbnail and we did not init startMediaLoading from there.\n stopMediaLoading(this.root, 'EMBED');\n }\n window.console.log(error);\n });\n };\n\n /**\n * Reset the media insert modal form.\n */\n resetUploadForm = () => {\n this.mediaType = null; // Set to null to be set again.\n this.loadTemplatePromise(insertMediaTemplateContext(this));\n };\n\n /**\n * Get selected media data.\n *\n * @returns {null|object}\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 medium = this.selectedMedia;\n if (!medium) {\n return null;\n }\n\n const isLink = (this.mediaType === 'link');\n if (isLink) {\n const urlParams = convertStringUrlToObject(medium.href);\n const mediaData = {\n type: this.mediaType,\n title: medium.textContent.trim(),\n };\n\n for (const param in urlParams) {\n let prop = param;\n if (param === 'mute') {\n prop = 'muted';\n }\n const isTrue = (urlParams[param] === 'true') || (urlParams[param] === '1');\n mediaData[prop] = isTrue;\n }\n return mediaData;\n } else {\n const tracks = {\n subtitles: [],\n captions: [],\n descriptions: [],\n chapters: [],\n metadata: []\n };\n const sources = [];\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 const title = medium.getAttribute('title');\n\n return {\n type: this.mediaType,\n sources,\n poster: medium.getAttribute('poster'),\n title: title ? title.trim() : false,\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\n /**\n * Get selected media data.\n *\n * @returns {object}\n */\n getCurrentEmbedData = () => {\n const properties = this.getMediumProperties();\n if (!properties) {\n return {};\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} A promise that resolves template context.\n */\n getMediaTemplateContext = async(data = null) => {\n if (!data) {\n data = Object.assign({}, this.getCurrentEmbedData());\n }\n this.isUpdating = Object.keys(data).length !== 0;\n return await this.getTemplateContext(data);\n };\n\n /**\n * Handles changes in the media URL input field and loads a preview of the media if the URL has changed.\n */\n urlChanged() {\n hideElements(Selectors.EMBED.elements.urlWarning, this.root);\n const url = this.root.querySelector(Selectors.EMBED.elements.fromUrl).value;\n if (url && url !== this.currentUrl) {\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 */\n trackFilePickerCallback(params) {\n if (params.url !== '') {\n this.loadMediaPreview(params.url);\n }\n }\n\n /**\n * Handle click events.\n *\n * @param {html} e Selected element\n */\n clickHandler = async(e) => {\n const element = e.target;\n\n const mediaBrowser = element.closest(Selectors.EMBED.actions.mediaBrowser);\n if (mediaBrowser) {\n e.preventDefault();\n const params = await displayFilepicker(this.editor, 'media');\n this.trackFilePickerCallback(params);\n }\n\n const addUrlEle = e.target.closest(Selectors.EMBED.actions.addUrl);\n if (addUrlEle) {\n this.urlChanged();\n }\n };\n\n /**\n * Enables or disables the URL-related buttons in the footer based on the current URL and input value.\n *\n * @param {html} input Url input field\n */\n toggleUrlButton(input) {\n const url = input.value;\n const addUrl = this.root.querySelector(Selectors.EMBED.actions.addUrl);\n addUrl.disabled = !(url !== \"\" && isValidUrl(url));\n }\n\n registerEventListeners = async(modal) => {\n await modal.getBody();\n const $root = modal.getRoot();\n const root = $root[0];\n if (this.canShowFilePickerTrack) {\n root.addEventListener('click', this.clickHandler.bind(this));\n }\n\n root.addEventListener('input', (e) => {\n const urlEle = e.target.closest(Selectors.EMBED.elements.fromUrl);\n if (urlEle) {\n this.toggleUrlButton(urlEle);\n }\n });\n\n $root.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n };\n}\n"],"names":["constructor","data","templateContext","elementid","this","editor","id","bodyTemplate","Selectors","EMBED","template","body","insertMediaBody","footerTemplate","footer","insertMediaFooter","selector","Promise","all","root","then","EmbedInsert","init","catch","error","window","console","log","async","embedPreview","elements","mediaData","currentModal","uploadThumbnailModal","destroy","insertMediaModal","mediaType","loadTemplatePromise","boolAttr","elem","attr","hasAttribute","getAttribute","medium","selectedMedia","urlParams","href","type","title","textContent","trim","param","prop","isTrue","tracks","subtitles","captions","descriptions","chapters","metadata","sources","querySelectorAll","forEach","track","push","src","srclang","label","defaultTrack","source","poster","width","height","autoplay","loop","muted","controls","properties","getMediumProperties","processedProperties","media","link","helpStrings","languages","helpIcons","Array","from","Object","entries","getHelpStrings","_ref","key","text","toLowerCase","assign","getElement","showFilePickerTrack","canShowFilePickerTrack","langsInstalled","installed","langsAvailable","available","isUpdating","_this","getCurrentEmbedData","keys","length","getTemplateContext","url","loadMediaPreview","e","target","closest","actions","mediaBrowser","preventDefault","params","trackFilePickerCallback","addUrl","urlChanged","modal","getBody","$root","getRoot","addEventListener","clickHandler","bind","urlEle","fromUrl","toggleUrlButton","on","ModalEvents","hidden","urlWarning","querySelector","value","currentUrl","input","disabled"],"mappings":"kgDAoDIA,YAAYC,kEASWC,kBACnBA,gBAAgBC,UAAYC,KAAKC,OAAOC,GACxCJ,gBAAgBK,aAAeC,mBAAUC,MAAMC,SAASC,KAAKC,gBAC7DV,gBAAgBW,eAAiBL,mBAAUC,MAAMC,SAASI,OAAOC,kBACjEb,gBAAgBc,SAAW,QAE3BC,QAAQC,IAAI,EAAC,iBAAKhB,gBAAiBE,KAAKe,OAAO,mBAAOjB,gBAAiBE,KAAKe,QACvEC,MAAK,SACGC,yBAAYjB,MAAOkB,UAG3BC,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,sDAUZI,MAAMC,aAAc3B,mBACnCe,QAAQC,IAAI,EAAC,iBAAKhB,gBAAiBE,KAAKe,OAAO,mBAAOjB,gBAAiBE,KAAKe,QACvEC,MAAK,kCAEWZ,mBAAUC,MAAMqB,SAASvB,aAAcH,KAAKe,MAErDf,KAAK2B,UAAW,MACXC,aAAaC,qBAAqBC,gBACjCF,aAAe5B,KAAK4B,aAAaG,sBAClCH,aAAeA,aAAaG,iBAErCN,aAAaP,UAGhBC,OAAMC,QACEpB,KAAK2B,yCACW3B,KAAKe,KAAM,SAEhCM,OAAOC,QAAQC,IAAIH,qDAOb,UACTY,UAAY,UACZC,qBAAoB,4CAA2BjC,sDAQlC,WACZkC,SAAW,CAACC,KAAMC,OAGZD,KAAKE,aAAaD,QAAUD,KAAKG,aAAaF,OAAqC,KAA5BD,KAAKG,aAAaF,OAG/EG,OAASvC,KAAKwC,kBACfD,cACM,QAGwB,SAAnBvC,KAAKgC,UACT,OACFS,WAAY,qCAAyBF,OAAOG,MAC5Cf,UAAY,CACdgB,KAAM3C,KAAKgC,UACXY,MAAOL,OAAOM,YAAYC,YAGzB,MAAMC,SAASN,UAAW,KACvBO,KAAOD,MACG,SAAVA,QACAC,KAAO,eAELC,OAA+B,SAArBR,UAAUM,QAA4C,MAArBN,UAAUM,OAC3DpB,UAAUqB,MAAQC,cAEftB,UACJ,OACGuB,OAAS,CACXC,UAAW,GACXC,SAAU,GACVC,aAAc,GACdC,SAAU,GACVC,SAAU,IAERC,QAAU,GAEhBjB,OAAOkB,iBAAiB,SAASC,SAASC,QACtCT,OAAOS,MAAMrB,aAAa,SAASsB,KAAK,CACpCC,IAAKF,MAAMrB,aAAa,OACxBwB,QAASH,MAAMrB,aAAa,WAC5ByB,MAAOJ,MAAMrB,aAAa,SAC1B0B,aAAc9B,SAASyB,MAAO,gBAItCpB,OAAOkB,iBAAiB,UAAUC,SAASO,SACvCT,QAAQI,KAAKK,OAAOJ,cAElBjB,MAAQL,OAAOD,aAAa,eAE3B,CACHK,KAAM3C,KAAKgC,UACXwB,QAAAA,QACAU,OAAQ3B,OAAOD,aAAa,UAC5BM,QAAOA,OAAQA,MAAME,OACrBqB,MAAO5B,OAAOD,aAAa,SAC3B8B,OAAQ7B,OAAOD,aAAa,UAC5B+B,SAAUnC,SAASK,OAAQ,YAC3B+B,KAAMpC,SAASK,OAAQ,QACvBgC,MAAOrC,SAASK,OAAQ,SACxBiC,SAAUtC,SAASK,OAAQ,YAC3BW,OAAAA,wDAUU,WACZuB,WAAazE,KAAK0E,0BACnBD,iBACM,SAGLE,oBAAsB,UAC5BA,oBAAoBC,MAAQH,WAC5BE,oBAAoBE,MAAO,EAEpBF,8DAQMnD,UACRxB,KAAK8E,mBACDA,kBAAoB,mCAGtB9E,KAAK8E,0DASKtD,MAAAA,aACXuD,WAAY,mCAAkB/E,KAAKC,QACnC+E,UAAYC,MAAMC,KAAKC,OAAOC,cAAcpF,KAAKqF,mBAAmB3B,SAAQ4B,WAAEC,IAAKC,WACrF3F,eAAQ0F,IAAIE,2BAA2B,CAACD,KAAAA,gBAGrCL,OAAOO,OAAO,GAAI,CACrB3F,UAAWC,KAAKC,OAAO0F,aAAazF,GACpC0F,oBAAqB5F,KAAK6F,uBAC1BC,eAAgBf,UAAUgB,UAC1BC,eAAgBjB,UAAUkB,UAC1BrB,OAAO,EACPsB,WAAYlG,KAAKkG,YAClBrG,KAAMmF,8DASaxD,qBAAM3B,4DAAO,YAC9BA,OACDA,KAAOsF,OAAOO,OAAO,GAAIS,MAAKC,wBAElCD,MAAKD,WAA0C,IAA7Bf,OAAOkB,KAAKxG,MAAMyG,aACvBH,MAAKI,mBAAmB1G,kDAmBrB2G,UACXvF,yBAAYjB,MAAOyG,iBAAiBD,6CAmB9BhF,MAAAA,OACKkF,EAAEC,OAEWC,QAAQxG,mBAAUC,MAAMwG,QAAQC,cAC3C,CACdJ,EAAEK,uBACIC,aAAe,4BAAkBhH,KAAKC,OAAQ,cAC/CgH,wBAAwBD,QAGfN,EAAEC,OAAOC,QAAQxG,mBAAUC,MAAMwG,QAAQK,cAElDC,+DAeY3F,MAAAA,cACf4F,MAAMC,gBACNC,MAAQF,MAAMG,UACdxG,KAAOuG,MAAM,GACftH,KAAK6F,wBACL9E,KAAKyG,iBAAiB,QAASxH,KAAKyH,aAAaC,KAAK1H,OAG1De,KAAKyG,iBAAiB,SAAUd,UACtBiB,OAASjB,EAAEC,OAAOC,QAAQxG,mBAAUC,MAAMqB,SAASkG,SACrDD,aACKE,gBAAgBF,WAI7BL,MAAMQ,GAAGC,YAAYC,QAAQ,UACpBpG,aAAaE,mDAxRA9B,KAAMH,OA4MhCsH,uCACiB/G,mBAAUC,MAAMqB,SAASuG,WAAYjI,KAAKe,YACjDyF,IAAMxG,KAAKe,KAAKmH,cAAc9H,mBAAUC,MAAMqB,SAASkG,SAASO,MAClE3B,KAAOA,MAAQxG,KAAKoI,iBACf3B,iBAAiBD,KAkB9BS,wBAAwBD,QACD,KAAfA,OAAOR,UACFC,iBAAiBO,OAAOR,KA8BrCqB,gBAAgBQ,aACN7B,IAAM6B,MAAMF,MACHnI,KAAKe,KAAKmH,cAAc9H,mBAAUC,MAAMwG,QAAQK,QACxDoB,WAAqB,KAAR9B,MAAc,uBAAWA"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js b/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js index b7d4ab2130a..15ad64d4c37 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js @@ -1,3 +1,12 @@ -define("tiny_media/embed/embedhelpers",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.insertMediaTemplateContext=void 0;_exports.insertMediaTemplateContext=props=>({mediaType:props.mediaType,showDropzone:props.canShowDropZone,showFilePicker:props.canShowFilePicker})})); +define("tiny_media/embed/embedhelpers",["exports","../selectors","../helpers","core/str","../common","editor_tiny/options"],(function(_exports,_selectors,_helpers,_str,_common,_options){var obj; +/** + * Tiny media plugin embed helpers. + * + * This provides easy access to any classes without instantiating a new object. + * + * @module tiny_media/embed/embedhelpers + * @copyright 2024 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.prepareMoodleLang=_exports.mediaDetailsTemplateContext=_exports.isUrlFromKnownMediaSites=_exports.insertMediaTemplateContext=_exports.getSelectedMediaElement=_exports.getHelpStrings=_exports.formatMediaUrl=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};_exports.insertMediaTemplateContext=props=>({mediaType:props.mediaType,showDropzone:props.canShowDropZone,showFilePicker:props.canShowFilePicker});_exports.getSelectedMediaElement=editor=>{let mediaType=null,selectedMedia=null;const mediaElm=editor.selection.getNode();return mediaElm?"video"===mediaElm.nodeName.toLowerCase()||"audio"===mediaElm.nodeName.toLowerCase()?(mediaType=mediaElm.nodeName.toLowerCase(),selectedMedia=mediaElm):"a"===mediaElm.nodeName.toLowerCase()?(mediaType="link",selectedMedia=mediaElm):mediaElm.querySelector("video")?(mediaType="video",selectedMedia=mediaElm.querySelector("video")):mediaElm.querySelector("audio")&&(mediaType="audio",selectedMedia=mediaElm.querySelector("audio")):(mediaType=null,selectedMedia=null),[mediaType,selectedMedia]};_exports.formatMediaUrl=url=>{const params=(0,_helpers.convertStringUrlToObject)(url);if(url.includes(_selectors.default.EMBED.mediaSites.youtube)){let fetchedUrl=null,fetchedUrlValue=null;for(const k in params)if(url.includes(k)){fetchedUrl=k,fetchedUrlValue=params[k],delete params[k];break}url=(url=fetchedUrl.replace("watch?v","embed/"))+fetchedUrlValue+"?"+(0,_helpers.createUrlParams)(params)}return url};_exports.isUrlFromKnownMediaSites=url=>{let state=!1;const sites=_selectors.default.EMBED.mediaSites;for(const site in sites)if(url.includes(sites[site])){state=!0;break}return state};_exports.mediaDetailsTemplateContext=async props=>{const context={bodyTemplate:_selectors.default.EMBED.template.body.mediaDetailsBody,footerTemplate:_selectors.default.EMBED.template.footer.mediaDetailsFooter,isVideo:"video"===props.mediaType,isAudio:"audio"===props.mediaType,isLink:"link"===props.mediaType,isUpdating:props.isUpdating};return props.mediaData?{...context,...props.mediaData}:{...context,...await props.mediaTemplateContext}};_exports.getHelpStrings=async()=>{const[customsize]=await(0,_str.getStrings)(["customsize_help"].map((key=>({key:key,component:_common.component}))));return{customsize:customsize}};_exports.prepareMoodleLang=editor=>{const moodleLangs=(0,_options.getMoodleLang)(editor),currentLanguage=(0,_options.getCurrentLanguage)(editor);return{installed:Object.entries(moodleLangs.installed).map((_ref=>{let[lang,code]=_ref;return{lang:lang,code:code,default:lang===currentLanguage}})),available:Object.entries(moodleLangs.available).map((_ref2=>{let[lang,code]=_ref2;return{lang:lang,code:code,default:lang===currentLanguage}}))}}})); //# sourceMappingURL=embedhelpers.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js.map b/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js.map index 0ceeb8fa010..eb8d2c0f345 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js.map +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedhelpers.min.js.map @@ -1 +1 @@ -{"version":3,"file":"embedhelpers.min.js","sources":["../../src/embed/embedhelpers.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 embed helpers.\n *\n * This provides easy access to any classes without instantiating a new object.\n *\n * @module tiny_media/embed/embedhelpers\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Return template context for insert media.\n *\n * @param {object} props\n * @returns {object}\n */\nexport const insertMediaTemplateContext = (props) => {\n return {\n mediaType: props.mediaType,\n showDropzone: props.canShowDropZone,\n showFilePicker: props.canShowFilePicker,\n };\n};\n"],"names":["props","mediaType","showDropzone","canShowDropZone","showFilePicker","canShowFilePicker"],"mappings":"8MA+B2CA,QAChC,CACHC,UAAWD,MAAMC,UACjBC,aAAcF,MAAMG,gBACpBC,eAAgBJ,MAAMK"} \ No newline at end of file +{"version":3,"file":"embedhelpers.min.js","sources":["../../src/embed/embedhelpers.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 embed helpers.\n *\n * This provides easy access to any classes without instantiating a new object.\n *\n * @module tiny_media/embed/embedhelpers\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Selectors from '../selectors';\nimport {\n convertStringUrlToObject,\n createUrlParams,\n} from '../helpers';\nimport {getStrings} from 'core/str';\nimport {component} from \"../common\";\nimport {\n getCurrentLanguage,\n getMoodleLang\n} from 'editor_tiny/options';\n\n/**\n * Return template context for insert media.\n *\n * @param {object} props\n * @returns {object}\n */\nexport const insertMediaTemplateContext = (props) => {\n return {\n mediaType: props.mediaType,\n showDropzone: props.canShowDropZone,\n showFilePicker: props.canShowFilePicker,\n };\n};\n\n/**\n * Return selected media type and element.\n *\n * @param {editor} editor\n * @returns {Array}\n */\nexport const getSelectedMediaElement = (editor) => {\n let mediaType = null;\n let selectedMedia = null;\n const mediaElm = editor.selection.getNode();\n\n if (!mediaElm) {\n mediaType = null;\n selectedMedia = null;\n } else if (mediaElm.nodeName.toLowerCase() === 'video' || mediaElm.nodeName.toLowerCase() === 'audio') {\n mediaType = mediaElm.nodeName.toLowerCase();\n selectedMedia = mediaElm;\n } else if (mediaElm.nodeName.toLowerCase() === 'a') {\n mediaType = 'link';\n selectedMedia = mediaElm;\n } else if (mediaElm.querySelector('video')) {\n mediaType = 'video';\n selectedMedia = mediaElm.querySelector('video');\n } else if (mediaElm.querySelector('audio')) {\n mediaType = 'audio';\n selectedMedia = mediaElm.querySelector('audio');\n }\n\n return [mediaType, selectedMedia];\n};\n\n/**\n * Format url when inserting media link to be previewed.\n *\n * @param {string} url\n * @returns {string}\n */\nexport const formatMediaUrl = (url) => {\n // Convert the string url into url param object.\n const params = convertStringUrlToObject(url);\n\n // Format the url for youtube links.\n if (url.includes(Selectors.EMBED.mediaSites.youtube)) {\n let fetchedUrl = null;\n let fetchedUrlValue = null;\n for (const k in params) {\n if (url.includes(k)) {\n fetchedUrl = k;\n fetchedUrlValue = params[k];\n delete params[k];\n break;\n }\n }\n url = fetchedUrl.replace('watch?v', 'embed/');\n url = url + fetchedUrlValue + '?' + createUrlParams(params);\n }\n return url;\n};\n\n/**\n * Check if the url is from a known media site.\n *\n * @param {string} url\n * @returns {boolean}\n */\nexport const isUrlFromKnownMediaSites = (url) => {\n let state = false;\n const sites = Selectors.EMBED.mediaSites;\n for (const site in sites) {\n if (url.includes(sites[site])) {\n state = true;\n break;\n }\n }\n return state;\n};\n\n/**\n * Return template context for media details.\n *\n * @param {object} props\n * @returns {object}\n */\nexport const mediaDetailsTemplateContext = async(props) => {\n const context = {\n bodyTemplate: Selectors.EMBED.template.body.mediaDetailsBody,\n footerTemplate: Selectors.EMBED.template.footer.mediaDetailsFooter,\n isVideo: (props.mediaType === 'video'),\n isAudio: (props.mediaType === 'audio'),\n isLink: (props.mediaType === 'link'),\n isUpdating: props.isUpdating,\n };\n\n if (props.mediaData) {\n return {\n ...context,\n ...props.mediaData,\n };\n } else {\n return {\n ...context,\n ...await props.mediaTemplateContext,\n };\n }\n};\n\n/**\n * Get help strings.\n *\n * @returns {object}\n */\nexport const getHelpStrings = async() => {\n const [\n customsize,\n ] = await getStrings([\n 'customsize_help',\n ].map((key) => ({\n key,\n component,\n })));\n\n return {\n customsize,\n };\n};\n\n/**\n * Get current moodle languages.\n *\n * @param {editor} editor\n * @returns {object}\n */\nexport const prepareMoodleLang = (editor) => {\n const moodleLangs = getMoodleLang(editor);\n const currentLanguage = getCurrentLanguage(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"],"names":["props","mediaType","showDropzone","canShowDropZone","showFilePicker","canShowFilePicker","editor","selectedMedia","mediaElm","selection","getNode","nodeName","toLowerCase","querySelector","url","params","includes","Selectors","EMBED","mediaSites","youtube","fetchedUrl","fetchedUrlValue","k","replace","state","sites","site","async","context","bodyTemplate","template","body","mediaDetailsBody","footerTemplate","footer","mediaDetailsFooter","isVideo","isAudio","isLink","isUpdating","mediaData","mediaTemplateContext","customsize","map","key","component","moodleLangs","currentLanguage","installed","Object","entries","_ref","lang","code","available","_ref2"],"mappings":";;;;;;;;;6XA2C2CA,QAChC,CACHC,UAAWD,MAAMC,UACjBC,aAAcF,MAAMG,gBACpBC,eAAgBJ,MAAMK,qDAUUC,aAChCL,UAAY,KACZM,cAAgB,WACdC,SAAWF,OAAOG,UAAUC,iBAE7BF,SAG0C,UAApCA,SAASG,SAASC,eAAiE,UAApCJ,SAASG,SAASC,eACxEX,UAAYO,SAASG,SAASC,cAC9BL,cAAgBC,UAC2B,MAApCA,SAASG,SAASC,eACzBX,UAAY,OACZM,cAAgBC,UACTA,SAASK,cAAc,UAC9BZ,UAAY,QACZM,cAAgBC,SAASK,cAAc,UAChCL,SAASK,cAAc,WAC9BZ,UAAY,QACZM,cAAgBC,SAASK,cAAc,WAbvCZ,UAAY,KACZM,cAAgB,MAeb,CAACN,UAAWM,wCASQO,YAErBC,QAAS,qCAAyBD,QAGpCA,IAAIE,SAASC,mBAAUC,MAAMC,WAAWC,SAAU,KAC9CC,WAAa,KACbC,gBAAkB,SACjB,MAAMC,KAAKR,UACRD,IAAIE,SAASO,GAAI,CACjBF,WAAaE,EACbD,gBAAkBP,OAAOQ,UAClBR,OAAOQ,SAKtBT,KADAA,IAAMO,WAAWG,QAAQ,UAAW,WACxBF,gBAAkB,KAAM,4BAAgBP,eAEjDD,uCAS8BA,UACjCW,OAAQ,QACNC,MAAQT,mBAAUC,MAAMC,eACzB,MAAMQ,QAAQD,SACXZ,IAAIE,SAASU,MAAMC,OAAQ,CAC3BF,OAAQ,eAITA,4CASgCG,MAAAA,cACjCC,QAAU,CACZC,aAAcb,mBAAUC,MAAMa,SAASC,KAAKC,iBAC5CC,eAAgBjB,mBAAUC,MAAMa,SAASI,OAAOC,mBAChDC,QAA8B,UAApBrC,MAAMC,UAChBqC,QAA8B,UAApBtC,MAAMC,UAChBsC,OAA6B,SAApBvC,MAAMC,UACfuC,WAAYxC,MAAMwC,mBAGlBxC,MAAMyC,UACC,IACAZ,WACA7B,MAAMyC,WAGN,IACAZ,iBACM7B,MAAM0C,+CAUGd,gBAEtBe,kBACM,mBAAW,CACjB,mBACFC,KAAKC,OACHA,IAAAA,IACAC,UAAAA,6BAGG,CACHH,WAAAA,wCAU0BrC,eACxByC,aAAc,0BAAczC,QAC5B0C,iBAAkB,+BAAmB1C,cAcpC,CACH2C,UAbcC,OAAOC,QAAQJ,YAAYE,WAAWL,KAAIQ,WAAEC,KAAMC,iBAAW,CAC3ED,KAAAA,KACAC,KAAAA,aACWD,OAASL,oBAWpBO,UARcL,OAAOC,QAAQJ,YAAYQ,WAAWX,KAAIY,YAAEH,KAAMC,kBAAW,CAC3ED,KAAAA,KACAC,KAAAA,aACWD,OAASL"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js b/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js index 433f609cb6a..209ef2648dd 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js @@ -1,3 +1,3 @@ -define("tiny_media/embed/embedinsert",["exports","core/prefetch","core/str","../common","../helpers","../selectors","core/dropzone","editor_tiny/uploader"],(function(_exports,_prefetch,_str,_common,_helpers,_selectors,_dropzone,_uploader){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.EmbedInsert=void 0,_selectors=_interopRequireDefault(_selectors),_dropzone=_interopRequireDefault(_dropzone),_uploader=_interopRequireDefault(_uploader),(0,_prefetch.prefetchStrings)("tiny_media",["insertmedia","addmediafilesdrop","uploading","loadingmedia"]);_exports.EmbedInsert=class{constructor(data){var _this=this;_defineProperty(this,"init",(async()=>{const langStringKeys=["insertmedia","addmediafilesdrop","uploading","loadingmedia"],langStringValues=await(0,_str.getStrings)([...langStringKeys].map((key=>({key:key,component:_common.component}))));if(this.langStrings=Object.fromEntries(langStringKeys.map(((key,index)=>[key,langStringValues[index]]))),this.currentModal.setTitle(this.langStrings.insertmedia),this.canShowDropZone&&!this.mediaType){const dropZoneEle=document.querySelector(_selectors.default.EMBED.elements.dropzoneContainer),dropZone=new _dropzone.default(dropZoneEle,"audio/*,video/*",(files=>{this.handleUploadedFile(files)}));dropZone.setLabel(this.langStrings.addmediafilesdrop),dropZone.init()}})),_defineProperty(this,"updateLoaderIcon",(function(root,langStrings){let progress=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const loaderIcon=_this.root.querySelector(_selectors.default.EMBED.elements.loaderIcon);loaderIcon&&loaderIcon.classList.contains("d-none")&&(0,_helpers.showElements)(_selectors.default.EMBED.elements.loaderIcon);const loaderIconState=root.querySelector(_selectors.default.EMBED.elements.loaderIconContainer+" div");loaderIconState.innerHTML=null!==progress?"".concat(langStrings.uploading," ").concat(Math.round(progress),"%"):langStrings.loadingmedia})),_defineProperty(this,"filePickerCallback",(params=>{params.url&&(window.console.log(params.url),(0,_helpers.stopMediaLoading)(this.root,"EMBED"))})),_defineProperty(this,"handleUploadedFile",(async files=>{try{(0,_helpers.startMediaLoading)(this.root,"EMBED");const fileURL=await(0,_uploader.default)(this.editor,"media",files[0],files[0].name,(progress=>{this.updateLoaderIcon(this.root,this.langStrings,progress)}));this.updateLoaderIcon(this.root,this.langStrings),this.filePickerCallback({url:fileURL})}catch(error){this.root.querySelector(_selectors.default.EMBED.elements.urlWarning).innerHTML=void 0!==error.error?error.error:error,(0,_helpers.showElements)(_selectors.default.EMBED.elements.urlWarning,this.root),(0,_helpers.stopMediaLoading)(this.root,"EMBED")}})),(0,_helpers.setPropertiesFromData)(this,data)}}})); +define("tiny_media/embed/embedinsert",["exports","core/prefetch","core/str","../common","../helpers","../selectors","core/dropzone","editor_tiny/uploader","./embedhandler","core/notification","./embedhelpers","./embedpreview"],(function(_exports,_prefetch,_str,_common,_helpers,_selectors,_dropzone,_uploader,_embedhandler,_notification,_embedhelpers,_embedpreview){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.EmbedInsert=void 0,_selectors=_interopRequireDefault(_selectors),_dropzone=_interopRequireDefault(_dropzone),_uploader=_interopRequireDefault(_uploader),(0,_prefetch.prefetchStrings)("tiny_media",["insertmedia","addmediafilesdrop","uploading","loadingmedia"]);_exports.EmbedInsert=class{constructor(data){var _this=this;_defineProperty(this,"init",(async()=>{const langStringKeys=["insertmedia","addmediafilesdrop","uploading","loadingmedia"],langStringValues=await(0,_str.getStrings)([...langStringKeys].map((key=>({key:key,component:_common.component}))));if(this.langStrings=Object.fromEntries(langStringKeys.map(((key,index)=>[key,langStringValues[index]]))),this.currentModal.setTitle(this.langStrings.insertmedia),this.canShowDropZone&&!this.mediaType){const dropZoneEle=document.querySelector(_selectors.default.EMBED.elements.dropzoneContainer),dropZone=new _dropzone.default(dropZoneEle,"audio/*,video/*",(files=>{this.handleUploadedFile(files)}));dropZone.setLabel(this.langStrings.addmediafilesdrop),dropZone.init()}})),_defineProperty(this,"loadMediaPreview",(async url=>{(0,_helpers.startMediaLoading)(this.root,"EMBED"),this.mediaSource=(0,_embedhelpers.formatMediaUrl)(url);const mediaType=await(0,_helpers.getFileMimeTypeFromUrl)(this.mediaSource);if(!_selectors.default.EMBED.mediaTypes.includes(mediaType))return(0,_notification.alert)(await(0,_str.getString)("onlymediafiles",_common.component),await(0,_str.getString)("onlymediafilesdesc",_common.component)),(0,_helpers.stopMediaLoading)(this.root,"EMBED"),void new _embedhandler.EmbedHandler(this).resetUploadForm();this.mediaType=mediaType;const templateContext=await(0,_embedhelpers.mediaDetailsTemplateContext)({...this,mediaTemplateContext:new _embedhandler.EmbedHandler(this).getMediaTemplateContext()});if(templateContext.selector="EMBED","video"===this.mediaType&&this.isUpdating){const media=templateContext.media;""!==media.height&&""!==media.width&&(this.mediaHeight=media.height,this.mediaWidth=media.width)}this.isUpdating&&(this.mediaTitle=templateContext.media.title),new _embedhandler.EmbedHandler(this).loadMediaDetails(new _embedpreview.EmbedPreview(this),templateContext)})),_defineProperty(this,"updateLoaderIcon",(function(root,langStrings){let progress=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const loaderIcon=_this.root.querySelector(_selectors.default.EMBED.elements.loaderIcon);loaderIcon&&loaderIcon.classList.contains("d-none")&&(0,_helpers.showElements)(_selectors.default.EMBED.elements.loaderIcon);const loaderIconState=root.querySelector(_selectors.default.EMBED.elements.loaderIconContainer+" div");loaderIconState.innerHTML=null!==progress?"".concat(langStrings.uploading," ").concat(Math.round(progress),"%"):langStrings.loadingmedia})),_defineProperty(this,"filePickerCallback",(params=>{params.url&&this.loadMediaPreview(params.url)})),_defineProperty(this,"handleUploadedFile",(async files=>{try{(0,_helpers.startMediaLoading)(this.root,"EMBED");const fileURL=await(0,_uploader.default)(this.editor,"media",files[0],files[0].name,(progress=>{this.updateLoaderIcon(this.root,this.langStrings,progress)}));this.updateLoaderIcon(this.root,this.langStrings),this.filePickerCallback({url:fileURL})}catch(error){this.root.querySelector(_selectors.default.EMBED.elements.urlWarning).innerHTML=void 0!==error.error?error.error:error,(0,_helpers.showElements)(_selectors.default.EMBED.elements.urlWarning,this.root),(0,_helpers.stopMediaLoading)(this.root,"EMBED")}})),(0,_helpers.setPropertiesFromData)(this,data)}}})); //# sourceMappingURL=embedinsert.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js.map b/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js.map index cdbced43343..4ed388b9ef0 100644 --- a/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js.map +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedinsert.min.js.map @@ -1 +1 @@ -{"version":3,"file":"embedinsert.min.js","sources":["../../src/embed/embedinsert.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 embed upload class.\n *\n * This handles the embed upload using url, drag-drop and repositories.\n *\n * @module tiny_media/embed/embedinsert\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {prefetchStrings} from 'core/prefetch';\nimport {getStrings} from 'core/str';\nimport {component} from \"../common\";\nimport {\n setPropertiesFromData,\n startMediaLoading,\n stopMediaLoading,\n showElements,\n} from '../helpers';\nimport Selectors from \"../selectors\";\nimport Dropzone from 'core/dropzone';\nimport uploadFile from 'editor_tiny/uploader';\n\nprefetchStrings('tiny_media', [\n 'insertmedia',\n 'addmediafilesdrop',\n 'uploading',\n 'loadingmedia',\n]);\n\nexport class EmbedInsert {\n\n constructor(data) {\n setPropertiesFromData(this, data); // Creates dynamic properties based on \"data\" param.\n }\n\n /**\n * Init the dropzone and lang strings.\n */\n init = async() => {\n const langStringKeys = [\n 'insertmedia',\n 'addmediafilesdrop',\n 'uploading',\n 'loadingmedia',\n ];\n const langStringValues = await getStrings([...langStringKeys].map((key) => ({key, component})));\n this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringValues[index]]));\n this.currentModal.setTitle(this.langStrings.insertmedia);\n\n // Let's init the dropzone if canShowDropZone is true and mediaType is null.\n if (this.canShowDropZone && !this.mediaType) {\n const dropZoneEle = document.querySelector(Selectors.EMBED.elements.dropzoneContainer);\n const dropZone = new Dropzone(\n dropZoneEle,\n 'audio/*,video/*',\n files => {\n this.handleUploadedFile(files);\n }\n );\n\n dropZone.setLabel(this.langStrings.addmediafilesdrop);\n dropZone.init();\n }\n };\n\n /**\n * Updates the content of the loader icon.\n *\n * @param {HTMLElement} root - The root element containing the loader icon.\n * @param {object} langStrings - An object containing language strings.\n * @param {number|null} progress - The progress percentage (optional).\n * @returns {void}\n */\n updateLoaderIcon = (root, langStrings, progress = null) => {\n const loaderIcon = this.root.querySelector(Selectors.EMBED.elements.loaderIcon);\n if (loaderIcon && loaderIcon.classList.contains('d-none')) {\n showElements(Selectors.EMBED.elements.loaderIcon);\n }\n\n const loaderIconState = root.querySelector(Selectors.EMBED.elements.loaderIconContainer + ' div');\n loaderIconState.innerHTML = (progress !== null) ?\n `${langStrings.uploading} ${Math.round(progress)}%` :\n langStrings.loadingmedia;\n };\n\n /**\n * Handles media preview on file picker callback.\n *\n * @param {object} params Object of uploaded file\n */\n filePickerCallback = (params) => {\n if (params.url) {\n window.console.log(params.url);\n stopMediaLoading(this.root, 'EMBED');\n }\n };\n\n /**\n * Handles the uploaded file, initiates the upload process, and updates the UI during the upload.\n *\n * @param {FileList} files - The list of files to upload (usually from a file input field).\n * @returns {Promise} A promise that resolves when the file is uploaded and processed.\n */\n handleUploadedFile = async(files) => {\n try {\n startMediaLoading(this.root, 'EMBED');\n const fileURL = await uploadFile(this.editor, 'media', files[0], files[0].name, (progress) => {\n this.updateLoaderIcon(this.root, this.langStrings, progress);\n });\n\n // Set the loader icon content to \"loading\" after the file upload completes.\n this.updateLoaderIcon(this.root, this.langStrings);\n this.filePickerCallback({url: fileURL});\n } catch (error) {\n // Handle the error.\n const urlWarningLabelEle = this.root.querySelector(Selectors.EMBED.elements.urlWarning);\n urlWarningLabelEle.innerHTML = error.error !== undefined ? error.error : error;\n showElements(Selectors.EMBED.elements.urlWarning, this.root);\n stopMediaLoading(this.root, 'EMBED');\n }\n };\n}\n"],"names":["constructor","data","async","langStringKeys","langStringValues","map","key","component","langStrings","Object","fromEntries","index","currentModal","setTitle","this","insertmedia","canShowDropZone","mediaType","dropZoneEle","document","querySelector","Selectors","EMBED","elements","dropzoneContainer","dropZone","Dropzone","files","handleUploadedFile","setLabel","addmediafilesdrop","init","root","progress","loaderIcon","_this","classList","contains","loaderIconState","loaderIconContainer","innerHTML","uploading","Math","round","loadingmedia","params","url","window","console","log","fileURL","editor","name","updateLoaderIcon","filePickerCallback","error","urlWarning","undefined"],"mappings":"6tBAsCgB,aAAc,CAC1B,cACA,oBACA,YACA,4CAKAA,YAAYC,kDAOLC,gBACGC,eAAiB,CACnB,cACA,oBACA,YACA,gBAEEC,uBAAyB,mBAAW,IAAID,gBAAgBE,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,+BAC7EC,YAAcC,OAAOC,YAAYP,eAAeE,KAAI,CAACC,IAAKK,QAAU,CAACL,IAAKF,iBAAiBO,gBAC3FC,aAAaC,SAASC,KAAKN,YAAYO,aAGxCD,KAAKE,kBAAoBF,KAAKG,UAAW,OACnCC,YAAcC,SAASC,cAAcC,mBAAUC,MAAMC,SAASC,mBAC9DC,SAAW,IAAIC,kBACjBR,YACA,mBACAS,aACSC,mBAAmBD,UAIhCF,SAASI,SAASf,KAAKN,YAAYsB,mBACnCL,SAASM,oDAYE,SAACC,KAAMxB,iBAAayB,gEAAW,WACxCC,WAAaC,MAAKH,KAAKZ,cAAcC,mBAAUC,MAAMC,SAASW,YAChEA,YAAcA,WAAWE,UAAUC,SAAS,qCAC/BhB,mBAAUC,MAAMC,SAASW,kBAGpCI,gBAAkBN,KAAKZ,cAAcC,mBAAUC,MAAMC,SAASgB,oBAAsB,QAC1FD,gBAAgBE,UAA0B,OAAbP,mBACHzB,YAAYiC,sBAAaC,KAAKC,MAAMV,eACvCzB,YAAYoC,2DAQjBC,SACdA,OAAOC,MACPC,OAAOC,QAAQC,IAAIJ,OAAOC,mCACThC,KAAKkB,KAAM,wDAUf9B,MAAAA,2CAEKY,KAAKkB,KAAM,eACvBkB,cAAgB,qBAAWpC,KAAKqC,OAAQ,QAASxB,MAAM,GAAIA,MAAM,GAAGyB,MAAOnB,gBACxEoB,iBAAiBvC,KAAKkB,KAAMlB,KAAKN,YAAayB,kBAIlDoB,iBAAiBvC,KAAKkB,KAAMlB,KAAKN,kBACjC8C,mBAAmB,CAACR,IAAKI,UAChC,MAAOK,OAEsBzC,KAAKkB,KAAKZ,cAAcC,mBAAUC,MAAMC,SAASiC,YACzDhB,eAA4BiB,IAAhBF,MAAMA,MAAsBA,MAAMA,MAAQA,gCAC5DlC,mBAAUC,MAAMC,SAASiC,WAAY1C,KAAKkB,oCACtClB,KAAKkB,KAAM,gDAtFVlB,KAAMb"} \ No newline at end of file +{"version":3,"file":"embedinsert.min.js","sources":["../../src/embed/embedinsert.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 embed upload class.\n *\n * This handles the embed upload using url, drag-drop and repositories.\n *\n * @module tiny_media/embed/embedinsert\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {prefetchStrings} from 'core/prefetch';\nimport {getStrings, getString} from 'core/str';\nimport {component} from \"../common\";\nimport {\n setPropertiesFromData,\n startMediaLoading,\n stopMediaLoading,\n showElements,\n getFileMimeTypeFromUrl,\n} from '../helpers';\nimport Selectors from \"../selectors\";\nimport Dropzone from 'core/dropzone';\nimport uploadFile from 'editor_tiny/uploader';\nimport {EmbedHandler} from './embedhandler';\nimport {alert} from 'core/notification';\nimport {\n formatMediaUrl,\n mediaDetailsTemplateContext,\n} from './embedhelpers';\nimport {EmbedPreview} from './embedpreview';\n\nprefetchStrings('tiny_media', [\n 'insertmedia',\n 'addmediafilesdrop',\n 'uploading',\n 'loadingmedia',\n]);\n\nexport class EmbedInsert {\n\n constructor(data) {\n setPropertiesFromData(this, data); // Creates dynamic properties based on \"data\" param.\n }\n\n /**\n * Init the dropzone and lang strings.\n */\n init = async() => {\n const langStringKeys = [\n 'insertmedia',\n 'addmediafilesdrop',\n 'uploading',\n 'loadingmedia',\n ];\n const langStringValues = await getStrings([...langStringKeys].map((key) => ({key, component})));\n this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringValues[index]]));\n this.currentModal.setTitle(this.langStrings.insertmedia);\n\n // Let's init the dropzone if canShowDropZone is true and mediaType is null.\n if (this.canShowDropZone && !this.mediaType) {\n const dropZoneEle = document.querySelector(Selectors.EMBED.elements.dropzoneContainer);\n const dropZone = new Dropzone(\n dropZoneEle,\n 'audio/*,video/*',\n files => {\n this.handleUploadedFile(files);\n }\n );\n\n dropZone.setLabel(this.langStrings.addmediafilesdrop);\n dropZone.init();\n }\n };\n\n /**\n * Loads and displays a preview media based on the provided URL, and handles media loading events.\n *\n * @param {string} url - The URL of the media to load and display.\n */\n loadMediaPreview = async(url) => {\n startMediaLoading(this.root, 'EMBED');\n this.mediaSource = formatMediaUrl(url);\n\n // Get media mime type.\n const mediaType = await getFileMimeTypeFromUrl(this.mediaSource);\n\n // Check if media type is acceptable.\n if (!Selectors.EMBED.mediaTypes.includes(mediaType)) {\n alert(\n await getString('onlymediafiles', component),\n await getString('onlymediafilesdesc', component)\n );\n\n stopMediaLoading(this.root, 'EMBED');\n (new EmbedHandler(this)).resetUploadForm();\n return;\n }\n\n // Set mediaType to newly fetched mime type.\n this.mediaType = mediaType;\n\n // Construct templateContext for embed preview.\n const templateContext = await mediaDetailsTemplateContext({\n ...this,\n // Get data from the selected media element.\n mediaTemplateContext: (new EmbedHandler(this)).getMediaTemplateContext(),\n });\n templateContext.selector = 'EMBED';\n\n if (this.mediaType === 'video' && this.isUpdating) {\n // Let's get selected video height & width and create props for them to be used in embedPreview.\n const media = templateContext.media;\n if (media.height !== '' && media.width !== '') {\n this.mediaHeight = media.height;\n this.mediaWidth = media.width;\n }\n }\n\n if (this.isUpdating) {\n // Will be used to set the media title if it's in update state.\n this.mediaTitle = templateContext.media.title;\n }\n\n // Load the media details and preview of the selected media.\n (new EmbedHandler(this)).loadMediaDetails(new EmbedPreview(this), templateContext);\n };\n\n /**\n * Updates the content of the loader icon.\n *\n * @param {HTMLElement} root - The root element containing the loader icon.\n * @param {object} langStrings - An object containing language strings.\n * @param {number|null} progress - The progress percentage (optional).\n * @returns {void}\n */\n updateLoaderIcon = (root, langStrings, progress = null) => {\n const loaderIcon = this.root.querySelector(Selectors.EMBED.elements.loaderIcon);\n if (loaderIcon && loaderIcon.classList.contains('d-none')) {\n showElements(Selectors.EMBED.elements.loaderIcon);\n }\n\n const loaderIconState = root.querySelector(Selectors.EMBED.elements.loaderIconContainer + ' div');\n loaderIconState.innerHTML = (progress !== null) ?\n `${langStrings.uploading} ${Math.round(progress)}%` :\n langStrings.loadingmedia;\n };\n\n /**\n * Handles media preview on file picker callback.\n *\n * @param {object} params Object of uploaded file\n */\n filePickerCallback = (params) => {\n if (params.url) {\n this.loadMediaPreview(params.url);\n }\n };\n\n /**\n * Handles the uploaded file, initiates the upload process, and updates the UI during the upload.\n *\n * @param {FileList} files - The list of files to upload (usually from a file input field).\n * @returns {Promise} A promise that resolves when the file is uploaded and processed.\n */\n handleUploadedFile = async(files) => {\n try {\n startMediaLoading(this.root, 'EMBED');\n const fileURL = await uploadFile(this.editor, 'media', files[0], files[0].name, (progress) => {\n this.updateLoaderIcon(this.root, this.langStrings, progress);\n });\n\n // Set the loader icon content to \"loading\" after the file upload completes.\n this.updateLoaderIcon(this.root, this.langStrings);\n this.filePickerCallback({url: fileURL});\n } catch (error) {\n // Handle the error.\n const urlWarningLabelEle = this.root.querySelector(Selectors.EMBED.elements.urlWarning);\n urlWarningLabelEle.innerHTML = error.error !== undefined ? error.error : error;\n showElements(Selectors.EMBED.elements.urlWarning, this.root);\n stopMediaLoading(this.root, 'EMBED');\n }\n };\n}\n"],"names":["constructor","data","async","langStringKeys","langStringValues","map","key","component","langStrings","Object","fromEntries","index","currentModal","setTitle","this","insertmedia","canShowDropZone","mediaType","dropZoneEle","document","querySelector","Selectors","EMBED","elements","dropzoneContainer","dropZone","Dropzone","files","handleUploadedFile","setLabel","addmediafilesdrop","init","root","mediaSource","url","mediaTypes","includes","EmbedHandler","resetUploadForm","templateContext","mediaTemplateContext","getMediaTemplateContext","selector","isUpdating","media","height","width","mediaHeight","mediaWidth","mediaTitle","title","loadMediaDetails","EmbedPreview","progress","loaderIcon","_this","classList","contains","loaderIconState","loaderIconContainer","innerHTML","uploading","Math","round","loadingmedia","params","loadMediaPreview","fileURL","editor","name","updateLoaderIcon","filePickerCallback","error","urlWarning","undefined"],"mappings":"41BA8CgB,aAAc,CAC1B,cACA,oBACA,YACA,4CAKAA,YAAYC,kDAOLC,gBACGC,eAAiB,CACnB,cACA,oBACA,YACA,gBAEEC,uBAAyB,mBAAW,IAAID,gBAAgBE,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,+BAC7EC,YAAcC,OAAOC,YAAYP,eAAeE,KAAI,CAACC,IAAKK,QAAU,CAACL,IAAKF,iBAAiBO,gBAC3FC,aAAaC,SAASC,KAAKN,YAAYO,aAGxCD,KAAKE,kBAAoBF,KAAKG,UAAW,OACnCC,YAAcC,SAASC,cAAcC,mBAAUC,MAAMC,SAASC,mBAC9DC,SAAW,IAAIC,kBACjBR,YACA,mBACAS,aACSC,mBAAmBD,UAIhCF,SAASI,SAASf,KAAKN,YAAYsB,mBACnCL,SAASM,oDASE7B,MAAAA,qCACGY,KAAKkB,KAAM,cACxBC,aAAc,gCAAeC,WAG5BjB,gBAAkB,mCAAuBH,KAAKmB,iBAG/CZ,mBAAUC,MAAMa,WAAWC,SAASnB,+CAE3B,kBAAU,iBAAkBV,yBAC5B,kBAAU,qBAAsBA,kDAGzBO,KAAKkB,KAAM,kBACvBK,2BAAavB,MAAOwB,uBAKxBrB,UAAYA,gBAGXsB,sBAAwB,6CAA4B,IACnDzB,KAEH0B,qBAAuB,IAAIH,2BAAavB,MAAO2B,+BAEnDF,gBAAgBG,SAAW,QAEJ,UAAnB5B,KAAKG,WAAyBH,KAAK6B,WAAY,OAEzCC,MAAQL,gBAAgBK,MACT,KAAjBA,MAAMC,QAAiC,KAAhBD,MAAME,aACxBC,YAAcH,MAAMC,YACpBG,WAAaJ,MAAME,OAI5BhC,KAAK6B,kBAEAM,WAAaV,gBAAgBK,MAAMM,WAIvCb,2BAAavB,MAAOqC,iBAAiB,IAAIC,2BAAatC,MAAOyB,6DAWnD,SAACP,KAAMxB,iBAAa6C,gEAAW,WACxCC,WAAaC,MAAKvB,KAAKZ,cAAcC,mBAAUC,MAAMC,SAAS+B,YAChEA,YAAcA,WAAWE,UAAUC,SAAS,qCAC/BpC,mBAAUC,MAAMC,SAAS+B,kBAGpCI,gBAAkB1B,KAAKZ,cAAcC,mBAAUC,MAAMC,SAASoC,oBAAsB,QAC1FD,gBAAgBE,UAA0B,OAAbP,mBACH7C,YAAYqD,sBAAaC,KAAKC,MAAMV,eACvC7C,YAAYwD,2DAQjBC,SACdA,OAAO/B,UACFgC,iBAAiBD,OAAO/B,mDAUhBhC,MAAAA,2CAEKY,KAAKkB,KAAM,eACvBmC,cAAgB,qBAAWrD,KAAKsD,OAAQ,QAASzC,MAAM,GAAIA,MAAM,GAAG0C,MAAOhB,gBACxEiB,iBAAiBxD,KAAKkB,KAAMlB,KAAKN,YAAa6C,kBAIlDiB,iBAAiBxD,KAAKkB,KAAMlB,KAAKN,kBACjC+D,mBAAmB,CAACrC,IAAKiC,UAChC,MAAOK,OAEsB1D,KAAKkB,KAAKZ,cAAcC,mBAAUC,MAAMC,SAASkD,YACzDb,eAA4Bc,IAAhBF,MAAMA,MAAsBA,MAAMA,MAAQA,gCAC5DnD,mBAAUC,MAAMC,SAASkD,WAAY3D,KAAKkB,oCACtClB,KAAKkB,KAAM,gDA1IVlB,KAAMb"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js b/lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js new file mode 100644 index 00000000000..24dbaf3bd42 --- /dev/null +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js @@ -0,0 +1,3 @@ +define("tiny_media/embed/embedpreview",["exports","core/notification","../selectors","../common","core/str","../helpers","./embedhandler","../mediabase"],(function(_exports,_notification,_selectors,_common,_str,_helpers,_embedhandler,_mediabase){var 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.EmbedPreview=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};class EmbedPreview extends _mediabase.MediaBase{constructor(data){super(),_defineProperty(this,"selectorType","EMBED"),_defineProperty(this,"isEmbedPreviewDeleted",!1),_defineProperty(this,"init",(async()=>{this.currentModal.setTitle((0,_str.getString)("mediadetails",_common.component)),(0,_helpers.sourceTypeChecked)({source:this.mediaSource,root:this.root,urlSelector:_selectors.default.EMBED.elements.fromUrl,fileNameSelector:_selectors.default.EMBED.elements.fileNameLabel}),this.setMediaSourceAndPoster(),this.registerMediaDetailsEventListeners(this.currentModal)})),_defineProperty(this,"setMediaSourceAndPoster",(()=>{const box=this.root.querySelector(_selectors.default.EMBED.elements.previewBox),preview=this.root.querySelector(_selectors.default.EMBED.elements.preview);if(preview.src=this.mediaSource,preview.innerHTML=this.mediaSource,["video","audio"].includes(this.mediaType)){let fileName=(0,_helpers.getFileName)(this.root);this.isUpdating&&(this.isEmbedPreviewDeleted||(fileName=this.mediaTitle)),this.root.querySelector(_selectors.default.EMBED.elements.title).value=fileName}if(preview.addEventListener("error",(async()=>{(0,_notification.alert)(await(0,_str.getString)("medianotavailable",_common.component),await(0,_str.getString)("medianotavailabledesc",_common.component,this.mediaSource)),this.showBodyTemplate(),new _embedhandler.EmbedHandler(this).resetUploadForm()})),"video"===this.mediaType){let videoHeight=null,videoWidth=null;const videoTag=document.querySelector(_selectors.default.EMBED.elements.videoTag);this.thumbnail&&(videoTag.poster=this.thumbnail),videoTag.load(),videoTag.addEventListener("loadedmetadata",(()=>{this.showBodyTemplate(),videoHeight=videoTag.videoHeight,videoWidth=videoTag.videoWidth;const isLandscape=videoWidth-videoHeight>0;this.mediaDimensions={width:videoWidth,height:videoHeight},isLandscape?videoTag.width=box.offsetWidth:videoTag.height=box.offsetHeight})),videoTag.addEventListener("canplay",(()=>{const height=this.root.querySelector(_selectors.default.EMBED.elements.height),width=this.root.querySelector(_selectors.default.EMBED.elements.width);""===height.value&&""===width.value&&(height.value=videoHeight,width.value=videoWidth),videoHeight===parseInt(height.value)&&videoWidth===parseInt(width.value)?(this.currentWidth=this.mediaDimensions.width,this.currentHeight=this.mediaDimensions.height,this.sizeChecked("original")):(this.currentWidth=parseInt(width.value),this.currentHeight=parseInt(height.value),this.sizeChecked("custom"))}))}else if("audio"===this.mediaType){const audioTag=this.root.querySelector(_selectors.default.EMBED.elements.audioTag);audioTag.load(),audioTag.addEventListener("loadedmetadata",(()=>{this.showBodyTemplate()}))}else this.showBodyTemplate(),preview.width=box.offsetWidth,preview.height=box.offsetHeight})),_defineProperty(this,"showBodyTemplate",(()=>{(0,_helpers.stopMediaLoading)(this.root,"EMBED"),(0,_helpers.showElements)(_selectors.default.EMBED.elements.bodyTemplate,this.root)})),_defineProperty(this,"registerMediaDetailsEventListeners",(async()=>{const autoPlay=this.root.querySelector(_selectors.default.EMBED.elements.mediaAutoplay),mute=this.root.querySelector(_selectors.default.EMBED.elements.mediaMute);autoPlay&&mute&&"link"===this.mediaType&&(autoPlay.addEventListener("change",(()=>{autoPlay.checked&&(mute.checked=!0)})),mute.addEventListener("change",(()=>{autoPlay.checked&&!mute.checked&&(autoPlay.checked=!1)})));const sizeOriginalEle=this.root.querySelector(_selectors.default.EMBED.elements.sizeOriginal);sizeOriginalEle&&sizeOriginalEle.addEventListener("change",(()=>{this.sizeChecked("original")}));const sizeCustomEle=this.root.querySelector(_selectors.default.EMBED.elements.sizeCustom);sizeCustomEle&&sizeCustomEle.addEventListener("change",(()=>{this.sizeChecked("custom")}));const widthEle=this.root.querySelector(_selectors.default.EMBED.elements.width);widthEle&&widthEle.addEventListener("input",(()=>{widthEle.value=""===widthEle.value?0:Number(widthEle.value),this.autoAdjustSize()}));const heightEle=this.root.querySelector(_selectors.default.EMBED.elements.height);heightEle&&heightEle.addEventListener("input",(()=>{heightEle.value=""===heightEle.value?0:Number(heightEle.value),this.autoAdjustSize(!0)}))})),(0,_helpers.setPropertiesFromData)(this,data)}}_exports.EmbedPreview=EmbedPreview})); + +//# sourceMappingURL=embedpreview.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js.map b/lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js.map new file mode 100644 index 00000000000..ba9a94d3bf7 --- /dev/null +++ b/lib/editor/tiny/plugins/media/amd/build/embed/embedpreview.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"embedpreview.min.js","sources":["../../src/embed/embedpreview.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 embed preview and details class.\n *\n * This handles the embed file/url preview before embedding them into tiny editor.\n *\n * @module tiny_media/embed/embedpreview\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {alert} from 'core/notification';\nimport Selectors from '../selectors';\nimport {component} from '../common';\nimport {getString} from 'core/str';\nimport {\n sourceTypeChecked,\n getFileName,\n setPropertiesFromData,\n showElements,\n stopMediaLoading,\n} from '../helpers';\nimport {EmbedHandler} from './embedhandler';\nimport {MediaBase} from '../mediabase';\n\nexport class EmbedPreview extends MediaBase {\n\n selectorType = 'EMBED';\n\n isEmbedPreviewDeleted = false;\n\n constructor(data) {\n super();\n setPropertiesFromData(this, data); // Creates dynamic properties based on \"data\" param.\n }\n\n /**\n * Init the media details preview.\n */\n init = async() => {\n this.currentModal.setTitle(getString('mediadetails', component));\n sourceTypeChecked({\n source: this.mediaSource,\n root: this.root,\n urlSelector: Selectors.EMBED.elements.fromUrl,\n fileNameSelector: Selectors.EMBED.elements.fileNameLabel,\n });\n this.setMediaSourceAndPoster();\n this.registerMediaDetailsEventListeners(this.currentModal);\n };\n\n /**\n * Sets media source and thumbnail for the video.\n */\n setMediaSourceAndPoster = () => {\n const box = this.root.querySelector(Selectors.EMBED.elements.previewBox);\n const preview = this.root.querySelector(Selectors.EMBED.elements.preview);\n preview.src = this.mediaSource;\n preview.innerHTML = this.mediaSource;\n\n // Getting and setting up media title/name.\n if (['video', 'audio'].includes(this.mediaType)) {\n let fileName = getFileName(this.root); // Get original filename.\n if (this.isUpdating) {\n if (!this.isEmbedPreviewDeleted) {\n fileName = this.mediaTitle; // Title from the selected media.\n }\n }\n\n // Set the media name/title.\n this.root.querySelector(Selectors.EMBED.elements.title).value = fileName;\n }\n\n // Handle error when loading the media.\n preview.addEventListener('error', async() => {\n alert(\n await getString('medianotavailable', component),\n await getString('medianotavailabledesc', component, this.mediaSource)\n );\n\n // Stop the loader and display back the body template when failed to load the media.\n this.showBodyTemplate();\n\n (new EmbedHandler(this)).resetUploadForm();\n return;\n });\n\n if (this.mediaType === 'video') {\n let videoHeight = null;\n let videoWidth = null;\n const videoTag = document.querySelector(Selectors.EMBED.elements.videoTag);\n\n if (this.thumbnail) {\n videoTag.poster = this.thumbnail;\n }\n\n // Load the video html tag to load the media.\n videoTag.load();\n\n // Handle media metadata loading event.\n videoTag.addEventListener('loadedmetadata', () => {\n // Stop the loader and display back the body template when the media is loaded.\n this.showBodyTemplate();\n\n videoHeight = videoTag.videoHeight;\n videoWidth = videoTag.videoWidth;\n const widthProportion = (videoWidth - videoHeight);\n const isLandscape = widthProportion > 0;\n\n // Store dimensions of the raw video.\n this.mediaDimensions = {\n width: videoWidth,\n height: videoHeight,\n };\n\n // Set the media preview based on the media dimensions.\n if (isLandscape) {\n videoTag.width = box.offsetWidth;\n } else {\n videoTag.height = box.offsetHeight;\n }\n });\n\n // Handle media canplay event.\n videoTag.addEventListener('canplay', () => {\n const height = this.root.querySelector(Selectors.EMBED.elements.height);\n const width = this.root.querySelector(Selectors.EMBED.elements.width);\n\n if (height.value === '' && width.value === '') {\n height.value = videoHeight;\n width.value = videoWidth;\n }\n\n // Size checking and adjustment.\n if (videoHeight === parseInt(height.value) && videoWidth === parseInt(width.value)) {\n this.currentWidth = this.mediaDimensions.width;\n this.currentHeight = this.mediaDimensions.height;\n this.sizeChecked('original');\n } else {\n this.currentWidth = parseInt(width.value);\n this.currentHeight = parseInt(height.value);\n this.sizeChecked('custom');\n }\n });\n } else if (this.mediaType === 'audio') {\n const audioTag = this.root.querySelector(Selectors.EMBED.elements.audioTag);\n audioTag.load();\n\n // Handle media metadata loading event.\n audioTag.addEventListener('loadedmetadata', () => {\n // Stop the loader and display back the body template when the media is loaded.\n this.showBodyTemplate();\n });\n } else {\n // Stop the loader and display back the body template when the media is loaded.\n this.showBodyTemplate();\n\n // Set iframe width/height = box width/height.\n preview.width = box.offsetWidth;\n preview.height = box.offsetHeight;\n }\n };\n\n /**\n * Stop the loader and display back the body template.\n */\n showBodyTemplate = () => {\n stopMediaLoading(this.root, 'EMBED');\n showElements(Selectors.EMBED.elements.bodyTemplate, this.root);\n };\n\n /**\n * Only registers event listeners for new loaded elements in embed preview modal.\n */\n registerMediaDetailsEventListeners = async() => {\n // Handle media autoplay and mute.\n const autoPlay = this.root.querySelector(Selectors.EMBED.elements.mediaAutoplay);\n const mute = this.root.querySelector(Selectors.EMBED.elements.mediaMute);\n if (autoPlay && mute && this.mediaType === 'link') {\n autoPlay.addEventListener('change', () => {\n if (autoPlay.checked) {\n mute.checked = true;\n }\n });\n\n mute.addEventListener('change', () => {\n if (autoPlay.checked && !mute.checked) {\n autoPlay.checked = false;\n }\n });\n }\n\n // Handle the original size when selected.\n const sizeOriginalEle = this.root.querySelector(Selectors.EMBED.elements.sizeOriginal);\n if (sizeOriginalEle) {\n sizeOriginalEle.addEventListener('change', () => {\n this.sizeChecked('original');\n });\n }\n\n // Handle the custom size when selected.\n const sizeCustomEle = this.root.querySelector(Selectors.EMBED.elements.sizeCustom);\n if (sizeCustomEle) {\n sizeCustomEle.addEventListener('change', () => {\n this.sizeChecked('custom');\n });\n }\n\n // Handle the custom with size when inputted.\n const widthEle = this.root.querySelector(Selectors.EMBED.elements.width);\n if (widthEle) {\n widthEle.addEventListener('input', () => {\n // Avoid empty value.\n widthEle.value = widthEle.value === \"\" ? 0 : Number(widthEle.value);\n this.autoAdjustSize();\n });\n }\n\n // Handle the custom height size when inputted.\n const heightEle = this.root.querySelector(Selectors.EMBED.elements.height);\n if (heightEle) {\n heightEle.addEventListener('input', () => {\n // Avoid empty value.\n heightEle.value = heightEle.value === \"\" ? 0 : Number(heightEle.value);\n this.autoAdjustSize(true);\n });\n }\n };\n}\n"],"names":["EmbedPreview","MediaBase","constructor","data","async","currentModal","setTitle","component","source","this","mediaSource","root","urlSelector","Selectors","EMBED","elements","fromUrl","fileNameSelector","fileNameLabel","setMediaSourceAndPoster","registerMediaDetailsEventListeners","box","querySelector","previewBox","preview","src","innerHTML","includes","mediaType","fileName","isUpdating","isEmbedPreviewDeleted","mediaTitle","title","value","addEventListener","showBodyTemplate","EmbedHandler","resetUploadForm","videoHeight","videoWidth","videoTag","document","thumbnail","poster","load","isLandscape","mediaDimensions","width","height","offsetWidth","offsetHeight","parseInt","currentWidth","currentHeight","sizeChecked","audioTag","bodyTemplate","autoPlay","mediaAutoplay","mute","mediaMute","checked","sizeOriginalEle","sizeOriginal","sizeCustomEle","sizeCustom","widthEle","Number","autoAdjustSize","heightEle"],"mappings":"2jBAuCaA,qBAAqBC,qBAM9BC,YAAYC,kDAJG,uDAES,gCAUjBC,eACEC,aAAaC,UAAS,kBAAU,eAAgBC,mDACnC,CACdC,OAAQC,KAAKC,YACbC,KAAMF,KAAKE,KACXC,YAAaC,mBAAUC,MAAMC,SAASC,QACtCC,iBAAkBJ,mBAAUC,MAAMC,SAASG,qBAE1CC,+BACAC,mCAAmCX,KAAKJ,iEAMvB,WAChBgB,IAAMZ,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASQ,YACvDC,QAAUf,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASS,YACjEA,QAAQC,IAAMhB,KAAKC,YACnBc,QAAQE,UAAYjB,KAAKC,YAGrB,CAAC,QAAS,SAASiB,SAASlB,KAAKmB,WAAY,KACzCC,UAAW,wBAAYpB,KAAKE,MAC5BF,KAAKqB,aACArB,KAAKsB,wBACNF,SAAWpB,KAAKuB,kBAKnBrB,KAAKW,cAAcT,mBAAUC,MAAMC,SAASkB,OAAOC,MAAQL,YAIpEL,QAAQW,iBAAiB,SAAS/B,wCAEpB,kBAAU,oBAAqBG,yBAC/B,kBAAU,wBAAyBA,kBAAWE,KAAKC,mBAIxD0B,uBAEAC,2BAAa5B,MAAO6B,qBAIN,UAAnB7B,KAAKmB,UAAuB,KACxBW,YAAc,KACdC,WAAa,WACXC,SAAWC,SAASpB,cAAcT,mBAAUC,MAAMC,SAAS0B,UAE7DhC,KAAKkC,YACLF,SAASG,OAASnC,KAAKkC,WAI3BF,SAASI,OAGTJ,SAASN,iBAAiB,kBAAkB,UAEnCC,mBAELG,YAAcE,SAASF,YACvBC,WAAaC,SAASD,iBAEhBM,YADmBN,WAAaD,YACA,OAGjCQ,gBAAkB,CACnBC,MAAOR,WACPS,OAAQV,aAIRO,YACAL,SAASO,MAAQ3B,IAAI6B,YAErBT,SAASQ,OAAS5B,IAAI8B,gBAK9BV,SAASN,iBAAiB,WAAW,WAC3Bc,OAASxC,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASkC,QAC1DD,MAAQvC,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASiC,OAE1C,KAAjBC,OAAOf,OAAgC,KAAhBc,MAAMd,QAC7Be,OAAOf,MAAQK,YACfS,MAAMd,MAAQM,YAIdD,cAAgBa,SAASH,OAAOf,QAAUM,aAAeY,SAASJ,MAAMd,aACnEmB,aAAe5C,KAAKsC,gBAAgBC,WACpCM,cAAgB7C,KAAKsC,gBAAgBE,YACrCM,YAAY,mBAEZF,aAAeD,SAASJ,MAAMd,YAC9BoB,cAAgBF,SAASH,OAAOf,YAChCqB,YAAY,mBAGtB,GAAuB,UAAnB9C,KAAKmB,UAAuB,OAC7B4B,SAAW/C,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASyC,UAClEA,SAASX,OAGTW,SAASrB,iBAAiB,kBAAkB,UAEnCC,gCAIJA,mBAGLZ,QAAQwB,MAAQ3B,IAAI6B,YACpB1B,QAAQyB,OAAS5B,IAAI8B,yDAOV,mCACE1C,KAAKE,KAAM,mCACfE,mBAAUC,MAAMC,SAAS0C,aAAchD,KAAKE,oEAMxBP,gBAE3BsD,SAAWjD,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAAS4C,eAC5DC,KAAOnD,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAAS8C,WAC1DH,UAAYE,MAA2B,SAAnBnD,KAAKmB,YACzB8B,SAASvB,iBAAiB,UAAU,KAC5BuB,SAASI,UACTF,KAAKE,SAAU,MAIvBF,KAAKzB,iBAAiB,UAAU,KACxBuB,SAASI,UAAYF,KAAKE,UAC1BJ,SAASI,SAAU,aAMzBC,gBAAkBtD,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASiD,cACrED,iBACAA,gBAAgB5B,iBAAiB,UAAU,UAClCoB,YAAY,qBAKnBU,cAAgBxD,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASmD,YACnED,eACAA,cAAc9B,iBAAiB,UAAU,UAChCoB,YAAY,mBAKnBY,SAAW1D,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASiC,OAC9DmB,UACAA,SAAShC,iBAAiB,SAAS,KAE/BgC,SAASjC,MAA2B,KAAnBiC,SAASjC,MAAe,EAAIkC,OAAOD,SAASjC,YACxDmC,0BAKPC,UAAY7D,KAAKE,KAAKW,cAAcT,mBAAUC,MAAMC,SAASkC,QAC/DqB,WACAA,UAAUnC,iBAAiB,SAAS,KAEhCmC,UAAUpC,MAA4B,KAApBoC,UAAUpC,MAAe,EAAIkC,OAAOE,UAAUpC,YAC3DmC,gBAAe,4CA/LN5D,KAAMN"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/helpers.min.js b/lib/editor/tiny/plugins/media/amd/build/helpers.min.js index 0ccabbd0934..f78f94dd490 100644 --- a/lib/editor/tiny/plugins/media/amd/build/helpers.min.js +++ b/lib/editor/tiny/plugins/media/amd/build/helpers.min.js @@ -1,10 +1,10 @@ -define("tiny_media/helpers",["exports","core/templates","./selectors"],(function(_exports,_templates,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("tiny_media/helpers",["exports","core/templates","./selectors","./embed/embedhelpers","core/config"],(function(_exports,_templates,_selectors,_embedhelpers,_config){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Tiny media plugin helpers for image and embed. * * @module tiny_media/helpers * @copyright 2024 Stevani Andolo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.stopMediaLoading=_exports.startMediaLoading=_exports.showElements=_exports.setPropertiesFromData=_exports.isValidUrl=_exports.hideElements=_exports.footer=_exports.body=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors);_exports.body=async(templateContext,root)=>_templates.default.renderForPromise(templateContext.bodyTemplate,{...templateContext}).then((_ref=>{let{html:html,js:js}=_ref;_templates.default.replaceNodeContents(root.querySelector(_selectors.default[templateContext.selector].elements.bodyTemplate),html,js)})).catch((error=>{window.console.log(error)}));_exports.footer=async(templateContext,root)=>_templates.default.renderForPromise(templateContext.footerTemplate,{...templateContext}).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.replaceNodeContents(root.querySelector(_selectors.default[templateContext.selector].elements.footerTemplate),html,js)})).catch((error=>{window.console.log(error)}));_exports.setPropertiesFromData=(instance,data)=>{for(const property in data)"function"!=typeof data[property]&&(instance[property]=data[property]);return instance};_exports.isValidUrl=urlString=>!!new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3})|localhost)(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*").test(urlString);const hideElements=(elements,root)=>{if(elements instanceof Array)elements.forEach((elementSelector=>{const element=root.querySelector(elementSelector);element&&element.classList.add("d-none")}));else{const element=root.querySelector(elements);element&&element.classList.add("d-none")}};_exports.hideElements=hideElements;const showElements=(elements,root)=>{if(elements instanceof Array)elements.forEach((elementSelector=>{const element=root.querySelector(elementSelector);element&&element.classList.remove("d-none")}));else{const element=root.querySelector(elements);element&&element.classList.remove("d-none")}};_exports.showElements=showElements;_exports.startMediaLoading=(root,selector)=>{showElements(_selectors.default[selector].elements.loaderIcon,root);const elementsToHide=[_selectors.default[selector].elements.insertMedia,_selectors.default[selector].elements.urlWarning,_selectors.default[selector].elements.modalFooter];hideElements(elementsToHide,root)};_exports.stopMediaLoading=(root,selector)=>{hideElements(_selectors.default[selector].elements.loaderIcon,root);const elementsToShow=[_selectors.default[selector].elements.insertMedia,_selectors.default[selector].elements.modalFooter];showElements(elementsToShow,root)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.stopMediaLoading=_exports.startMediaLoading=_exports.sourceTypeChecked=_exports.showElements=_exports.setPropertiesFromData=_exports.setFilenameLabel=_exports.isValidUrl=_exports.isPercentageValue=_exports.isExternalUrl=_exports.hideElements=_exports.getFileName=_exports.getFileMimeTypeFromUrl=_exports.footer=_exports.createUrlParams=_exports.convertStringUrlToObject=_exports.body=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors),_config=_interopRequireDefault(_config);_exports.body=async(templateContext,root)=>_templates.default.renderForPromise(templateContext.bodyTemplate,{...templateContext}).then((_ref=>{let{html:html,js:js}=_ref;_templates.default.replaceNodeContents(root.querySelector(_selectors.default[templateContext.selector].elements.bodyTemplate),html,js)})).catch((error=>{window.console.log(error)}));_exports.footer=async(templateContext,root)=>_templates.default.renderForPromise(templateContext.footerTemplate,{...templateContext}).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.replaceNodeContents(root.querySelector(_selectors.default[templateContext.selector].elements.footerTemplate),html,js)})).catch((error=>{window.console.log(error)}));_exports.setPropertiesFromData=(instance,data)=>{for(const property in data)"function"!=typeof data[property]&&(instance[property]=data[property]);return instance};_exports.isValidUrl=urlString=>!!new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3})|localhost)(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*").test(urlString);const hideElements=(elements,root)=>{if(elements instanceof Array)elements.forEach((elementSelector=>{const element=root.querySelector(elementSelector);element&&element.classList.add("d-none")}));else{const element=root.querySelector(elements);element&&element.classList.add("d-none")}};_exports.hideElements=hideElements;const showElements=(elements,root)=>{if(elements instanceof Array)elements.forEach((elementSelector=>{const element=root.querySelector(elementSelector);element&&element.classList.remove("d-none")}));else{const element=root.querySelector(elements);element&&element.classList.remove("d-none")}};_exports.showElements=showElements;_exports.startMediaLoading=(root,selector)=>{showElements(_selectors.default[selector].elements.loaderIcon,root);const elementsToHide=[_selectors.default[selector].elements.insertMedia,_selectors.default[selector].elements.urlWarning,_selectors.default[selector].elements.modalFooter];hideElements(elementsToHide,root)};_exports.stopMediaLoading=(root,selector)=>{hideElements(_selectors.default[selector].elements.loaderIcon,root);const elementsToShow=[_selectors.default[selector].elements.insertMedia,_selectors.default[selector].elements.modalFooter];showElements(elementsToShow,root)};_exports.getFileMimeTypeFromUrl=async url=>{if((0,_embedhelpers.isUrlFromKnownMediaSites)(url))return"link";let fetchedMimeType=null;const mimeTypes=_selectors.default.MEDIA_MIME_TYPES;for(const property in mimeTypes){const uri=url.split("/");let fileExtension=uri[uri.length-1].split(".");if(fileExtension=fileExtension[fileExtension.length-1],(fileExtension.includes("/")||fileExtension.includes("?"))&&fileExtension.includes(property))fetchedMimeType=mimeTypes[property];else if(fileExtension===property){fetchedMimeType=mimeTypes[property];break}}return fetchedMimeType};_exports.convertStringUrlToObject=url=>Object.fromEntries(new URLSearchParams(url));_exports.createUrlParams=params=>Object.entries(params).map((_ref3=>{let[key,value]=_ref3;return"".concat(encodeURIComponent(key),"=").concat(encodeURIComponent(value))})).join("&");const isExternalUrl=url=>!1===new RegExp("".concat(_config.default.wwwroot)).test(url);_exports.isExternalUrl=isExternalUrl;const setFilenameLabel=props=>{const urlLabelEle=props.root.querySelector(props.fileNameSelector);urlLabelEle&&(urlLabelEle.innerHTML=props.label,urlLabelEle.setAttribute("title",props.label))};_exports.setFilenameLabel=setFilenameLabel;_exports.sourceTypeChecked=props=>{if(isExternalUrl(props.source))props.label=decodeURI(props.source);else{const filename=props.source.split("/").pop().split("?")[0];props.label=decodeURI(filename)}setFilenameLabel(props)};_exports.getFileName=root=>{const fileLabel=root.querySelector(_selectors.default.EMBED.elements.fileNameLabel).textContent;if(fileLabel.includes("/")){const split=fileLabel.split("/");return split[split.length-1].split(".")[0]}return fileLabel.split(".")[0]};_exports.isPercentageValue=value=>value.match(/\d+%/)})); //# sourceMappingURL=helpers.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/helpers.min.js.map b/lib/editor/tiny/plugins/media/amd/build/helpers.min.js.map index 2907aa22e6c..d31cb154bf4 100644 --- a/lib/editor/tiny/plugins/media/amd/build/helpers.min.js.map +++ b/lib/editor/tiny/plugins/media/amd/build/helpers.min.js.map @@ -1 +1 @@ -{"version":3,"file":"helpers.min.js","sources":["../src/helpers.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 helpers for image and embed.\n *\n * @module tiny_media/helpers\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Selectors from './selectors';\n\n/**\n * Renders and inserts the body template for inserting an media into the modal.\n *\n * @param {object} templateContext - The context for rendering the template.\n * @param {HTMLElement} root - The root element where the template will be inserted.\n * @returns {Promise}\n */\nexport const body = async(templateContext, root) => {\n return Templates.renderForPromise(templateContext.bodyTemplate, {...templateContext})\n .then(({html, js}) => {\n Templates.replaceNodeContents(root.querySelector(Selectors[templateContext.selector].elements.bodyTemplate), html, js);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n};\n\n/**\n * Renders and inserts the footer template for inserting an media into the modal.\n *\n * @param {object} templateContext - The context for rendering the template.\n * @param {HTMLElement} root - The root element where the template will be inserted.\n * @returns {Promise}\n */\nexport const footer = async(templateContext, root) => {\n return Templates.renderForPromise(templateContext.footerTemplate, {...templateContext})\n .then(({html, js}) => {\n Templates.replaceNodeContents(root.querySelector(Selectors[templateContext.selector].elements.footerTemplate), html, js);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n};\n\n/**\n * Set extra properties on an instance using incoming data.\n *\n * @param {object} instance\n * @param {object} data\n * @return {object} Modified instance\n */\nexport const setPropertiesFromData = (instance, data) => {\n for (const property in data) {\n if (typeof data[property] !== 'function') {\n instance[property] = data[property];\n }\n }\n return instance;\n};\n\n/**\n * Check if given string is a valid URL.\n *\n * @param {String} urlString URL the link will point to.\n * @returns {boolean} True is valid, otherwise false.\n */\nexport const isValidUrl = urlString => {\n const urlPattern = new RegExp('^(https?:\\\\/\\\\/)?' + // Protocol.\n '((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|' + // Domain name.\n '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})|localhost)' + // OR ip (v4) address, localhost.\n '(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*'); // Port and path.\n return !!urlPattern.test(urlString);\n};\n\n/**\n * Hide the element(s).\n *\n * @param {string|string[]} elements - The CSS selector for the elements to toggle.\n * @param {object} root - The CSS selector for the elements to toggle.\n */\nexport const hideElements = (elements, root) => {\n if (elements instanceof Array) {\n elements.forEach((elementSelector) => {\n const element = root.querySelector(elementSelector);\n if (element) {\n element.classList.add('d-none');\n }\n });\n } else {\n const element = root.querySelector(elements);\n if (element) {\n element.classList.add('d-none');\n }\n }\n};\n\n/**\n * Show the element(s).\n *\n * @param {string|string[]} elements - The CSS selector for the elements to toggle.\n * @param {object} root - The CSS selector for the elements to toggle.\n */\nexport const showElements = (elements, root) => {\n if (elements instanceof Array) {\n elements.forEach((elementSelector) => {\n const element = root.querySelector(elementSelector);\n if (element) {\n element.classList.remove('d-none');\n }\n });\n } else {\n const element = root.querySelector(elements);\n if (element) {\n element.classList.remove('d-none');\n }\n }\n};\n\n/**\n * Displays the upload loader and disables UI elements while loading a file.\n *\n * @param {html} root Modal element\n * @param {string} selector String of type IMAGE/EMBED\n */\nexport const startMediaLoading = (root, selector) => {\n showElements(Selectors[selector].elements.loaderIcon, root);\n const elementsToHide = [\n Selectors[selector].elements.insertMedia,\n Selectors[selector].elements.urlWarning,\n Selectors[selector].elements.modalFooter,\n ];\n hideElements(elementsToHide, root);\n};\n\n/**\n * Hide the upload loader and enable UI elements when loaded.\n *\n * @param {html} root Modal element\n * @param {string} selector String of type IMAGE/EMBED\n */\nexport const stopMediaLoading = (root, selector) => {\n hideElements(Selectors[selector].elements.loaderIcon, root);\n const elementsToShow = [\n Selectors[selector].elements.insertMedia,\n Selectors[selector].elements.modalFooter,\n ];\n showElements(elementsToShow, root);\n};\n"],"names":["async","templateContext","root","Templates","renderForPromise","bodyTemplate","then","_ref","html","js","replaceNodeContents","querySelector","Selectors","selector","elements","catch","error","window","console","log","footerTemplate","_ref2","instance","data","property","urlString","RegExp","test","hideElements","Array","forEach","elementSelector","element","classList","add","showElements","remove","loaderIcon","elementsToHide","insertMedia","urlWarning","modalFooter","elementsToShow"],"mappings":";;;;;;;gWAiCoBA,MAAMC,gBAAiBC,OAChCC,mBAAUC,iBAAiBH,gBAAgBI,aAAc,IAAIJ,kBACnEK,MAAKC,WAACC,KAACA,KAADC,GAAOA,4BACAC,oBAAoBR,KAAKS,cAAcC,mBAAUX,gBAAgBY,UAAUC,SAAST,cAAeG,KAAMC,OAGtHM,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,0BAWLhB,MAAMC,gBAAiBC,OAClCC,mBAAUC,iBAAiBH,gBAAgBmB,eAAgB,IAAInB,kBACrEK,MAAKe,YAACb,KAACA,KAADC,GAAOA,6BACAC,oBAAoBR,KAAKS,cAAcC,mBAAUX,gBAAgBY,UAAUC,SAASM,gBAAiBZ,KAAMC,OAGxHM,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,yCAWU,CAACM,SAAUC,YACvC,MAAMC,YAAYD,KACW,mBAAnBA,KAAKC,YACZF,SAASE,UAAYD,KAAKC,kBAG3BF,8BASeG,aACH,IAAIC,OAAO,yIAIVC,KAAKF,iBAShBG,aAAe,CAACd,SAAUZ,WAC/BY,oBAAoBe,MACpBf,SAASgB,SAASC,wBACRC,QAAU9B,KAAKS,cAAcoB,iBAC/BC,SACAA,QAAQC,UAAUC,IAAI,iBAG3B,OACGF,QAAU9B,KAAKS,cAAcG,UAC/BkB,SACAA,QAAQC,UAAUC,IAAI,qDAWrBC,aAAe,CAACrB,SAAUZ,WAC/BY,oBAAoBe,MACpBf,SAASgB,SAASC,wBACRC,QAAU9B,KAAKS,cAAcoB,iBAC/BC,SACAA,QAAQC,UAAUG,OAAO,iBAG9B,OACGJ,QAAU9B,KAAKS,cAAcG,UAC/BkB,SACAA,QAAQC,UAAUG,OAAO,0EAWJ,CAAClC,KAAMW,YACpCsB,aAAavB,mBAAUC,UAAUC,SAASuB,WAAYnC,YAChDoC,eAAiB,CACnB1B,mBAAUC,UAAUC,SAASyB,YAC7B3B,mBAAUC,UAAUC,SAAS0B,WAC7B5B,mBAAUC,UAAUC,SAAS2B,aAEjCb,aAAaU,eAAgBpC,iCASD,CAACA,KAAMW,YACnCe,aAAahB,mBAAUC,UAAUC,SAASuB,WAAYnC,YAChDwC,eAAiB,CACnB9B,mBAAUC,UAAUC,SAASyB,YAC7B3B,mBAAUC,UAAUC,SAAS2B,aAEjCN,aAAaO,eAAgBxC"} \ No newline at end of file +{"version":3,"file":"helpers.min.js","sources":["../src/helpers.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 helpers for image and embed.\n *\n * @module tiny_media/helpers\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Selectors from './selectors';\nimport {isUrlFromKnownMediaSites} from './embed/embedhelpers';\nimport Config from 'core/config';\n\n/**\n * Renders and inserts the body template for inserting an media into the modal.\n *\n * @param {object} templateContext - The context for rendering the template.\n * @param {HTMLElement} root - The root element where the template will be inserted.\n * @returns {Promise}\n */\nexport const body = async(templateContext, root) => {\n return Templates.renderForPromise(templateContext.bodyTemplate, {...templateContext})\n .then(({html, js}) => {\n Templates.replaceNodeContents(root.querySelector(Selectors[templateContext.selector].elements.bodyTemplate), html, js);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n};\n\n/**\n * Renders and inserts the footer template for inserting an media into the modal.\n *\n * @param {object} templateContext - The context for rendering the template.\n * @param {HTMLElement} root - The root element where the template will be inserted.\n * @returns {Promise}\n */\nexport const footer = async(templateContext, root) => {\n return Templates.renderForPromise(templateContext.footerTemplate, {...templateContext})\n .then(({html, js}) => {\n Templates.replaceNodeContents(root.querySelector(Selectors[templateContext.selector].elements.footerTemplate), html, js);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n};\n\n/**\n * Set extra properties on an instance using incoming data.\n *\n * @param {object} instance\n * @param {object} data\n * @return {object} Modified instance\n */\nexport const setPropertiesFromData = (instance, data) => {\n for (const property in data) {\n if (typeof data[property] !== 'function') {\n instance[property] = data[property];\n }\n }\n return instance;\n};\n\n/**\n * Check if given string is a valid URL.\n *\n * @param {String} urlString URL the link will point to.\n * @returns {boolean} True is valid, otherwise false.\n */\nexport const isValidUrl = urlString => {\n const urlPattern = new RegExp('^(https?:\\\\/\\\\/)?' + // Protocol.\n '((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|' + // Domain name.\n '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})|localhost)' + // OR ip (v4) address, localhost.\n '(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*'); // Port and path.\n return !!urlPattern.test(urlString);\n};\n\n/**\n * Hide the element(s).\n *\n * @param {string|string[]} elements - The CSS selector for the elements to toggle.\n * @param {object} root - The CSS selector for the elements to toggle.\n */\nexport const hideElements = (elements, root) => {\n if (elements instanceof Array) {\n elements.forEach((elementSelector) => {\n const element = root.querySelector(elementSelector);\n if (element) {\n element.classList.add('d-none');\n }\n });\n } else {\n const element = root.querySelector(elements);\n if (element) {\n element.classList.add('d-none');\n }\n }\n};\n\n/**\n * Show the element(s).\n *\n * @param {string|string[]} elements - The CSS selector for the elements to toggle.\n * @param {object} root - The CSS selector for the elements to toggle.\n */\nexport const showElements = (elements, root) => {\n if (elements instanceof Array) {\n elements.forEach((elementSelector) => {\n const element = root.querySelector(elementSelector);\n if (element) {\n element.classList.remove('d-none');\n }\n });\n } else {\n const element = root.querySelector(elements);\n if (element) {\n element.classList.remove('d-none');\n }\n }\n};\n\n/**\n * Displays the upload loader and disables UI elements while loading a file.\n *\n * @param {html} root Modal element\n * @param {string} selector String of type IMAGE/EMBED\n */\nexport const startMediaLoading = (root, selector) => {\n showElements(Selectors[selector].elements.loaderIcon, root);\n const elementsToHide = [\n Selectors[selector].elements.insertMedia,\n Selectors[selector].elements.urlWarning,\n Selectors[selector].elements.modalFooter,\n ];\n hideElements(elementsToHide, root);\n};\n\n/**\n * Hide the upload loader and enable UI elements when loaded.\n *\n * @param {html} root Modal element\n * @param {string} selector String of type IMAGE/EMBED\n */\nexport const stopMediaLoading = (root, selector) => {\n hideElements(Selectors[selector].elements.loaderIcon, root);\n const elementsToShow = [\n Selectors[selector].elements.insertMedia,\n Selectors[selector].elements.modalFooter,\n ];\n showElements(elementsToShow, root);\n};\n\n/**\n * Return file mime type from the url.\n *\n * @param {string} url\n * @returns {string}\n */\nexport const getFileMimeTypeFromUrl = async(url) => {\n if (isUrlFromKnownMediaSites(url)) {\n return 'link';\n }\n\n let fetchedMimeType = null;\n const mimeTypes = Selectors.MEDIA_MIME_TYPES;\n for (const property in mimeTypes) {\n const uri = url.split('/');\n const fileName = uri[uri.length - 1];\n let fileExtension = fileName.split('.');\n fileExtension = fileExtension[fileExtension.length - 1];\n if ((fileExtension.includes('/') || fileExtension.includes('?')) && fileExtension.includes(property)) {\n fetchedMimeType = mimeTypes[property];\n } else if (fileExtension === property) {\n fetchedMimeType = mimeTypes[property];\n break;\n }\n }\n return fetchedMimeType;\n};\n\n/**\n * Convert string url to object.\n *\n * @param {string} url\n * @returns {object}\n */\nexport const convertStringUrlToObject = (url) => {\n return Object.fromEntries(\n new URLSearchParams(url)\n );\n};\n\n/**\n * Create url params based on the object.\n *\n * @param {object} params\n * @returns {string}\n */\nexport const createUrlParams = (params) => {\n return Object.entries(params)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&');\n};\n\n/**\n * Return true or false if the url is external.\n *\n * @param {string} url\n * @returns\n */\nexport const isExternalUrl = (url) => {\n const regex = new RegExp(`${Config.wwwroot}`);\n\n // True if the URL is from external, otherwise false.\n return regex.test(url) === false;\n};\n\n/**\n * Set the string for the URL label element.\n *\n * @param {object} props - The label text to set.\n */\nexport const setFilenameLabel = (props) => {\n const urlLabelEle = props.root.querySelector(props.fileNameSelector);\n if (urlLabelEle) {\n urlLabelEle.innerHTML = props.label;\n urlLabelEle.setAttribute(\"title\", props.label);\n }\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 * @param {object} props\n */\nexport const sourceTypeChecked = (props) => {\n if (!isExternalUrl(props.source)) {\n // Split the URL by '/' to get an array of segments.\n const segments = props.source.split('/');\n // Get the last segment, which should be the filename.\n const filename = segments.pop().split('?')[0];\n // Show the file name.\n props.label = decodeURI(filename);\n } else {\n props.label = decodeURI(props.source);\n }\n setFilenameLabel(props);\n};\n\n/**\n * Get filename from the name label.\n *\n * @param {element} root\n * @returns {string}\n */\nexport const getFileName = (root) => {\n const fileLabel = root.querySelector(Selectors.EMBED.elements.fileNameLabel).textContent;\n if (fileLabel.includes('/')) {\n const split = fileLabel.split('/');\n let fileName = split[split.length - 1];\n return fileName.split('.')[0];\n } else {\n return fileLabel.split('.')[0];\n }\n};\n\n/**\n * Return true or false if % is found.\n *\n * @param {string} value\n * @returns {boolean}\n */\nexport const isPercentageValue = (value) => {\n return value.match(/\\d+%/);\n};\n"],"names":["async","templateContext","root","Templates","renderForPromise","bodyTemplate","then","_ref","html","js","replaceNodeContents","querySelector","Selectors","selector","elements","catch","error","window","console","log","footerTemplate","_ref2","instance","data","property","urlString","RegExp","test","hideElements","Array","forEach","elementSelector","element","classList","add","showElements","remove","loaderIcon","elementsToHide","insertMedia","urlWarning","modalFooter","elementsToShow","url","fetchedMimeType","mimeTypes","MEDIA_MIME_TYPES","uri","split","fileExtension","length","includes","Object","fromEntries","URLSearchParams","params","entries","map","_ref3","key","value","encodeURIComponent","join","isExternalUrl","Config","wwwroot","setFilenameLabel","props","urlLabelEle","fileNameSelector","innerHTML","label","setAttribute","source","decodeURI","filename","pop","fileLabel","EMBED","fileNameLabel","textContent","match"],"mappings":";;;;;;;+lBAmCoBA,MAAMC,gBAAiBC,OAChCC,mBAAUC,iBAAiBH,gBAAgBI,aAAc,IAAIJ,kBACnEK,MAAKC,WAACC,KAACA,KAADC,GAAOA,4BACAC,oBAAoBR,KAAKS,cAAcC,mBAAUX,gBAAgBY,UAAUC,SAAST,cAAeG,KAAMC,OAGtHM,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,0BAWLhB,MAAMC,gBAAiBC,OAClCC,mBAAUC,iBAAiBH,gBAAgBmB,eAAgB,IAAInB,kBACrEK,MAAKe,YAACb,KAACA,KAADC,GAAOA,6BACAC,oBAAoBR,KAAKS,cAAcC,mBAAUX,gBAAgBY,UAAUC,SAASM,gBAAiBZ,KAAMC,OAGxHM,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,yCAWU,CAACM,SAAUC,YACvC,MAAMC,YAAYD,KACW,mBAAnBA,KAAKC,YACZF,SAASE,UAAYD,KAAKC,kBAG3BF,8BASeG,aACH,IAAIC,OAAO,yIAIVC,KAAKF,iBAShBG,aAAe,CAACd,SAAUZ,WAC/BY,oBAAoBe,MACpBf,SAASgB,SAASC,wBACRC,QAAU9B,KAAKS,cAAcoB,iBAC/BC,SACAA,QAAQC,UAAUC,IAAI,iBAG3B,OACGF,QAAU9B,KAAKS,cAAcG,UAC/BkB,SACAA,QAAQC,UAAUC,IAAI,qDAWrBC,aAAe,CAACrB,SAAUZ,WAC/BY,oBAAoBe,MACpBf,SAASgB,SAASC,wBACRC,QAAU9B,KAAKS,cAAcoB,iBAC/BC,SACAA,QAAQC,UAAUG,OAAO,iBAG9B,OACGJ,QAAU9B,KAAKS,cAAcG,UAC/BkB,SACAA,QAAQC,UAAUG,OAAO,0EAWJ,CAAClC,KAAMW,YACpCsB,aAAavB,mBAAUC,UAAUC,SAASuB,WAAYnC,YAChDoC,eAAiB,CACnB1B,mBAAUC,UAAUC,SAASyB,YAC7B3B,mBAAUC,UAAUC,SAAS0B,WAC7B5B,mBAAUC,UAAUC,SAAS2B,aAEjCb,aAAaU,eAAgBpC,iCASD,CAACA,KAAMW,YACnCe,aAAahB,mBAAUC,UAAUC,SAASuB,WAAYnC,YAChDwC,eAAiB,CACnB9B,mBAAUC,UAAUC,SAASyB,YAC7B3B,mBAAUC,UAAUC,SAAS2B,aAEjCN,aAAaO,eAAgBxC,uCASKF,MAAAA,UAC9B,0CAAyB2C,WAClB,WAGPC,gBAAkB,WAChBC,UAAYjC,mBAAUkC,qBACvB,MAAMtB,YAAYqB,UAAW,OACxBE,IAAMJ,IAAIK,MAAM,SAElBC,cADaF,IAAIA,IAAIG,OAAS,GACLF,MAAM,QACnCC,cAAgBA,cAAcA,cAAcC,OAAS,IAChDD,cAAcE,SAAS,MAAQF,cAAcE,SAAS,OAASF,cAAcE,SAAS3B,UACvFoB,gBAAkBC,UAAUrB,eACzB,GAAIyB,gBAAkBzB,SAAU,CACnCoB,gBAAkBC,UAAUrB,wBAI7BoB,mDAS8BD,KAC9BS,OAAOC,YACV,IAAIC,gBAAgBX,+BAUIY,QACrBH,OAAOI,QAAQD,QACjBE,KAAIC,YAAEC,IAAKC,6BAAcC,mBAAmBF,iBAAQE,mBAAmBD,WACvEE,KAAK,WASDC,cAAiBpB,MAIC,IAHb,IAAIjB,iBAAUsC,gBAAOC,UAGtBtC,KAAKgB,gDAQTuB,iBAAoBC,cACvBC,YAAcD,MAAMjE,KAAKS,cAAcwD,MAAME,kBAC/CD,cACAA,YAAYE,UAAYH,MAAMI,MAC9BH,YAAYI,aAAa,QAASL,MAAMI,+EAYdJ,WACzBJ,cAAcI,MAAMM,QAQrBN,MAAMI,MAAQG,UAAUP,MAAMM,YARA,OAIxBE,SAFWR,MAAMM,OAAOzB,MAAM,KAEV4B,MAAM5B,MAAM,KAAK,GAE3CmB,MAAMI,MAAQG,UAAUC,UAI5BT,iBAAiBC,6BASOjE,aAClB2E,UAAY3E,KAAKS,cAAcC,mBAAUkE,MAAMhE,SAASiE,eAAeC,eACzEH,UAAU1B,SAAS,KAAM,OACnBH,MAAQ6B,UAAU7B,MAAM,YACfA,MAAMA,MAAME,OAAS,GACpBF,MAAM,KAAK,UAEpB6B,UAAU7B,MAAM,KAAK,+BAUFY,OACvBA,MAAMqB,MAAM"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/mediabase.min.js b/lib/editor/tiny/plugins/media/amd/build/mediabase.min.js new file mode 100644 index 00000000000..f845a6266d4 --- /dev/null +++ b/lib/editor/tiny/plugins/media/amd/build/mediabase.min.js @@ -0,0 +1,3 @@ +define("tiny_media/mediabase",["exports","./helpers","./selectors"],(function(_exports,_helpers,_selectors){var 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.MediaBase=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};_exports.MediaBase=class{constructor(){var _this=this;_defineProperty(this,"sizeChecked",(async option=>{const widthInput=this.root.querySelector(_selectors.default[this.selectorType].elements.width),heightInput=this.root.querySelector(_selectors.default[this.selectorType].elements.height);if("original"===option)this.sizeOriginalChecked(),widthInput.value=this.mediaDimensions.width,heightInput.value=this.mediaDimensions.height;else if("custom"===option&&(this.sizeCustomChecked(),widthInput.value=this.currentWidth,heightInput.value=this.currentHeight,"IMAGE"===this.selectorType&&this.currentWidth===this.mediaDimensions.width&&this.currentHeight===this.mediaDimensions.height)){this.root.querySelector(_selectors.default[this.selectorType].elements.constrain).checked=!0}this.autoAdjustSize()})),_defineProperty(this,"autoAdjustSize",(function(){let forceHeight=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(!_this.mediaDimensions)return;const widthField=_this.root.querySelector(_selectors.default[_this.selectorType].elements.width),heightField=_this.root.querySelector(_selectors.default[_this.selectorType].elements.height),normalizeFieldData=fieldData=>(fieldData.isPercentageValue=(0,_helpers.isPercentageValue)(fieldData.field.value),fieldData.isPercentageValue?(fieldData.percentValue=parseInt(fieldData.field.value,10),fieldData.pixelSize=_this.mediaDimensions[fieldData.type]/100*fieldData.percentValue):(fieldData.pixelSize=parseInt(fieldData.field.value,10),fieldData.percentValue=fieldData.pixelSize/_this.mediaDimensions[fieldData.type]*100),fieldData),getKeyField=()=>{const currentValue=forceHeight?{field:heightField,type:"height"}:{field:widthField,type:"width"};return""===currentValue.field.value&&(currentValue.field.value=_this.mediaDimensions[currentValue.type]),normalizeFieldData(currentValue)},getRelativeField=()=>normalizeFieldData(forceHeight?{field:widthField,type:"width"}:{field:heightField,type:"height"}),constrainField=_this.root.querySelector(_selectors.default[_this.selectorType].elements.constrain);if(constrainField&&constrainField.checked||"video"===_this.mediaType){const keyField=getKeyField(),relativeField=getRelativeField();keyField.isPercentageValue?(relativeField.field.value=keyField.field.value,relativeField.percentValue=keyField.percentValue):(relativeField.pixelSize=Math.round(keyField.pixelSize/_this.mediaDimensions[keyField.type]*_this.mediaDimensions[relativeField.type]),relativeField.field.value=relativeField.pixelSize)}"IMAGE"===_this.selectorType&&(_this.currentWidth=Number(widthField.value)!==_this.mediaDimensions.width?widthField.value:_this.currentWidth,_this.currentHeight=Number(heightField.value)!==_this.mediaDimensions.height?heightField.value:_this.currentHeight)}))}sizeOriginalChecked(){this.root.querySelector(_selectors.default[this.selectorType].elements.sizeOriginal).checked=!0,this.root.querySelector(_selectors.default[this.selectorType].elements.sizeCustom).checked=!1,(0,_helpers.hideElements)(_selectors.default[this.selectorType].elements.properties,this.root)}sizeCustomChecked(){this.root.querySelector(_selectors.default[this.selectorType].elements.sizeOriginal).checked=!1,this.root.querySelector(_selectors.default[this.selectorType].elements.sizeCustom).checked=!0,(0,_helpers.showElements)(_selectors.default[this.selectorType].elements.properties,this.root)}}})); + +//# sourceMappingURL=mediabase.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/mediabase.min.js.map b/lib/editor/tiny/plugins/media/amd/build/mediabase.min.js.map new file mode 100644 index 00000000000..94b5297b35f --- /dev/null +++ b/lib/editor/tiny/plugins/media/amd/build/mediabase.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mediabase.min.js","sources":["../src/mediabase.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 class helpers for image and embed.\n *\n * @module tiny_media/mediabase\n * @copyright 2024 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {\n isPercentageValue,\n hideElements,\n showElements,\n} from './helpers';\nimport Selectors from './selectors';\n\nexport class MediaBase {\n\n /**\n * Handles the selection of media size options and updates the form inputs accordingly.\n *\n * @param {string} option - The selected media size option (\"original\" or \"custom\").\n */\n sizeChecked = async(option) => {\n const widthInput = this.root.querySelector(Selectors[this.selectorType].elements.width);\n const heightInput = this.root.querySelector(Selectors[this.selectorType].elements.height);\n if (option === \"original\") {\n this.sizeOriginalChecked();\n widthInput.value = this.mediaDimensions.width;\n heightInput.value = this.mediaDimensions.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 and selectorType = IMAGE,\n // then check the Keep proportion checkbox.\n if (\n this.selectorType === 'IMAGE' &&\n this.currentWidth === this.mediaDimensions.width &&\n this.currentHeight === this.mediaDimensions.height\n ) {\n const constrainField = this.root.querySelector(Selectors[this.selectorType].elements.constrain);\n constrainField.checked = true;\n }\n }\n this.autoAdjustSize();\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[this.selectorType].elements.sizeOriginal).checked = true;\n this.root.querySelector(Selectors[this.selectorType].elements.sizeCustom).checked = false;\n hideElements(Selectors[this.selectorType].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[this.selectorType].elements.sizeOriginal).checked = false;\n this.root.querySelector(Selectors[this.selectorType].elements.sizeCustom).checked = true;\n showElements(Selectors[this.selectorType].elements.properties, this.root);\n }\n\n /**\n * Auto adjust the media width/height.\n * It is put here so image.js and/or friends can extend this class and call this for media proportion.\n *\n * @param {boolean} forceHeight Whether set by height or not\n * @returns\n */\n autoAdjustSize = (forceHeight = false) => {\n // If we do not know the media size, do not do anything.\n if (!this.mediaDimensions) {\n return;\n }\n\n const widthField = this.root.querySelector(Selectors[this.selectorType].elements.width);\n const heightField = this.root.querySelector(Selectors[this.selectorType].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.mediaDimensions[fieldData.type] / 100 * fieldData.percentValue;\n } else {\n fieldData.pixelSize = parseInt(fieldData.field.value, 10);\n fieldData.percentValue = fieldData.pixelSize / this.mediaDimensions[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.mediaDimensions[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[this.selectorType].elements.constrain); // Only image.\n if ((constrainField && constrainField.checked) || this.mediaType === 'video') {\n const keyField = getKeyField();\n const relativeField = getRelativeField();\n // We are keeping the media 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.mediaDimensions[keyField.type] * this.mediaDimensions[relativeField.type]\n );\n relativeField.field.value = relativeField.pixelSize;\n }\n }\n\n if (this.selectorType === 'IMAGE') {\n // Store the custom width and height to reuse.\n this.currentWidth = Number(widthField.value) !== this.mediaDimensions.width ? widthField.value : this.currentWidth;\n this.currentHeight = Number(heightField.value) !== this.mediaDimensions.height ? heightField.value : this.currentHeight;\n }\n };\n}\n"],"names":["async","widthInput","this","root","querySelector","Selectors","selectorType","elements","width","heightInput","height","option","sizeOriginalChecked","value","mediaDimensions","sizeCustomChecked","currentWidth","currentHeight","constrain","checked","autoAdjustSize","forceHeight","_this","widthField","heightField","normalizeFieldData","fieldData","isPercentageValue","field","percentValue","parseInt","pixelSize","type","getKeyField","currentValue","getRelativeField","constrainField","mediaType","keyField","relativeField","Math","round","Number","sizeOriginal","sizeCustom","properties"],"mappings":"kgBAoCkBA,MAAAA,eACJC,WAAaC,KAAKC,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASC,OAC3EC,YAAcP,KAAKC,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASG,WACnE,aAAXC,YACKC,sBACLX,WAAWY,MAAQX,KAAKY,gBAAgBN,MACxCC,YAAYI,MAAQX,KAAKY,gBAAgBJ,YACtC,GAAe,WAAXC,cACFI,oBACLd,WAAWY,MAAQX,KAAKc,aACxBP,YAAYI,MAAQX,KAAKe,cAKC,UAAtBf,KAAKI,cACLJ,KAAKc,eAAiBd,KAAKY,gBAAgBN,OAC3CN,KAAKe,gBAAkBf,KAAKY,gBAAgBJ,QAC9C,CACyBR,KAAKC,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASW,WACtEC,SAAU,OAG5BC,2DA4BQ,eAACC,wEAETC,MAAKR,6BAIJS,WAAaD,MAAKnB,KAAKC,cAAcC,mBAAUiB,MAAKhB,cAAcC,SAASC,OAC3EgB,YAAcF,MAAKnB,KAAKC,cAAcC,mBAAUiB,MAAKhB,cAAcC,SAASG,QAE5Ee,mBAAsBC,YACxBA,UAAUC,mBAAoB,8BAAkBD,UAAUE,MAAMf,OAC5Da,UAAUC,mBACVD,UAAUG,aAAeC,SAASJ,UAAUE,MAAMf,MAAO,IACzDa,UAAUK,UAAYT,MAAKR,gBAAgBY,UAAUM,MAAQ,IAAMN,UAAUG,eAE7EH,UAAUK,UAAYD,SAASJ,UAAUE,MAAMf,MAAO,IACtDa,UAAUG,aAAeH,UAAUK,UAAYT,MAAKR,gBAAgBY,UAAUM,MAAQ,KAGnFN,WAGLO,YAAc,WAeVC,aAbEb,YACO,CACHO,MAAOJ,YACPQ,KAAM,UAGH,CACHJ,MAAOL,WACPS,KAAM,eAMe,KAA7BE,aAAaN,MAAMf,QACnBqB,aAAaN,MAAMf,MAAQS,MAAKR,gBAAgBoB,aAAaF,OAG1DP,mBAAmBS,eAGxBC,iBAAmB,IAEVV,mBADPJ,YAC0B,CACtBO,MAAOL,WACPS,KAAM,SAGgB,CACtBJ,MAAOJ,YACPQ,KAAM,WAMZI,eAAiBd,MAAKnB,KAAKC,cAAcC,mBAAUiB,MAAKhB,cAAcC,SAASW,cAChFkB,gBAAkBA,eAAejB,SAA+B,UAAnBG,MAAKe,UAAuB,OACpEC,SAAWL,cACXM,cAAgBJ,mBAGlBG,SAASX,mBAETY,cAAcX,MAAMf,MAAQyB,SAASV,MAAMf,MAC3C0B,cAAcV,aAAeS,SAAST,eAEtCU,cAAcR,UAAYS,KAAKC,MAC3BH,SAASP,UAAYT,MAAKR,gBAAgBwB,SAASN,MAAQV,MAAKR,gBAAgByB,cAAcP,OAElGO,cAAcX,MAAMf,MAAQ0B,cAAcR,WAIxB,UAAtBT,MAAKhB,eAELgB,MAAKN,aAAe0B,OAAOnB,WAAWV,SAAWS,MAAKR,gBAAgBN,MAAQe,WAAWV,MAAQS,MAAKN,aACtGM,MAAKL,cAAgByB,OAAOlB,YAAYX,SAAWS,MAAKR,gBAAgBJ,OAASc,YAAYX,MAAQS,MAAKL,kBAvGlHL,2BACST,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASoC,cAAcxB,SAAU,OACjFhB,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASqC,YAAYzB,SAAU,4BACvEd,mBAAUH,KAAKI,cAAcC,SAASsC,WAAY3C,KAAKC,MAMxEY,yBACSZ,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASoC,cAAcxB,SAAU,OACjFhB,KAAKC,cAAcC,mBAAUH,KAAKI,cAAcC,SAASqC,YAAYzB,SAAU,4BACvEd,mBAAUH,KAAKI,cAAcC,SAASsC,WAAY3C,KAAKC"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/selectors.min.js b/lib/editor/tiny/plugins/media/amd/build/selectors.min.js index 9d2a6d22018..4f86319b07c 100644 --- a/lib/editor/tiny/plugins/media/amd/build/selectors.min.js +++ b/lib/editor/tiny/plugins/media/amd/build/selectors.min.js @@ -1,3 +1,3 @@ -define("tiny_media/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_image_urlentrysubmit",imageBrowser:".openimagebrowser",addUrl:".tiny_image_addurl",deleteImage:".tiny_image_deleteicon"},elements:{form:"form.tiny_image_form",alignSettings:".tiny_image_button",alt:".tiny_image_altentry",altWarning:".tiny_image_altwarning",height:".tiny_image_heightentry",width:".tiny_image_widthentry",url:".tiny_image_urlentry",urlWarning:".tiny_image_urlwarning",size:".tiny_image_size",presentation:".tiny_image_presentation",constrain:".tiny_image_constrain",customStyle:".tiny_image_customstyle",preview:".tiny_image_preview",previewBox:".tiny_image_preview_box",loaderIcon:".tiny_image_loader",loaderIconContainer:".tiny_image_loader_container",insertImage:".tiny_image_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_image_dropzone_container",fileInput:"#tiny_image_fileinput",fileNameLabel:".tiny_image_filename",sizeOriginal:".tiny_image_sizeoriginal",sizeCustom:".tiny_image_sizecustom",properties:".tiny_image_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{mediaBrowser:".openmediabrowser",addUrl:".tiny_media_add_url"},elements:{source:".tiny_media_source",track:".tiny_media_track",posterSource:".tiny_media_poster_source",title:".tiny_media_title_entry",url:".tiny_media_url_entry",width:".tiny_media_width_entry",height:".tiny_media_height_entry",trackSource:".tiny_media_track_source",trackLabel:".tiny_media_track_label_entry",trackLang:".tiny_media_track_lang_entry",trackDefault:".tiny_media_track_default",mediaControl:".tiny_media_controls",mediaAutoplay:".tiny_media_autoplay",mediaMute:".tiny_media_mute",mediaLoop:".tiny_media_loop",bodyTemplate:".tiny_media_body_template",footerTemplate:".tiny_media_footer_template",dropzoneContainer:".tiny_media_dropzone_container",fromUrl:".tiny_media_from_url_entry",urlWarning:".tiny_media_url_warning",loaderIcon:".tiny_media_loader",loaderIconContainer:".tiny_media_loader_container",insertMedia:".tiny_media_insert_media",modalFooter:".modal-footer"},template:{body:{insertMediaBody:"tiny_media/embed/body/insert_media_body"},footer:{insertMediaFooter:"tiny_media/embed/footer/insert_media_footer"}}}},_exports.default})); +define("tiny_media/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_image_urlentrysubmit",imageBrowser:".openimagebrowser",addUrl:".tiny_image_addurl",deleteImage:".tiny_image_deleteicon"},elements:{form:"form.tiny_image_form",alignSettings:".tiny_image_button",alt:".tiny_image_altentry",altWarning:".tiny_image_altwarning",height:".tiny_image_heightentry",width:".tiny_image_widthentry",url:".tiny_image_urlentry",urlWarning:".tiny_image_urlwarning",size:".tiny_image_size",presentation:".tiny_image_presentation",constrain:".tiny_image_constrain",customStyle:".tiny_image_customstyle",preview:".tiny_image_preview",previewBox:".tiny_image_preview_box",loaderIcon:".tiny_image_loader",loaderIconContainer:".tiny_image_loader_container",insertImage:".tiny_image_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_image_dropzone_container",fileInput:"#tiny_image_fileinput",fileNameLabel:".tiny_image_filename",sizeOriginal:".tiny_image_sizeoriginal",sizeCustom:".tiny_image_sizecustom",properties:".tiny_image_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{mediaBrowser:".openmediabrowser",addUrl:".tiny_media_add_url"},elements:{source:".tiny_media_source",track:".tiny_media_track",posterSource:".tiny_media_poster_source",title:".tiny_media_title_entry",url:".tiny_media_url_entry",width:".tiny_media_width_entry",height:".tiny_media_height_entry",trackSource:".tiny_media_track_source",trackLabel:".tiny_media_track_label_entry",trackLang:".tiny_media_track_lang_entry",trackDefault:".tiny_media_track_default",mediaControl:".tiny_media_controls",mediaAutoplay:".tiny_media_autoplay",mediaMute:".tiny_media_mute",mediaLoop:".tiny_media_loop",bodyTemplate:".tiny_media_body_template",footerTemplate:".tiny_media_footer_template",dropzoneContainer:".tiny_media_dropzone_container",fromUrl:".tiny_media_from_url_entry",urlWarning:".tiny_media_url_warning",loaderIcon:".tiny_media_loader",loaderIconContainer:".tiny_media_loader_container",insertMedia:".tiny_media_insert_media",modalFooter:".modal-footer",fileNameLabel:".tiny_media_filename",preview:".tiny_media_preview",previewBox:".tiny_media_preview_box",previewClass:".tiny_media_preview_tag",videoTag:"#video-tag",audioTag:"#audio-tag",sizeOriginal:".tiny_media_sizeoriginal",sizeCustom:".tiny_media_sizecustom",properties:".tiny_media_properties"},template:{body:{insertMediaBody:"tiny_media/embed/body/insert_media_body",mediaDetailsBody:"tiny_media/embed/body/media_details_body"},footer:{insertMediaFooter:"tiny_media/embed/footer/insert_media_footer",mediaDetailsFooter:"tiny_media/embed/footer/media_details_footer"}},mediaSites:{youtube:"www.youtube.com"},mediaTypes:["audio","video","link"]},MEDIA_MIME_TYPES:{aac:"audio",avi:"video",midi:"audio",mid:"audio",mp3:"audio",mp4:"video",xmpeg3:"audio",mpeg3:"audio",xmpeg:"video",mpeg:"video",oga:"audio",ogg:"audio",ogv:"video",opus:"audio",ts:"video",wav:"audio",weba:"audio",webm:"video","3gp":"video","3g2":"video"}},_exports.default})); //# sourceMappingURL=selectors.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/build/selectors.min.js.map b/lib/editor/tiny/plugins/media/amd/build/selectors.min.js.map index 5296d300032..d9f63cacaae 100644 --- a/lib/editor/tiny/plugins/media/amd/build/selectors.min.js.map +++ b/lib/editor/tiny/plugins/media/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"file":"selectors.min.js","sources":["../src/selectors.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 helper function to build queryable data selectors.\n *\n * @module tiny_media/selectors\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n IMAGE: {\n actions: {\n submit: '.tiny_image_urlentrysubmit',\n imageBrowser: '.openimagebrowser',\n addUrl: '.tiny_image_addurl',\n deleteImage: '.tiny_image_deleteicon',\n },\n elements: {\n form: 'form.tiny_image_form',\n alignSettings: '.tiny_image_button',\n alt: '.tiny_image_altentry',\n altWarning: '.tiny_image_altwarning',\n height: '.tiny_image_heightentry',\n width: '.tiny_image_widthentry',\n url: '.tiny_image_urlentry',\n urlWarning: '.tiny_image_urlwarning',\n size: '.tiny_image_size',\n presentation: '.tiny_image_presentation',\n constrain: '.tiny_image_constrain',\n customStyle: '.tiny_image_customstyle',\n preview: '.tiny_image_preview',\n previewBox: '.tiny_image_preview_box',\n loaderIcon: '.tiny_image_loader',\n loaderIconContainer: '.tiny_image_loader_container',\n insertImage: '.tiny_image_insert_image',\n modalFooter: '.modal-footer',\n dropzoneContainer: '.tiny_image_dropzone_container',\n fileInput: '#tiny_image_fileinput',\n fileNameLabel: '.tiny_image_filename',\n sizeOriginal: '.tiny_image_sizeoriginal',\n sizeCustom: '.tiny_image_sizecustom',\n properties: '.tiny_image_properties',\n },\n styles: {\n responsive: 'img-fluid',\n },\n },\n EMBED: {\n actions: {\n mediaBrowser: '.openmediabrowser',\n addUrl: '.tiny_media_add_url',\n },\n elements: {\n source: '.tiny_media_source',\n track: '.tiny_media_track',\n posterSource: '.tiny_media_poster_source',\n title: '.tiny_media_title_entry',\n url: '.tiny_media_url_entry',\n width: '.tiny_media_width_entry',\n height: '.tiny_media_height_entry',\n trackSource: '.tiny_media_track_source',\n trackLabel: '.tiny_media_track_label_entry',\n trackLang: '.tiny_media_track_lang_entry',\n trackDefault: '.tiny_media_track_default',\n mediaControl: '.tiny_media_controls',\n mediaAutoplay: '.tiny_media_autoplay',\n mediaMute: '.tiny_media_mute',\n mediaLoop: '.tiny_media_loop',\n bodyTemplate: '.tiny_media_body_template',\n footerTemplate: '.tiny_media_footer_template',\n dropzoneContainer: '.tiny_media_dropzone_container',\n fromUrl: '.tiny_media_from_url_entry',\n urlWarning: '.tiny_media_url_warning',\n loaderIcon: '.tiny_media_loader',\n loaderIconContainer: '.tiny_media_loader_container',\n insertMedia: '.tiny_media_insert_media',\n modalFooter: '.modal-footer',\n },\n template: {\n body: {\n insertMediaBody: 'tiny_media/embed/body/insert_media_body',\n },\n footer: {\n insertMediaFooter: 'tiny_media/embed/footer/insert_media_footer',\n },\n },\n },\n};\n"],"names":["IMAGE","actions","submit","imageBrowser","addUrl","deleteImage","elements","form","alignSettings","alt","altWarning","height","width","url","urlWarning","size","presentation","constrain","customStyle","preview","previewBox","loaderIcon","loaderIconContainer","insertImage","modalFooter","dropzoneContainer","fileInput","fileNameLabel","sizeOriginal","sizeCustom","properties","styles","responsive","EMBED","mediaBrowser","source","track","posterSource","title","trackSource","trackLabel","trackLang","trackDefault","mediaControl","mediaAutoplay","mediaMute","mediaLoop","bodyTemplate","footerTemplate","fromUrl","insertMedia","template","body","insertMediaBody","footer","insertMediaFooter"],"mappings":"sKAuBe,CACXA,MAAO,CACHC,QAAS,CACLC,OAAQ,6BACRC,aAAc,oBACdC,OAAQ,qBACRC,YAAa,0BAEjBC,SAAU,CACNC,KAAM,uBACNC,cAAe,qBACfC,IAAK,uBACLC,WAAY,yBACZC,OAAQ,0BACRC,MAAO,yBACPC,IAAK,uBACLC,WAAY,yBACZC,KAAM,mBACNC,aAAc,2BACdC,UAAW,wBACXC,YAAa,0BACbC,QAAS,sBACTC,WAAY,0BACZC,WAAY,qBACZC,oBAAqB,+BACrBC,YAAa,2BACbC,YAAa,gBACbC,kBAAmB,iCACnBC,UAAW,wBACXC,cAAe,uBACfC,aAAc,2BACdC,WAAY,yBACZC,WAAY,0BAEhBC,OAAQ,CACJC,WAAY,cAGpBC,MAAO,CACHhC,QAAS,CACLiC,aAAc,oBACd9B,OAAQ,uBAEZE,SAAU,CACN6B,OAAQ,qBACRC,MAAO,oBACPC,aAAc,4BACdC,MAAO,0BACPzB,IAAK,wBACLD,MAAO,0BACPD,OAAQ,2BACR4B,YAAa,2BACbC,WAAY,gCACZC,UAAW,+BACXC,aAAc,4BACdC,aAAc,uBACdC,cAAe,uBACfC,UAAW,mBACXC,UAAW,mBACXC,aAAc,4BACdC,eAAgB,8BAChBvB,kBAAmB,iCACnBwB,QAAS,6BACTnC,WAAY,0BACZO,WAAY,qBACZC,oBAAqB,+BACrB4B,YAAa,2BACb1B,YAAa,iBAEjB2B,SAAU,CACNC,KAAM,CACFC,gBAAiB,2CAErBC,OAAQ,CACJC,kBAAmB"} \ No newline at end of file +{"version":3,"file":"selectors.min.js","sources":["../src/selectors.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 helper function to build queryable data selectors.\n *\n * @module tiny_media/selectors\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n IMAGE: {\n actions: {\n submit: '.tiny_image_urlentrysubmit',\n imageBrowser: '.openimagebrowser',\n addUrl: '.tiny_image_addurl',\n deleteImage: '.tiny_image_deleteicon',\n },\n elements: {\n form: 'form.tiny_image_form',\n alignSettings: '.tiny_image_button',\n alt: '.tiny_image_altentry',\n altWarning: '.tiny_image_altwarning',\n height: '.tiny_image_heightentry',\n width: '.tiny_image_widthentry',\n url: '.tiny_image_urlentry',\n urlWarning: '.tiny_image_urlwarning',\n size: '.tiny_image_size',\n presentation: '.tiny_image_presentation',\n constrain: '.tiny_image_constrain',\n customStyle: '.tiny_image_customstyle',\n preview: '.tiny_image_preview',\n previewBox: '.tiny_image_preview_box',\n loaderIcon: '.tiny_image_loader',\n loaderIconContainer: '.tiny_image_loader_container',\n insertImage: '.tiny_image_insert_image',\n modalFooter: '.modal-footer',\n dropzoneContainer: '.tiny_image_dropzone_container',\n fileInput: '#tiny_image_fileinput',\n fileNameLabel: '.tiny_image_filename',\n sizeOriginal: '.tiny_image_sizeoriginal',\n sizeCustom: '.tiny_image_sizecustom',\n properties: '.tiny_image_properties',\n },\n styles: {\n responsive: 'img-fluid',\n },\n },\n EMBED: {\n actions: {\n mediaBrowser: '.openmediabrowser',\n addUrl: '.tiny_media_add_url',\n },\n elements: {\n source: '.tiny_media_source',\n track: '.tiny_media_track',\n posterSource: '.tiny_media_poster_source',\n title: '.tiny_media_title_entry',\n url: '.tiny_media_url_entry',\n width: '.tiny_media_width_entry',\n height: '.tiny_media_height_entry',\n trackSource: '.tiny_media_track_source',\n trackLabel: '.tiny_media_track_label_entry',\n trackLang: '.tiny_media_track_lang_entry',\n trackDefault: '.tiny_media_track_default',\n mediaControl: '.tiny_media_controls',\n mediaAutoplay: '.tiny_media_autoplay',\n mediaMute: '.tiny_media_mute',\n mediaLoop: '.tiny_media_loop',\n bodyTemplate: '.tiny_media_body_template',\n footerTemplate: '.tiny_media_footer_template',\n dropzoneContainer: '.tiny_media_dropzone_container',\n fromUrl: '.tiny_media_from_url_entry',\n urlWarning: '.tiny_media_url_warning',\n loaderIcon: '.tiny_media_loader',\n loaderIconContainer: '.tiny_media_loader_container',\n insertMedia: '.tiny_media_insert_media',\n modalFooter: '.modal-footer',\n fileNameLabel: '.tiny_media_filename',\n preview: '.tiny_media_preview',\n previewBox: '.tiny_media_preview_box',\n previewClass: '.tiny_media_preview_tag',\n videoTag: '#video-tag',\n audioTag: '#audio-tag',\n sizeOriginal: '.tiny_media_sizeoriginal',\n sizeCustom: '.tiny_media_sizecustom',\n properties: '.tiny_media_properties',\n },\n template: {\n body: {\n insertMediaBody: 'tiny_media/embed/body/insert_media_body',\n mediaDetailsBody: 'tiny_media/embed/body/media_details_body',\n },\n footer: {\n insertMediaFooter: 'tiny_media/embed/footer/insert_media_footer',\n mediaDetailsFooter: 'tiny_media/embed/footer/media_details_footer',\n },\n },\n mediaSites: {\n youtube: 'www.youtube.com',\n },\n mediaTypes: ['audio', 'video', 'link'],\n },\n MEDIA_MIME_TYPES: {\n \"aac\": \"audio\", // Mime subtype = /aac.\n \"avi\": \"video\", // Mime subtype = /x-msvideo.\n \"midi\": \"audio\", // Mime subtype = /midi|x-midi.\n \"mid\": \"audio\", // Mime subtype = /midi|x-midi.\n \"mp3\": \"audio\", // Mime subtype = /mpeg.\n \"mp4\": \"video\", // Mime subtype = /mp4.\n \"xmpeg3\": \"audio\", // Mime subtype = /x-mpeg-3.\n \"mpeg3\": \"audio\", // Mime subtype = /mpeg3.\n \"xmpeg\": \"video\", // Mime subtype = /x-mpeg.\n \"mpeg\": \"video\", // Mime subtype = /mpeg.\n \"oga\": \"audio\", // Mime subtype = /ogg.\n \"ogg\": \"audio\", // Mime subtype = /ogg.\n \"ogv\": \"video\", // Mime subtype = /ogg.\n \"opus\": \"audio\", // Mime subtype = /opus.\n \"ts\": \"video\", // Mime subtype = /mp2t.\n \"wav\": \"audio\", // Mime subtype = /wav.\n \"weba\": \"audio\", // Mime subtype = /webm.\n \"webm\": \"video\", // Mime subtype = /webm.\n \"3gp\": \"video\", // Mime subtype = /3gpp.\n \"3g2\": \"video\", // Mime subtype = /3gpp2.\n },\n};\n"],"names":["IMAGE","actions","submit","imageBrowser","addUrl","deleteImage","elements","form","alignSettings","alt","altWarning","height","width","url","urlWarning","size","presentation","constrain","customStyle","preview","previewBox","loaderIcon","loaderIconContainer","insertImage","modalFooter","dropzoneContainer","fileInput","fileNameLabel","sizeOriginal","sizeCustom","properties","styles","responsive","EMBED","mediaBrowser","source","track","posterSource","title","trackSource","trackLabel","trackLang","trackDefault","mediaControl","mediaAutoplay","mediaMute","mediaLoop","bodyTemplate","footerTemplate","fromUrl","insertMedia","previewClass","videoTag","audioTag","template","body","insertMediaBody","mediaDetailsBody","footer","insertMediaFooter","mediaDetailsFooter","mediaSites","youtube","mediaTypes","MEDIA_MIME_TYPES"],"mappings":"sKAuBe,CACXA,MAAO,CACHC,QAAS,CACLC,OAAQ,6BACRC,aAAc,oBACdC,OAAQ,qBACRC,YAAa,0BAEjBC,SAAU,CACNC,KAAM,uBACNC,cAAe,qBACfC,IAAK,uBACLC,WAAY,yBACZC,OAAQ,0BACRC,MAAO,yBACPC,IAAK,uBACLC,WAAY,yBACZC,KAAM,mBACNC,aAAc,2BACdC,UAAW,wBACXC,YAAa,0BACbC,QAAS,sBACTC,WAAY,0BACZC,WAAY,qBACZC,oBAAqB,+BACrBC,YAAa,2BACbC,YAAa,gBACbC,kBAAmB,iCACnBC,UAAW,wBACXC,cAAe,uBACfC,aAAc,2BACdC,WAAY,yBACZC,WAAY,0BAEhBC,OAAQ,CACJC,WAAY,cAGpBC,MAAO,CACHhC,QAAS,CACLiC,aAAc,oBACd9B,OAAQ,uBAEZE,SAAU,CACN6B,OAAQ,qBACRC,MAAO,oBACPC,aAAc,4BACdC,MAAO,0BACPzB,IAAK,wBACLD,MAAO,0BACPD,OAAQ,2BACR4B,YAAa,2BACbC,WAAY,gCACZC,UAAW,+BACXC,aAAc,4BACdC,aAAc,uBACdC,cAAe,uBACfC,UAAW,mBACXC,UAAW,mBACXC,aAAc,4BACdC,eAAgB,8BAChBvB,kBAAmB,iCACnBwB,QAAS,6BACTnC,WAAY,0BACZO,WAAY,qBACZC,oBAAqB,+BACrB4B,YAAa,2BACb1B,YAAa,gBACbG,cAAe,uBACfR,QAAS,sBACTC,WAAY,0BACZ+B,aAAc,0BACdC,SAAU,aACVC,SAAU,aACVzB,aAAc,2BACdC,WAAY,yBACZC,WAAY,0BAEhBwB,SAAU,CACNC,KAAM,CACFC,gBAAiB,0CACjBC,iBAAkB,4CAEtBC,OAAQ,CACJC,kBAAmB,8CACnBC,mBAAoB,iDAG5BC,WAAY,CACRC,QAAS,mBAEbC,WAAY,CAAC,QAAS,QAAS,SAEnCC,iBAAkB,KACP,YACA,aACC,YACD,YACA,YACA,eACG,cACD,cACA,aACD,YACD,YACA,YACA,aACC,WACF,YACC,aACC,aACA,cACD,cACA"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/media/amd/src/embed.js b/lib/editor/tiny/plugins/media/amd/src/embed.js index e93c023117b..d060fc82e61 100644 --- a/lib/editor/tiny/plugins/media/amd/src/embed.js +++ b/lib/editor/tiny/plugins/media/amd/src/embed.js @@ -25,7 +25,11 @@ import EmbedModal from './embedmodal'; import {getEmbedPermissions} from './options'; import {getFilePicker} from 'editor_tiny/options'; import {EmbedHandler} from './embed/embedhandler'; -import {insertMediaTemplateContext} from './embed/embedhelpers'; +import { + insertMediaTemplateContext, + getSelectedMediaElement, +} from './embed/embedhelpers'; +import {EmbedInsert} from './embed/embedinsert'; export default class MediaEmbed { editor = null; @@ -46,11 +50,35 @@ export default class MediaEmbed { } async displayDialogue() { + const [mediaType, selectedMedia] = getSelectedMediaElement(this.editor); + this.mediaType = mediaType; + this.selectedMedia = selectedMedia; this.currentModal = await EmbedModal.create(); this.root = this.currentModal.getRoot()[0]; - const mediaHandler = new EmbedHandler(this); - mediaHandler.loadTemplatePromise(insertMediaTemplateContext(this)); - mediaHandler.registerEventListeners(this.currentModal); + if (this.selectedMedia) { + // Preview the selected media. + this.isUpdating = true; + this.loadSelectedMedia(); + } else { + const embedHandler = new EmbedHandler(this); + embedHandler.loadTemplatePromise(insertMediaTemplateContext(this)); + embedHandler.registerEventListeners(this.currentModal); + } } + + loadSelectedMedia = () => { + let mediaSource = null; + if (this.mediaType === 'link') { + mediaSource = this.selectedMedia.href; + } else { + mediaSource = this.selectedMedia.querySelector('source').src; + } + + // Load media preview. + const embedInsert = new EmbedInsert(this); + embedInsert.init(); + embedInsert.loadMediaPreview(mediaSource); + (new EmbedHandler(this)).registerEventListeners(this.currentModal); + }; } diff --git a/lib/editor/tiny/plugins/media/amd/src/embed/embedhandler.js b/lib/editor/tiny/plugins/media/amd/src/embed/embedhandler.js index 61d566bb938..d4ccfa902f4 100644 --- a/lib/editor/tiny/plugins/media/amd/src/embed/embedhandler.js +++ b/lib/editor/tiny/plugins/media/amd/src/embed/embedhandler.js @@ -37,9 +37,16 @@ import { setPropertiesFromData, hideElements, isValidUrl, + convertStringUrlToObject, + stopMediaLoading, } from '../helpers'; import * as ModalEvents from 'core/modal_events'; import {displayFilepicker} from 'editor_tiny/utils'; +import { + insertMediaTemplateContext, + getHelpStrings, + prepareMoodleLang, +} from "./embedhelpers"; export class EmbedHandler { @@ -68,6 +75,183 @@ export class EmbedHandler { }); }; + /** + * Loads the media preview dialogue. + * + * @param {object} embedPreview Object of embedPreview + * @param {object} templateContext Object of template context + */ + loadMediaDetails = async(embedPreview, templateContext) => { + Promise.all([body(templateContext, this.root), footer(templateContext, this.root)]) + .then(() => { + // Hide the body template when preparing the media preview. + hideElements(Selectors.EMBED.elements.bodyTemplate, this.root); + + if (this.mediaData) { // It came from mediaThumbnail and we should kill uploadThumbnailModal modal. + this.currentModal.uploadThumbnailModal.destroy(); + const currentModal = this.currentModal.insertMediaModal; + this.currentModal = currentModal.insertMediaModal; + } + embedPreview.init(); + return; + }) + .catch(error => { + if (!this.mediaData) { // It came from mediaThumbnail and we did not init startMediaLoading from there. + stopMediaLoading(this.root, 'EMBED'); + } + window.console.log(error); + }); + }; + + /** + * Reset the media insert modal form. + */ + resetUploadForm = () => { + this.mediaType = null; // Set to null to be set again. + this.loadTemplatePromise(insertMediaTemplateContext(this)); + }; + + /** + * Get selected media data. + * + * @returns {null|object} + */ + getMediumProperties = () => { + const boolAttr = (elem, attr) => { + // As explained in MDL-64175, some OS (like Ubuntu), are removing the value for these attributes. + // So in order to check if attr="true", we need to check if the attribute exists and if the value is empty or true. + return (elem.hasAttribute(attr) && (elem.getAttribute(attr) || elem.getAttribute(attr) === '')); + }; + + const medium = this.selectedMedia; + if (!medium) { + return null; + } + + const isLink = (this.mediaType === 'link'); + if (isLink) { + const urlParams = convertStringUrlToObject(medium.href); + const mediaData = { + type: this.mediaType, + title: medium.textContent.trim(), + }; + + for (const param in urlParams) { + let prop = param; + if (param === 'mute') { + prop = 'muted'; + } + const isTrue = (urlParams[param] === 'true') || (urlParams[param] === '1'); + mediaData[prop] = isTrue; + } + return mediaData; + } else { + const tracks = { + subtitles: [], + captions: [], + descriptions: [], + chapters: [], + metadata: [] + }; + const sources = []; + + medium.querySelectorAll('track').forEach((track) => { + tracks[track.getAttribute('kind')].push({ + src: track.getAttribute('src'), + srclang: track.getAttribute('srclang'), + label: track.getAttribute('label'), + defaultTrack: boolAttr(track, 'default') + }); + }); + + medium.querySelectorAll('source').forEach((source) => { + sources.push(source.src); + }); + const title = medium.getAttribute('title'); + + return { + type: this.mediaType, + sources, + poster: medium.getAttribute('poster'), + title: title ? title.trim() : false, + width: medium.getAttribute('width'), + height: medium.getAttribute('height'), + autoplay: boolAttr(medium, 'autoplay'), + loop: boolAttr(medium, 'loop'), + muted: boolAttr(medium, 'muted'), + controls: boolAttr(medium, 'controls'), + tracks, + }; + } + }; + + /** + * Get selected media data. + * + * @returns {object} + */ + getCurrentEmbedData = () => { + const properties = this.getMediumProperties(); + if (!properties) { + return {}; + } + + const processedProperties = {}; + processedProperties.media = properties; + processedProperties.link = false; + + return processedProperties; + }; + + /** + * Get help strings for media subtitles and captions. + * + * @returns {null|object} + */ + getHelpStrings = async() => { + if (!this.helpStrings) { + this.helpStrings = await getHelpStrings(); + } + + return this.helpStrings; + }; + + /** + * Set template context for insert media dialogue. + * + * @param {object} data Object of media data + * @returns {object} + */ + getTemplateContext = async(data) => { + const languages = prepareMoodleLang(this.editor); + const helpIcons = Array.from(Object.entries(await this.getHelpStrings())).forEach(([key, text]) => { + data[`${key.toLowerCase()}helpicon`] = {text}; + }); + + return Object.assign({}, { + elementid: this.editor.getElement().id, + showFilePickerTrack: this.canShowFilePickerTrack, + langsInstalled: languages.installed, + langsAvailable: languages.available, + media: true, + isUpdating: this.isUpdating, + }, data, helpIcons); + }; + + /** + * Set and get media template context. + * + * @param {null|object} data Null or object of media data + * @returns {Promise} A promise that resolves template context. + */ + getMediaTemplateContext = async(data = null) => { + if (!data) { + data = Object.assign({}, this.getCurrentEmbedData()); + } + this.isUpdating = Object.keys(data).length !== 0; + return await this.getTemplateContext(data); + }; + /** * Handles changes in the media URL input field and loads a preview of the media if the URL has changed. */ @@ -75,10 +259,19 @@ export class EmbedHandler { hideElements(Selectors.EMBED.elements.urlWarning, this.root); const url = this.root.querySelector(Selectors.EMBED.elements.fromUrl).value; if (url && url !== this.currentUrl) { - window.console.log(url); + this.loadMediaPreview(url); } } + /** + * Load the media preview dialogue. + * + * @param {string} url String of media url + */ + loadMediaPreview = (url) => { + (new EmbedInsert(this)).loadMediaPreview(url); + }; + /** * Callback for file picker that previews the media or add the captions and subtitles. * @@ -86,7 +279,7 @@ export class EmbedHandler { */ trackFilePickerCallback(params) { if (params.url !== '') { - window.console.log(params.url); + this.loadMediaPreview(params.url); } } diff --git a/lib/editor/tiny/plugins/media/amd/src/embed/embedhelpers.js b/lib/editor/tiny/plugins/media/amd/src/embed/embedhelpers.js index 1b67e14605b..f276243912c 100644 --- a/lib/editor/tiny/plugins/media/amd/src/embed/embedhelpers.js +++ b/lib/editor/tiny/plugins/media/amd/src/embed/embedhelpers.js @@ -23,6 +23,18 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +import Selectors from '../selectors'; +import { + convertStringUrlToObject, + createUrlParams, +} from '../helpers'; +import {getStrings} from 'core/str'; +import {component} from "../common"; +import { + getCurrentLanguage, + getMoodleLang +} from 'editor_tiny/options'; + /** * Return template context for insert media. * @@ -36,3 +48,157 @@ export const insertMediaTemplateContext = (props) => { showFilePicker: props.canShowFilePicker, }; }; + +/** + * Return selected media type and element. + * + * @param {editor} editor + * @returns {Array} + */ +export const getSelectedMediaElement = (editor) => { + let mediaType = null; + let selectedMedia = null; + const mediaElm = editor.selection.getNode(); + + if (!mediaElm) { + mediaType = null; + selectedMedia = null; + } else if (mediaElm.nodeName.toLowerCase() === 'video' || mediaElm.nodeName.toLowerCase() === 'audio') { + mediaType = mediaElm.nodeName.toLowerCase(); + selectedMedia = mediaElm; + } else if (mediaElm.nodeName.toLowerCase() === 'a') { + mediaType = 'link'; + selectedMedia = mediaElm; + } else if (mediaElm.querySelector('video')) { + mediaType = 'video'; + selectedMedia = mediaElm.querySelector('video'); + } else if (mediaElm.querySelector('audio')) { + mediaType = 'audio'; + selectedMedia = mediaElm.querySelector('audio'); + } + + return [mediaType, selectedMedia]; +}; + +/** + * Format url when inserting media link to be previewed. + * + * @param {string} url + * @returns {string} + */ +export const formatMediaUrl = (url) => { + // Convert the string url into url param object. + const params = convertStringUrlToObject(url); + + // Format the url for youtube links. + if (url.includes(Selectors.EMBED.mediaSites.youtube)) { + let fetchedUrl = null; + let fetchedUrlValue = null; + for (const k in params) { + if (url.includes(k)) { + fetchedUrl = k; + fetchedUrlValue = params[k]; + delete params[k]; + break; + } + } + url = fetchedUrl.replace('watch?v', 'embed/'); + url = url + fetchedUrlValue + '?' + createUrlParams(params); + } + return url; +}; + +/** + * Check if the url is from a known media site. + * + * @param {string} url + * @returns {boolean} + */ +export const isUrlFromKnownMediaSites = (url) => { + let state = false; + const sites = Selectors.EMBED.mediaSites; + for (const site in sites) { + if (url.includes(sites[site])) { + state = true; + break; + } + } + return state; +}; + +/** + * Return template context for media details. + * + * @param {object} props + * @returns {object} + */ +export const mediaDetailsTemplateContext = async(props) => { + const context = { + bodyTemplate: Selectors.EMBED.template.body.mediaDetailsBody, + footerTemplate: Selectors.EMBED.template.footer.mediaDetailsFooter, + isVideo: (props.mediaType === 'video'), + isAudio: (props.mediaType === 'audio'), + isLink: (props.mediaType === 'link'), + isUpdating: props.isUpdating, + }; + + if (props.mediaData) { + return { + ...context, + ...props.mediaData, + }; + } else { + return { + ...context, + ...await props.mediaTemplateContext, + }; + } +}; + +/** + * Get help strings. + * + * @returns {object} + */ +export const getHelpStrings = async() => { + const [ + customsize, + ] = await getStrings([ + 'customsize_help', + ].map((key) => ({ + key, + component, + }))); + + return { + customsize, + }; +}; + +/** + * Get current moodle languages. + * + * @param {editor} editor + * @returns {object} + */ +export const prepareMoodleLang = (editor) => { + const moodleLangs = getMoodleLang(editor); + const currentLanguage = getCurrentLanguage(editor); + + const installed = Object.entries(moodleLangs.installed).map(([lang, code]) => ({ + lang, + code, + "default": lang === currentLanguage, + })); + + const available = Object.entries(moodleLangs.available).map(([lang, code]) => ({ + lang, + code, + "default": lang === currentLanguage, + })); + + return { + installed, + available, + }; +}; diff --git a/lib/editor/tiny/plugins/media/amd/src/embed/embedinsert.js b/lib/editor/tiny/plugins/media/amd/src/embed/embedinsert.js index 5f151863515..d2f99584b3a 100644 --- a/lib/editor/tiny/plugins/media/amd/src/embed/embedinsert.js +++ b/lib/editor/tiny/plugins/media/amd/src/embed/embedinsert.js @@ -24,17 +24,25 @@ */ import {prefetchStrings} from 'core/prefetch'; -import {getStrings} from 'core/str'; +import {getStrings, getString} from 'core/str'; import {component} from "../common"; import { setPropertiesFromData, startMediaLoading, stopMediaLoading, showElements, + getFileMimeTypeFromUrl, } from '../helpers'; import Selectors from "../selectors"; import Dropzone from 'core/dropzone'; import uploadFile from 'editor_tiny/uploader'; +import {EmbedHandler} from './embedhandler'; +import {alert} from 'core/notification'; +import { + formatMediaUrl, + mediaDetailsTemplateContext, +} from './embedhelpers'; +import {EmbedPreview} from './embedpreview'; prefetchStrings('tiny_media', [ 'insertmedia', @@ -79,6 +87,59 @@ export class EmbedInsert { } }; + /** + * Loads and displays a preview media based on the provided URL, and handles media loading events. + * + * @param {string} url - The URL of the media to load and display. + */ + loadMediaPreview = async(url) => { + startMediaLoading(this.root, 'EMBED'); + this.mediaSource = formatMediaUrl(url); + + // Get media mime type. + const mediaType = await getFileMimeTypeFromUrl(this.mediaSource); + + // Check if media type is acceptable. + if (!Selectors.EMBED.mediaTypes.includes(mediaType)) { + alert( + await getString('onlymediafiles', component), + await getString('onlymediafilesdesc', component) + ); + + stopMediaLoading(this.root, 'EMBED'); + (new EmbedHandler(this)).resetUploadForm(); + return; + } + + // Set mediaType to newly fetched mime type. + this.mediaType = mediaType; + + // Construct templateContext for embed preview. + const templateContext = await mediaDetailsTemplateContext({ + ...this, + // Get data from the selected media element. + mediaTemplateContext: (new EmbedHandler(this)).getMediaTemplateContext(), + }); + templateContext.selector = 'EMBED'; + + if (this.mediaType === 'video' && this.isUpdating) { + // Let's get selected video height & width and create props for them to be used in embedPreview. + const media = templateContext.media; + if (media.height !== '' && media.width !== '') { + this.mediaHeight = media.height; + this.mediaWidth = media.width; + } + } + + if (this.isUpdating) { + // Will be used to set the media title if it's in update state. + this.mediaTitle = templateContext.media.title; + } + + // Load the media details and preview of the selected media. + (new EmbedHandler(this)).loadMediaDetails(new EmbedPreview(this), templateContext); + }; + /** * Updates the content of the loader icon. * @@ -106,8 +167,7 @@ export class EmbedInsert { */ filePickerCallback = (params) => { if (params.url) { - window.console.log(params.url); - stopMediaLoading(this.root, 'EMBED'); + this.loadMediaPreview(params.url); } }; diff --git a/lib/editor/tiny/plugins/media/amd/src/embed/embedpreview.js b/lib/editor/tiny/plugins/media/amd/src/embed/embedpreview.js new file mode 100644 index 00000000000..80629e9c289 --- /dev/null +++ b/lib/editor/tiny/plugins/media/amd/src/embed/embedpreview.js @@ -0,0 +1,243 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny media plugin embed preview and details class. + * + * This handles the embed file/url preview before embedding them into tiny editor. + * + * @module tiny_media/embed/embedpreview + * @copyright 2024 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {alert} from 'core/notification'; +import Selectors from '../selectors'; +import {component} from '../common'; +import {getString} from 'core/str'; +import { + sourceTypeChecked, + getFileName, + setPropertiesFromData, + showElements, + stopMediaLoading, +} from '../helpers'; +import {EmbedHandler} from './embedhandler'; +import {MediaBase} from '../mediabase'; + +export class EmbedPreview extends MediaBase { + + selectorType = 'EMBED'; + + isEmbedPreviewDeleted = false; + + constructor(data) { + super(); + setPropertiesFromData(this, data); // Creates dynamic properties based on "data" param. + } + + /** + * Init the media details preview. + */ + init = async() => { + this.currentModal.setTitle(getString('mediadetails', component)); + sourceTypeChecked({ + source: this.mediaSource, + root: this.root, + urlSelector: Selectors.EMBED.elements.fromUrl, + fileNameSelector: Selectors.EMBED.elements.fileNameLabel, + }); + this.setMediaSourceAndPoster(); + this.registerMediaDetailsEventListeners(this.currentModal); + }; + + /** + * Sets media source and thumbnail for the video. + */ + setMediaSourceAndPoster = () => { + const box = this.root.querySelector(Selectors.EMBED.elements.previewBox); + const preview = this.root.querySelector(Selectors.EMBED.elements.preview); + preview.src = this.mediaSource; + preview.innerHTML = this.mediaSource; + + // Getting and setting up media title/name. + if (['video', 'audio'].includes(this.mediaType)) { + let fileName = getFileName(this.root); // Get original filename. + if (this.isUpdating) { + if (!this.isEmbedPreviewDeleted) { + fileName = this.mediaTitle; // Title from the selected media. + } + } + + // Set the media name/title. + this.root.querySelector(Selectors.EMBED.elements.title).value = fileName; + } + + // Handle error when loading the media. + preview.addEventListener('error', async() => { + alert( + await getString('medianotavailable', component), + await getString('medianotavailabledesc', component, this.mediaSource) + ); + + // Stop the loader and display back the body template when failed to load the media. + this.showBodyTemplate(); + + (new EmbedHandler(this)).resetUploadForm(); + return; + }); + + if (this.mediaType === 'video') { + let videoHeight = null; + let videoWidth = null; + const videoTag = document.querySelector(Selectors.EMBED.elements.videoTag); + + if (this.thumbnail) { + videoTag.poster = this.thumbnail; + } + + // Load the video html tag to load the media. + videoTag.load(); + + // Handle media metadata loading event. + videoTag.addEventListener('loadedmetadata', () => { + // Stop the loader and display back the body template when the media is loaded. + this.showBodyTemplate(); + + videoHeight = videoTag.videoHeight; + videoWidth = videoTag.videoWidth; + const widthProportion = (videoWidth - videoHeight); + const isLandscape = widthProportion > 0; + + // Store dimensions of the raw video. + this.mediaDimensions = { + width: videoWidth, + height: videoHeight, + }; + + // Set the media preview based on the media dimensions. + if (isLandscape) { + videoTag.width = box.offsetWidth; + } else { + videoTag.height = box.offsetHeight; + } + }); + + // Handle media canplay event. + videoTag.addEventListener('canplay', () => { + const height = this.root.querySelector(Selectors.EMBED.elements.height); + const width = this.root.querySelector(Selectors.EMBED.elements.width); + + if (height.value === '' && width.value === '') { + height.value = videoHeight; + width.value = videoWidth; + } + + // Size checking and adjustment. + if (videoHeight === parseInt(height.value) && videoWidth === parseInt(width.value)) { + this.currentWidth = this.mediaDimensions.width; + this.currentHeight = this.mediaDimensions.height; + this.sizeChecked('original'); + } else { + this.currentWidth = parseInt(width.value); + this.currentHeight = parseInt(height.value); + this.sizeChecked('custom'); + } + }); + } else if (this.mediaType === 'audio') { + const audioTag = this.root.querySelector(Selectors.EMBED.elements.audioTag); + audioTag.load(); + + // Handle media metadata loading event. + audioTag.addEventListener('loadedmetadata', () => { + // Stop the loader and display back the body template when the media is loaded. + this.showBodyTemplate(); + }); + } else { + // Stop the loader and display back the body template when the media is loaded. + this.showBodyTemplate(); + + // Set iframe width/height = box width/height. + preview.width = box.offsetWidth; + preview.height = box.offsetHeight; + } + }; + + /** + * Stop the loader and display back the body template. + */ + showBodyTemplate = () => { + stopMediaLoading(this.root, 'EMBED'); + showElements(Selectors.EMBED.elements.bodyTemplate, this.root); + }; + + /** + * Only registers event listeners for new loaded elements in embed preview modal. + */ + registerMediaDetailsEventListeners = async() => { + // Handle media autoplay and mute. + const autoPlay = this.root.querySelector(Selectors.EMBED.elements.mediaAutoplay); + const mute = this.root.querySelector(Selectors.EMBED.elements.mediaMute); + if (autoPlay && mute && this.mediaType === 'link') { + autoPlay.addEventListener('change', () => { + if (autoPlay.checked) { + mute.checked = true; + } + }); + + mute.addEventListener('change', () => { + if (autoPlay.checked && !mute.checked) { + autoPlay.checked = false; + } + }); + } + + // Handle the original size when selected. + const sizeOriginalEle = this.root.querySelector(Selectors.EMBED.elements.sizeOriginal); + if (sizeOriginalEle) { + sizeOriginalEle.addEventListener('change', () => { + this.sizeChecked('original'); + }); + } + + // Handle the custom size when selected. + const sizeCustomEle = this.root.querySelector(Selectors.EMBED.elements.sizeCustom); + if (sizeCustomEle) { + sizeCustomEle.addEventListener('change', () => { + this.sizeChecked('custom'); + }); + } + + // Handle the custom with size when inputted. + const widthEle = this.root.querySelector(Selectors.EMBED.elements.width); + if (widthEle) { + widthEle.addEventListener('input', () => { + // Avoid empty value. + widthEle.value = widthEle.value === "" ? 0 : Number(widthEle.value); + this.autoAdjustSize(); + }); + } + + // Handle the custom height size when inputted. + const heightEle = this.root.querySelector(Selectors.EMBED.elements.height); + if (heightEle) { + heightEle.addEventListener('input', () => { + // Avoid empty value. + heightEle.value = heightEle.value === "" ? 0 : Number(heightEle.value); + this.autoAdjustSize(true); + }); + } + }; +} diff --git a/lib/editor/tiny/plugins/media/amd/src/helpers.js b/lib/editor/tiny/plugins/media/amd/src/helpers.js index ab73b369391..5988a07089d 100644 --- a/lib/editor/tiny/plugins/media/amd/src/helpers.js +++ b/lib/editor/tiny/plugins/media/amd/src/helpers.js @@ -23,6 +23,8 @@ import Templates from 'core/templates'; import Selectors from './selectors'; +import {isUrlFromKnownMediaSites} from './embed/embedhelpers'; +import Config from 'core/config'; /** * Renders and inserts the body template for inserting an media into the modal. @@ -164,3 +166,130 @@ export const stopMediaLoading = (root, selector) => { ]; showElements(elementsToShow, root); }; + +/** + * Return file mime type from the url. + * + * @param {string} url + * @returns {string} + */ +export const getFileMimeTypeFromUrl = async(url) => { + if (isUrlFromKnownMediaSites(url)) { + return 'link'; + } + + let fetchedMimeType = null; + const mimeTypes = Selectors.MEDIA_MIME_TYPES; + for (const property in mimeTypes) { + const uri = url.split('/'); + const fileName = uri[uri.length - 1]; + let fileExtension = fileName.split('.'); + fileExtension = fileExtension[fileExtension.length - 1]; + if ((fileExtension.includes('/') || fileExtension.includes('?')) && fileExtension.includes(property)) { + fetchedMimeType = mimeTypes[property]; + } else if (fileExtension === property) { + fetchedMimeType = mimeTypes[property]; + break; + } + } + return fetchedMimeType; +}; + +/** + * Convert string url to object. + * + * @param {string} url + * @returns {object} + */ +export const convertStringUrlToObject = (url) => { + return Object.fromEntries( + new URLSearchParams(url) + ); +}; + +/** + * Create url params based on the object. + * + * @param {object} params + * @returns {string} + */ +export const createUrlParams = (params) => { + return Object.entries(params) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join('&'); +}; + +/** + * Return true or false if the url is external. + * + * @param {string} url + * @returns + */ +export const isExternalUrl = (url) => { + const regex = new RegExp(`${Config.wwwroot}`); + + // True if the URL is from external, otherwise false. + return regex.test(url) === false; +}; + +/** + * Set the string for the URL label element. + * + * @param {object} props - The label text to set. + */ +export const setFilenameLabel = (props) => { + const urlLabelEle = props.root.querySelector(props.fileNameSelector); + if (urlLabelEle) { + urlLabelEle.innerHTML = props.label; + urlLabelEle.setAttribute("title", props.label); + } +}; + +/** + * This function checks whether an image URL is local (within the same website's domain) or external (from an external source). + * Depending on the result, it dynamically updates the visibility and content of HTML elements in a user interface. + * If the image is local then we only show it's filename. + * If the image is external then it will show full URL and it can be updated. + * + * @param {object} props + */ +export const sourceTypeChecked = (props) => { + if (!isExternalUrl(props.source)) { + // Split the URL by '/' to get an array of segments. + const segments = props.source.split('/'); + // Get the last segment, which should be the filename. + const filename = segments.pop().split('?')[0]; + // Show the file name. + props.label = decodeURI(filename); + } else { + props.label = decodeURI(props.source); + } + setFilenameLabel(props); +}; + +/** + * Get filename from the name label. + * + * @param {element} root + * @returns {string} + */ +export const getFileName = (root) => { + const fileLabel = root.querySelector(Selectors.EMBED.elements.fileNameLabel).textContent; + if (fileLabel.includes('/')) { + const split = fileLabel.split('/'); + let fileName = split[split.length - 1]; + return fileName.split('.')[0]; + } else { + return fileLabel.split('.')[0]; + } +}; + +/** + * Return true or false if % is found. + * + * @param {string} value + * @returns {boolean} + */ +export const isPercentageValue = (value) => { + return value.match(/\d+%/); +}; diff --git a/lib/editor/tiny/plugins/media/amd/src/mediabase.js b/lib/editor/tiny/plugins/media/amd/src/mediabase.js new file mode 100644 index 00000000000..9b85dc5fec2 --- /dev/null +++ b/lib/editor/tiny/plugins/media/amd/src/mediabase.js @@ -0,0 +1,172 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny media plugin class helpers for image and embed. + * + * @module tiny_media/mediabase + * @copyright 2024 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +import { + isPercentageValue, + hideElements, + showElements, +} from './helpers'; +import Selectors from './selectors'; + +export class MediaBase { + + /** + * Handles the selection of media size options and updates the form inputs accordingly. + * + * @param {string} option - The selected media size option ("original" or "custom"). + */ + sizeChecked = async(option) => { + const widthInput = this.root.querySelector(Selectors[this.selectorType].elements.width); + const heightInput = this.root.querySelector(Selectors[this.selectorType].elements.height); + if (option === "original") { + this.sizeOriginalChecked(); + widthInput.value = this.mediaDimensions.width; + heightInput.value = this.mediaDimensions.height; + } else if (option === "custom") { + this.sizeCustomChecked(); + widthInput.value = this.currentWidth; + heightInput.value = this.currentHeight; + + // If the current size is equal to the original size and selectorType = IMAGE, + // then check the Keep proportion checkbox. + if ( + this.selectorType === 'IMAGE' && + this.currentWidth === this.mediaDimensions.width && + this.currentHeight === this.mediaDimensions.height + ) { + const constrainField = this.root.querySelector(Selectors[this.selectorType].elements.constrain); + constrainField.checked = true; + } + } + this.autoAdjustSize(); + }; + + /** + * Handles the selection of the "Original Size" option and updates the form elements accordingly. + */ + sizeOriginalChecked() { + this.root.querySelector(Selectors[this.selectorType].elements.sizeOriginal).checked = true; + this.root.querySelector(Selectors[this.selectorType].elements.sizeCustom).checked = false; + hideElements(Selectors[this.selectorType].elements.properties, this.root); + } + + /** + * Handles the selection of the "Custom Size" option and updates the form elements accordingly. + */ + sizeCustomChecked() { + this.root.querySelector(Selectors[this.selectorType].elements.sizeOriginal).checked = false; + this.root.querySelector(Selectors[this.selectorType].elements.sizeCustom).checked = true; + showElements(Selectors[this.selectorType].elements.properties, this.root); + } + + /** + * Auto adjust the media width/height. + * It is put here so image.js and/or friends can extend this class and call this for media proportion. + * + * @param {boolean} forceHeight Whether set by height or not + * @returns + */ + autoAdjustSize = (forceHeight = false) => { + // If we do not know the media size, do not do anything. + if (!this.mediaDimensions) { + return; + } + + const widthField = this.root.querySelector(Selectors[this.selectorType].elements.width); + const heightField = this.root.querySelector(Selectors[this.selectorType].elements.height); + + const normalizeFieldData = (fieldData) => { + fieldData.isPercentageValue = isPercentageValue(fieldData.field.value); + if (fieldData.isPercentageValue) { + fieldData.percentValue = parseInt(fieldData.field.value, 10); + fieldData.pixelSize = this.mediaDimensions[fieldData.type] / 100 * fieldData.percentValue; + } else { + fieldData.pixelSize = parseInt(fieldData.field.value, 10); + fieldData.percentValue = fieldData.pixelSize / this.mediaDimensions[fieldData.type] * 100; + } + + return fieldData; + }; + + const getKeyField = () => { + const getValue = () => { + if (forceHeight) { + return { + field: heightField, + type: 'height', + }; + } else { + return { + field: widthField, + type: 'width', + }; + } + }; + + const currentValue = getValue(); + if (currentValue.field.value === '') { + currentValue.field.value = this.mediaDimensions[currentValue.type]; + } + + return normalizeFieldData(currentValue); + }; + + const getRelativeField = () => { + if (forceHeight) { + return normalizeFieldData({ + field: widthField, + type: 'width', + }); + } else { + return normalizeFieldData({ + field: heightField, + type: 'height', + }); + } + }; + + // Now update with the new values. + const constrainField = this.root.querySelector(Selectors[this.selectorType].elements.constrain); // Only image. + if ((constrainField && constrainField.checked) || this.mediaType === 'video') { + const keyField = getKeyField(); + const relativeField = getRelativeField(); + // We are keeping the media in proportion. + // Calculate the size for the relative field. + if (keyField.isPercentageValue) { + // In proportion, so the percentages are the same. + relativeField.field.value = keyField.field.value; + relativeField.percentValue = keyField.percentValue; + } else { + relativeField.pixelSize = Math.round( + keyField.pixelSize / this.mediaDimensions[keyField.type] * this.mediaDimensions[relativeField.type] + ); + relativeField.field.value = relativeField.pixelSize; + } + } + + if (this.selectorType === 'IMAGE') { + // Store the custom width and height to reuse. + this.currentWidth = Number(widthField.value) !== this.mediaDimensions.width ? widthField.value : this.currentWidth; + this.currentHeight = Number(heightField.value) !== this.mediaDimensions.height ? heightField.value : this.currentHeight; + } + }; +} diff --git a/lib/editor/tiny/plugins/media/amd/src/selectors.js b/lib/editor/tiny/plugins/media/amd/src/selectors.js index 139f16ab626..604a09eb19a 100644 --- a/lib/editor/tiny/plugins/media/amd/src/selectors.js +++ b/lib/editor/tiny/plugins/media/amd/src/selectors.js @@ -89,14 +89,51 @@ export default { loaderIconContainer: '.tiny_media_loader_container', insertMedia: '.tiny_media_insert_media', modalFooter: '.modal-footer', + fileNameLabel: '.tiny_media_filename', + preview: '.tiny_media_preview', + previewBox: '.tiny_media_preview_box', + previewClass: '.tiny_media_preview_tag', + videoTag: '#video-tag', + audioTag: '#audio-tag', + sizeOriginal: '.tiny_media_sizeoriginal', + sizeCustom: '.tiny_media_sizecustom', + properties: '.tiny_media_properties', }, template: { body: { insertMediaBody: 'tiny_media/embed/body/insert_media_body', + mediaDetailsBody: 'tiny_media/embed/body/media_details_body', }, footer: { insertMediaFooter: 'tiny_media/embed/footer/insert_media_footer', + mediaDetailsFooter: 'tiny_media/embed/footer/media_details_footer', }, }, + mediaSites: { + youtube: 'www.youtube.com', + }, + mediaTypes: ['audio', 'video', 'link'], + }, + MEDIA_MIME_TYPES: { + "aac": "audio", // Mime subtype = /aac. + "avi": "video", // Mime subtype = /x-msvideo. + "midi": "audio", // Mime subtype = /midi|x-midi. + "mid": "audio", // Mime subtype = /midi|x-midi. + "mp3": "audio", // Mime subtype = /mpeg. + "mp4": "video", // Mime subtype = /mp4. + "xmpeg3": "audio", // Mime subtype = /x-mpeg-3. + "mpeg3": "audio", // Mime subtype = /mpeg3. + "xmpeg": "video", // Mime subtype = /x-mpeg. + "mpeg": "video", // Mime subtype = /mpeg. + "oga": "audio", // Mime subtype = /ogg. + "ogg": "audio", // Mime subtype = /ogg. + "ogv": "video", // Mime subtype = /ogg. + "opus": "audio", // Mime subtype = /opus. + "ts": "video", // Mime subtype = /mp2t. + "wav": "audio", // Mime subtype = /wav. + "weba": "audio", // Mime subtype = /webm. + "webm": "video", // Mime subtype = /webm. + "3gp": "video", // Mime subtype = /3gpp. + "3g2": "video", // Mime subtype = /3gpp2. }, }; diff --git a/lib/editor/tiny/plugins/media/lang/en/tiny_media.php b/lib/editor/tiny/plugins/media/lang/en/tiny_media.php index fb573d03a83..5ccdaa0b78e 100644 --- a/lib/editor/tiny/plugins/media/lang/en/tiny_media.php +++ b/lib/editor/tiny/plugins/media/lang/en/tiny_media.php @@ -30,7 +30,7 @@ $string['addmediafilesdrop'] = 'Drag and drop audio/video file to upload, or cli $string['addmetadatatrack'] = 'Add metadata track'; $string['addsubtitlestrack'] = 'Add subtitle track'; $string['addurl'] = 'Add'; -$string['autoplay'] = 'Play automatically'; +$string['autoplay'] = 'Autoplay'; $string['browseembedimagerepositories'] = 'Browse repositories'; $string['browserepositories'] = 'Browse repositories...'; $string['browserepositoriesimage'] = 'Browse repositories'; @@ -42,6 +42,7 @@ $string['chapters'] = 'Chapters'; $string['chapterssourcelabel'] = 'Chapter track URL'; $string['constrain'] = 'Keep proportion'; $string['controls'] = 'Show controls'; +$string['customsize_help'] = 'For best viewing experience, the video\'s width and height will adjust together, keeping the original aspect ratio.'; $string['default'] = 'Default'; $string['deleteimage'] = 'Delete image'; $string['deleteimagewarning'] = 'Are you sure you want to remove the image?'; @@ -71,13 +72,18 @@ $string['loop'] = 'Loop'; $string['managefiles'] = 'Manage files'; $string['media:use'] = 'Use TinyMCE insert media'; $string['mediabuttontitle'] = 'Multimedia'; +$string['mediadetails'] = 'Media details'; $string['mediamanagerbuttontitle'] = 'Media manager'; $string['mediamanagerproperties'] = 'Media manager'; +$string['medianotavailable'] = 'Media is not available'; +$string['medianotavailabledesc'] = 'Error loading the following media url:
{$a}'; $string['metadata_help'] = 'Metadata tracks, for use from a script, may be used only if the player supports metadata.'; $string['metadata'] = 'Metadata'; $string['metadatasourcelabel'] = 'Metadata track URL'; $string['missingfiles'] = 'Missing files'; $string['mute'] = 'Muted'; +$string['onlymediafiles'] = 'Accepts only media files'; +$string['onlymediafilesdesc'] = 'You can only upload media files like video or audio'; $string['pluginname'] = 'Insert media'; $string['presentation'] = 'This image is decorative only'; $string['presentationoraltrequired'] = 'An image must have a description, unless it is marked as decorative only.'; diff --git a/lib/editor/tiny/plugins/media/styles.css b/lib/editor/tiny/plugins/media/styles.css index 077c7a2f570..21ca51cdf65 100644 --- a/lib/editor/tiny/plugins/media/styles.css +++ b/lib/editor/tiny/plugins/media/styles.css @@ -48,7 +48,8 @@ iframe.mm_iframe { height: 200px; } -.tiny_image_form .tiny_image_preview_box { +.tiny_image_form .tiny_image_preview_box, +.tiny_media_form .tiny_media_preview_box { height: 300px; display: flex; justify-content: center; @@ -56,6 +57,10 @@ iframe.mm_iframe { overflow: hidden; } +.tiny_media_form .tiny_media_preview_box { + position: relative; +} + .tiny_image_form .tiny_image_deleteicon { position: absolute; top: 5px; @@ -75,7 +80,12 @@ iframe.mm_iframe { @media (max-width: 767px) { - .tiny_image_form .tiny_image_properties_col { + .tiny_image_form .tiny_image_properties_col, + .tiny_media_form .tiny_media_properties_col { padding: 0; } } + +.tiny_media_properties_col .size-container { + margin-left: 23px; +}