MDL-78428 tiny_media: Implemented a new embed preview
This commit is contained in:
+1
-1
@@ -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
@@ -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,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
@@ -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.';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user