MDL-78428 tiny_media: Implemented a new embed preview

This commit is contained in:
Stevani Andolo
2025-03-18 13:23:03 +08:00
parent 75b6985900
commit cc3c4c08d0
26 changed files with 1086 additions and 25 deletions
+1 -1
View File
@@ -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
@@ -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 <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Media plugin Embed class for Moodle.\n *\n * @module tiny_media/embed\n * @copyright 2022 Huong Nguyen <[email protected]>\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"}
{"version":3,"file":"embed.min.js","sources":["../src/embed.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Media plugin Embed class for Moodle.\n *\n * @module tiny_media/embed\n * @copyright 2022 Huong Nguyen <[email protected]>\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"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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 <stevani@hotmail.com.au>
* @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
File diff suppressed because one or more lines are too long
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -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 <stevani@hotmail.com.au>
* @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
File diff suppressed because one or more lines are too long
@@ -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
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
File diff suppressed because one or more lines are too long
+32 -4
View File
@@ -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);
};
}
@@ -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<object>} 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);
}
}
@@ -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,
};
};
@@ -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);
}
};
@@ -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 <http://www.gnu.org/licenses/>.
/**
* 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 <[email protected]>
* @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);
});
}
};
}
@@ -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+%/);
};
@@ -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 <http://www.gnu.org/licenses/>.
/**
* Tiny media plugin class helpers for image and embed.
*
* @module tiny_media/mediabase
* @copyright 2024 Stevani Andolo <[email protected]>
* @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;
}
};
}
@@ -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.
},
};
@@ -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'] = '<b>Error loading the following media url:</b><br><i>{$a}</i>';
$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.';
+12 -2
View File
@@ -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;
}