From 467e273ff509c71cb8a1411b314eaddc7509e4d7 Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Tue, 10 Feb 2026 19:08:35 +0200 Subject: [PATCH] plugin --- .../mediacms-moodle/tiny/mediacms/README.md | 188 +----------------- .../mediacms/amd/build/iframeembed.min.js | 2 +- .../mediacms/amd/build/iframeembed.min.js.map | 2 +- .../tiny/mediacms/amd/src/iframeembed.js | 139 +++---------- .../tiny/mediacms/lang/en/tiny_mediacms.php | 6 +- .../templates/iframe_embed_modal.mustache | 96 +++++---- 6 files changed, 84 insertions(+), 349 deletions(-) diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/README.md b/lms-plugins/mediacms-moodle/tiny/mediacms/README.md index 99ef5639..a5eb915d 100644 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/README.md +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/README.md @@ -2,186 +2,16 @@ A TinyMCE editor plugin for Moodle that provides media embedding capabilities with MediaCMS/LTI integration. -## Plugin Information +## Build Information -- **Component:** `tiny_mediacms` -- **Version:** See `version.php` -- **Requires:** Moodle 4.5+ (2024100100) -## Directory Structure +1. Get and extract Moodle 5.1 +2. cp -r lms-plugins/mediacms-moodle/tiny/mediacms/ moodle/public/lib/editor/tiny/plugins/ +3. nvm use 22 && cd moodle/public && npm install +4. npx grunt amd --root=lib/editor/tiny/plugins/mediacms +# i've noticed that this fails, so this should work: npx grunt amd -``` -mediacms/ -├── amd/ -│ ├── src/ # JavaScript source files (ES6 modules) -│ │ ├── plugin.js # Main plugin entry point -│ │ ├── commands.js # Editor commands -│ │ ├── configuration.js # Plugin configuration -│ │ ├── iframeembed.js # Iframe embedding logic -│ │ ├── iframemodal.js # Iframe modal UI -│ │ ├── autoconvert.js # URL auto-conversion -│ │ ├── embed.js # Media embedding -│ │ ├── embedmodal.js # Embed modal UI -│ │ ├── image.js # Image handling -│ │ ├── imagemodal.js # Image modal UI -│ │ ├── imageinsert.js # Image insertion -│ │ ├── imagedetails.js # Image details panel -│ │ ├── imagehelpers.js # Image utility functions -│ │ ├── manager.js # File manager -│ │ ├── options.js # Plugin options -│ │ ├── selectors.js # DOM selectors -│ │ ├── common.js # Shared utilities -│ │ └── usedfiles.js # Track used files -│ └── build/ # Compiled/minified files (generated) -├── classes/ # PHP classes -├── lang/ # Language strings -│ └── en/ -│ └── tiny_mediacms.php -├── templates/ # Mustache templates -├── styles.css # Plugin styles -├── settings.php # Admin settings -└── version.php # Plugin version -``` +5. To test the output: + cp lib/editor/tiny/plugins/mediacms/* ../../lms-plugins/mediacms-moodle/tiny/mediacms/ -r +6. Then copy to Moodle server and purge caches -## Building JavaScript (AMD Modules) - -When you modify JavaScript files in `amd/src/`, you must rebuild the minified files in `amd/build/`. - -### Prerequisites - -Make sure you have Node.js installed and have run `npm install` in the Moodle root directory: - -```bash -cd /path/to/moodle/public -npm install -``` - -### Build Commands - -#### Build all AMD modules (entire Moodle): - -```bash -cd /path/to/moodle/public -npx grunt amd -``` - -#### Build only this plugin's AMD modules: - -```bash -cd /path/to/moodle/public -npx grunt amd --root=lib/editor/tiny/plugins/mediacms -``` - -#### Watch for changes (auto-rebuild): - -```bash -cd /path/to/moodle/public -npx grunt watch --root=lib/editor/tiny/plugins/mediacms -``` - -#### Force build (ignore warnings): - -```bash -cd /path/to/moodle/public -npx grunt amd --force --root=lib/editor/tiny/plugins/mediacms -``` - -### Build Output - -After running grunt, the following files are generated in `amd/build/`: - -- `*.min.js` - Minified JavaScript files -- `*.min.js.map` - Source maps for debugging - -## Development Mode (Skip Building) - -For faster development, you can skip building by enabling developer mode in Moodle's `config.php`: - -```php -// Add these lines to config.php -$CFG->debugdeveloper = true; -$CFG->cachejs = false; -``` - -This tells Moodle to load the unminified source files directly from `amd/src/` instead of `amd/build/`. - -**Note:** Always build before committing or deploying to production! - -## Purging Caches - -After making changes, you may need to purge Moodle caches: - -### Via CLI (Docker): - -```bash -docker compose exec moodle php /var/www/html/public/admin/cli/purge_caches.php -``` - -### Via CLI (Local): - -```bash -php admin/cli/purge_caches.php -``` - -### Via Web: - -Visit: `http://your-moodle-site/admin/purgecaches.php` - -## What Needs Cache Purging? - -| File Type | Cache Purge Needed? | -|-----------|---------------------| -| `amd/src/*.js` | No (if `$CFG->cachejs = false`) | -| `amd/build/*.min.js` | Yes | -| `lang/en/*.php` | Yes | -| `templates/*.mustache` | Yes | -| `styles.css` | Yes | -| `classes/*.php` | Usually no | -| `settings.php` | Yes | - -## Troubleshooting - -### Changes not appearing? - -1. **JavaScript changes:** - - Rebuild AMD modules: `npx grunt amd --root=lib/editor/tiny/plugins/mediacms` - - Hard refresh browser: `Cmd+Shift+R` (Mac) / `Ctrl+Shift+R` (Windows/Linux) - - Check browser console for errors - -2. **Language strings:** - - Purge Moodle caches - -3. **Templates:** - - Purge Moodle caches - -4. **Styles:** - - Purge Moodle caches - - Hard refresh browser - -### Grunt errors? - -```bash -# Make sure dependencies are installed -cd /path/to/moodle/public -npm install - -# Try with force flag -npx grunt amd --force --root=lib/editor/tiny/plugins/mediacms -``` - -### ESLint errors? - -Fix linting issues or use: - -```bash -npx grunt amd --force --root=lib/editor/tiny/plugins/mediacms -``` - -## Related Documentation - -- [AUTOCONVERT.md](./AUTOCONVERT.md) - URL auto-conversion feature documentation -- [LTI_INTEGRATION.md](./LTI_INTEGRATION.md) - LTI integration documentation - -## License - -GNU GPL v3 or later - http://www.gnu.org/copyleft/gpl.html diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js index 203b0305..4d26cd4c 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js @@ -1,3 +1,3 @@ -define("tiny_mediacms/iframeembed",["exports","core/templates","core/str","core/modal_events","./common","./iframemodal","./selectors","./options"],(function(_exports,_templates,_str,ModalEvents,_common,_iframemodal,_selectors,_options){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _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.default=void 0,_templates=_interopRequireDefault(_templates),ModalEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalEvents),_iframemodal=_interopRequireDefault(_iframemodal),_selectors=_interopRequireDefault(_selectors);return _exports.default=class{constructor(editor){_defineProperty(this,"editor",null),_defineProperty(this,"currentModal",null),_defineProperty(this,"isUpdating",!1),_defineProperty(this,"selectedIframe",null),_defineProperty(this,"debounceTimer",null),_defineProperty(this,"iframeLibraryLoaded",!1),_defineProperty(this,"selectedLibraryVideo",null),_defineProperty(this,"iframeLibraryUrl","https://temp.web357.com/mediacms/deic-mediacms-embed-videos.html"),this.editor=editor}parseInput(input){if(!input||!input.trim())return null;const iframeMatch=(input=input.trim()).match(/]*src=["']([^"']+)["'][^>]*>/i);return iframeMatch?this.parseEmbedUrl(iframeMatch[1]):input.startsWith("http://")||input.startsWith("https://")?this.parseVideoUrl(input):null}parseVideoUrl(url){try{const urlObj=new URL(url),baseUrl="".concat(urlObj.protocol,"//").concat(urlObj.host);if("/view"===urlObj.pathname&&urlObj.searchParams.has("m"))return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!1};if("/embed"===urlObj.pathname&&urlObj.searchParams.has("m")){const tParam=urlObj.searchParams.get("t");return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!0,showTitle:"1"===urlObj.searchParams.get("showTitle"),linkTitle:"1"===urlObj.searchParams.get("linkTitle"),showRelated:"1"===urlObj.searchParams.get("showRelated"),showUserAvatar:"1"===urlObj.searchParams.get("showUserAvatar"),startAt:tParam?this.secondsToTimeString(parseInt(tParam)):null}}return{baseUrl:baseUrl,rawUrl:url,isGeneric:!0}}catch(e){return null}}parseEmbedUrl(url){return this.parseVideoUrl(url)}secondsToTimeString(seconds){const mins=Math.floor(seconds/60),secs=seconds%60;return"".concat(mins,":").concat(secs.toString().padStart(2,"0"))}timeStringToSeconds(timeStr){if(!timeStr||!timeStr.trim())return null;if((timeStr=timeStr.trim()).includes(":")){const parts=timeStr.split(":");return 60*(parseInt(parts[0])||0)+(parseInt(parts[1])||0)}const secs=parseInt(timeStr);return isNaN(secs)?null:secs}buildEmbedUrl(parsed,options){if(parsed.isGeneric)return parsed.rawUrl;const url=new URL("".concat(parsed.baseUrl,"/embed"));if(url.searchParams.set("m",parsed.videoId),url.searchParams.set("showTitle",options.showTitle?"1":"0"),url.searchParams.set("showRelated",options.showRelated?"1":"0"),url.searchParams.set("showUserAvatar",options.showUserAvatar?"1":"0"),url.searchParams.set("linkTitle",options.linkTitle?"1":"0"),options.startAtEnabled&&options.startAt){const seconds=this.timeStringToSeconds(options.startAt);null!==seconds&&seconds>0&&url.searchParams.set("t",seconds.toString())}return url.toString()}async getTemplateContext(){var _this=this;let data=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const editorData=(0,_options.getData)(this.editor),autoConvertOptions=(null==editorData?void 0:editorData.autoConvertOptions)||{},getDefault=function(key){let fallback=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return _this.isUpdating&&void 0!==data[key]?data[key]:void 0!==autoConvertOptions[key]?autoConvertOptions[key]:fallback};return{elementid:this.editor.getElement().id,isupdating:this.isUpdating,url:data.url||"",showTitle:getDefault("showTitle"),linkTitle:getDefault("linkTitle"),showRelated:getDefault("showRelated"),showUserAvatar:getDefault("showUserAvatar"),responsive:!1!==data.responsive,startAtEnabled:data.startAtEnabled||!1,startAt:data.startAt||"0:00",width:data.width||560,height:data.height||315,is16_9:!data.aspectRatio||"16:9"===data.aspectRatio,is4_3:"4:3"===data.aspectRatio,is1_1:"1:1"===data.aspectRatio,isCustom:"custom"===data.aspectRatio}}async displayDialogue(){this.selectedIframe=this.getSelectedIframe();const data=this.getCurrentIframeData();this.isUpdating=null!==data,this.iframeLibraryLoaded=!1,this.currentModal=await _iframemodal.default.create({title:(0,_str.getString)("iframemodaltitle",_common.component),templateContext:await this.getTemplateContext(data||{})}),await this.registerEventListeners(this.currentModal)}getSelectedIframe(){const node=this.editor.selection.getNode();if("iframe"===node.nodeName.toLowerCase())return node;const iframe=node.querySelector("iframe");if(iframe)return iframe;const wrapper=node.closest(".tiny-mediacms-iframe-wrapper")||node.closest(".tiny-iframe-responsive");return wrapper?wrapper.querySelector("iframe"):null}getCurrentIframeData(){var _parsed$showTitle,_parsed$linkTitle,_parsed$showRelated,_parsed$showUserAvata;if(!this.selectedIframe)return null;const src=this.selectedIframe.getAttribute("src"),parsed=this.parseInput(src),isResponsive=(this.selectedIframe.getAttribute("style")||"").includes("aspect-ratio");return{url:src,width:this.selectedIframe.getAttribute("width")||560,height:this.selectedIframe.getAttribute("height")||315,showTitle:null===(_parsed$showTitle=null==parsed?void 0:parsed.showTitle)||void 0===_parsed$showTitle||_parsed$showTitle,linkTitle:null===(_parsed$linkTitle=null==parsed?void 0:parsed.linkTitle)||void 0===_parsed$linkTitle||_parsed$linkTitle,showRelated:null===(_parsed$showRelated=null==parsed?void 0:parsed.showRelated)||void 0===_parsed$showRelated||_parsed$showRelated,showUserAvatar:null===(_parsed$showUserAvata=null==parsed?void 0:parsed.showUserAvatar)||void 0===_parsed$showUserAvata||_parsed$showUserAvata,responsive:isResponsive,startAtEnabled:null!==(null==parsed?void 0:parsed.startAt),startAt:(null==parsed?void 0:parsed.startAt)||"0:00"}}getFormValues(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form);return{url:form.querySelector(_selectors.default.IFRAME.elements.url).value.trim(),showTitle:form.querySelector(_selectors.default.IFRAME.elements.showTitle).checked,linkTitle:form.querySelector(_selectors.default.IFRAME.elements.linkTitle).checked,showRelated:form.querySelector(_selectors.default.IFRAME.elements.showRelated).checked,showUserAvatar:form.querySelector(_selectors.default.IFRAME.elements.showUserAvatar).checked,responsive:form.querySelector(_selectors.default.IFRAME.elements.responsive).checked,startAtEnabled:form.querySelector(_selectors.default.IFRAME.elements.startAtEnabled).checked,startAt:form.querySelector(_selectors.default.IFRAME.elements.startAt).value.trim(),aspectRatio:form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,width:parseInt(form.querySelector(_selectors.default.IFRAME.elements.width).value)||560,height:parseInt(form.querySelector(_selectors.default.IFRAME.elements.height).value)||315}}async generateIframeHtml(values){const parsed=this.parseInput(values.url);if(!parsed)return"";const embedUrl=this.buildEmbedUrl(parsed,values),aspectRatioCalcs={"16:9":"16 / 9","4:3":"4 / 3","1:1":"1 / 1",custom:"".concat(values.width," / ").concat(values.height)},context={src:embedUrl,width:values.width,height:values.height,responsive:values.responsive,aspectRatioCalc:aspectRatioCalcs[values.aspectRatio]||"16 / 9",aspectRatioValue:aspectRatioCalcs[values.aspectRatio]||"16 / 9"},{html:html}=await _templates.default.renderForPromise("tiny_mediacms/iframe_embed_output",context);return html}async updatePreview(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const values=this.getFormValues(root),previewContainer=root.querySelector(_selectors.default.IFRAME.elements.preview),urlWarning=root.querySelector(_selectors.default.IFRAME.elements.urlWarning);if(!values.url)return previewContainer.innerHTML='Enter a video URL to see preview',void urlWarning.classList.add("d-none");const parsed=this.parseInput(values.url);if(!parsed)return previewContainer.innerHTML='Invalid URL format',void urlWarning.classList.remove("d-none");urlWarning.classList.add("d-none");const embedUrl=this.buildEmbedUrl(parsed,values);if(updateUrlField&&!parsed.isGeneric){root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.url).value=embedUrl}const previewWidth=Math.min(values.width,400),scale=previewWidth/values.width,previewHeight=Math.round(values.height*scale);previewContainer.innerHTML='\n \n ')}handleInputChange(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout((()=>{this.updatePreview(root,updateUrlField)}),500)}handleAspectRatioChange(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,dimensions=_selectors.default.IFRAME.aspectRatios[aspectRatio];dimensions&&"custom"!==aspectRatio&&(form.querySelector(_selectors.default.IFRAME.elements.width).value=dimensions.width,form.querySelector(_selectors.default.IFRAME.elements.height).value=dimensions.height),this.updatePreview(root)}async handleDialogueSubmission(modal){const root=modal.getRoot()[0],values=this.getFormValues(root);if(!values.url)return;const html=await this.generateIframeHtml(values);if(html)if(this.isUpdating&&this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.outerHTML=html:this.selectedIframe.outerHTML=html,this.isUpdating=!1,this.editor.fire("Change")}else this.editor.insertContent(html)}async handleRemove(modal){const confirmMessage=await(0,_str.getString)("removeiframeconfirm",_common.component);if(window.confirm(confirmMessage)){if(this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.remove():this.selectedIframe.remove()}this.isUpdating=!1,modal.hide()}}async registerEventListeners(modal){await modal.getBody();const $root=modal.getRoot(),root=$root[0],form=root.querySelector(_selectors.default.IFRAME.elements.form);form.querySelector(_selectors.default.IFRAME.elements.url).addEventListener("input",(()=>this.handleInputChange(root))),[_selectors.default.IFRAME.elements.showTitle,_selectors.default.IFRAME.elements.linkTitle,_selectors.default.IFRAME.elements.showRelated,_selectors.default.IFRAME.elements.showUserAvatar,_selectors.default.IFRAME.elements.startAtEnabled].forEach((selector=>{form.querySelector(selector).addEventListener("change",(()=>this.handleInputChange(root,!0)))})),form.querySelector(_selectors.default.IFRAME.elements.responsive).addEventListener("change",(()=>this.handleInputChange(root,!1))),form.querySelector(_selectors.default.IFRAME.elements.startAt).addEventListener("input",(()=>this.handleInputChange(root,!0))),form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).addEventListener("change",(()=>this.handleAspectRatioChange(root))),form.querySelector(_selectors.default.IFRAME.elements.width).addEventListener("input",(()=>this.handleInputChange(root))),form.querySelector(_selectors.default.IFRAME.elements.height).addEventListener("input",(()=>this.handleInputChange(root))),$root.on(ModalEvents.save,(()=>this.handleDialogueSubmission(modal))),$root.on(ModalEvents.hidden,(()=>{this.currentModal.destroy()}));const removeBtn=root.querySelector(_selectors.default.IFRAME.actions.remove);removeBtn&&removeBtn.addEventListener("click",(()=>this.handleRemove(modal)));form.querySelector(_selectors.default.IFRAME.elements.url).value&&this.updatePreview(root);const iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn);if(iframeLibraryTabBtn){iframeLibraryTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToIframeLibraryTab(root),setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)})),iframeLibraryTabBtn.addEventListener("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)));const $iframeLibraryTabBtn=window.jQuery?window.jQuery(iframeLibraryTabBtn):null;$iframeLibraryTabBtn&&$iframeLibraryTabBtn.on("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)))}const urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn);urlTabBtn&&urlTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToUrlTab(root)})),this.registerIframeLibraryEventListeners(root)}switchToUrlTab(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabBtn&&(urlTabBtn.classList.add("active"),urlTabBtn.setAttribute("aria-selected","true")),iframeLibraryTabBtn&&(iframeLibraryTabBtn.classList.remove("active"),iframeLibraryTabBtn.setAttribute("aria-selected","false")),urlPane&&urlPane.classList.add("show","active"),iframeLibraryPane&&iframeLibraryPane.classList.remove("show","active")}switchToIframeLibraryTab(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabBtn&&(urlTabBtn.classList.remove("active"),urlTabBtn.setAttribute("aria-selected","false")),iframeLibraryTabBtn&&(iframeLibraryTabBtn.classList.add("active"),iframeLibraryTabBtn.setAttribute("aria-selected","true")),urlPane&&urlPane.classList.remove("show","active"),iframeLibraryPane&&iframeLibraryPane.classList.add("show","active")}registerIframeLibraryEventListeners(root){window.addEventListener("message",(event=>{this.handleIframeLibraryMessage(root,event)}))}handleIframeLibraryTabClick(root){const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary),iframeEl=pane?pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame):null;console.log("handleIframeLibraryTabClick called, iframeLibraryLoaded:",this.iframeLibraryLoaded,"iframe src:",iframeEl?iframeEl.src:"no iframe","pane:",pane),this.iframeLibraryLoaded=!1,this.loadIframeLibrary(root)}loadIframeLibrary(root){const ltiConfig=(0,_options.getLti)(this.editor);console.log("loadIframeLibrary called, LTI config:",ltiConfig),null!=ltiConfig&<iConfig.contentItemUrl?this.loadIframeLibraryViaLti(root,ltiConfig):this.loadIframeLibraryStatic(root)}loadIframeLibraryViaLti(root,ltiConfig){console.log("loadIframeLibraryViaLti called, config:",ltiConfig);const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);if(!pane)return void console.log("paneIframeLibrary not found!");const placeholderEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryPlaceholder),loadingEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryLoading),iframeEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame);if(!iframeEl)return void console.log("iframeEl not found!");placeholderEl&&placeholderEl.classList.add("d-none"),loadingEl&&loadingEl.classList.remove("d-none"),iframeEl.classList.add("d-none");iframeEl.addEventListener("load",(()=>{console.log("LTI iframe loaded"),this.handleIframeLibraryLoad(root)})),console.log("Setting iframe src to LTI content item URL:",ltiConfig.contentItemUrl),iframeEl.src=ltiConfig.contentItemUrl}loadIframeLibraryStatic(root){console.log("loadIframeLibraryStatic called, URL:",this.iframeLibraryUrl);const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);if(!pane)return void console.log("paneIframeLibrary not found!");const placeholderEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryPlaceholder),loadingEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryLoading),iframeEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame);if(console.log("Elements found:",{placeholderEl:placeholderEl,loadingEl:loadingEl,iframeEl:iframeEl}),!iframeEl)return void console.log("iframeEl not found!");placeholderEl&&placeholderEl.classList.add("d-none"),loadingEl&&loadingEl.classList.remove("d-none"),iframeEl.classList.add("d-none");const loadHandler=()=>{console.log("iframe loaded, src:",iframeEl.src),iframeEl.src===this.iframeLibraryUrl&&(this.handleIframeLibraryLoad(root),iframeEl.removeEventListener("load",loadHandler))};iframeEl.addEventListener("load",loadHandler),iframeEl.src=this.iframeLibraryUrl,console.log("iframe src set to:",iframeEl.src)}handleIframeLibraryLoad(root){console.log("handleIframeLibraryLoad called");const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);if(!pane)return;const placeholderEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryPlaceholder),loadingEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryLoading),iframeEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame);placeholderEl&&placeholderEl.classList.add("d-none"),loadingEl&&loadingEl.classList.add("d-none"),iframeEl&&iframeEl.classList.remove("d-none"),this.iframeLibraryLoaded=!0}handleIframeLibraryMessage(root,event){console.log("handleIframeLibraryMessage received:",event.data,"from origin:",event.origin);const data=event.data;if(data){if("videoSelected"===data.type&&data.embedUrl)return console.log("Video selected (videoSelected):",data.embedUrl,"videoId:",data.videoId),void this.selectIframeLibraryVideo(root,data.embedUrl,data.videoId);if("ltiDeepLinkingResponse"!==data.type&&"LtiDeepLinkingResponse"!==data.messageType)if("selectMedia"!==data.action&&"mediaSelected"!==data.action);else{const embedUrl=data.embedUrl||data.url||"",videoId=data.mediaId||data.videoId||data.id||"";embedUrl&&(console.log("Video selected (mediaSelected):",embedUrl,"videoId:",videoId),this.selectIframeLibraryVideo(root,embedUrl,videoId))}else{console.log("LTI Deep Linking response received:",data);const contentItems=data.content_items||data.contentItems||[];if(contentItems.length>0){const item=contentItems[0],embedUrl=item.url||item.embed_url||item.embedUrl||"",videoId=item.id||item.mediaId||"";embedUrl&&(console.log("Video selected (LTI):",embedUrl,"videoId:",videoId),this.selectIframeLibraryVideo(root,embedUrl,videoId))}}}}selectIframeLibraryVideo(root,embedUrl,videoId){root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.url).value=embedUrl,this.switchToUrlTab(root),this.updatePreview(root),this.selectedLibraryVideo={embedUrl:embedUrl,videoId:videoId}}},_exports.default})); +define("tiny_mediacms/iframeembed",["exports","core/templates","core/str","core/modal_events","./common","./iframemodal","./selectors","./options"],(function(_exports,_templates,_str,ModalEvents,_common,_iframemodal,_selectors,_options){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _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.default=void 0,_templates=_interopRequireDefault(_templates),ModalEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalEvents),_iframemodal=_interopRequireDefault(_iframemodal),_selectors=_interopRequireDefault(_selectors);return _exports.default=class{constructor(editor){_defineProperty(this,"editor",null),_defineProperty(this,"currentModal",null),_defineProperty(this,"isUpdating",!1),_defineProperty(this,"selectedIframe",null),_defineProperty(this,"debounceTimer",null),_defineProperty(this,"iframeLibraryLoaded",!1),_defineProperty(this,"selectedLibraryVideo",null),_defineProperty(this,"iframeLibraryUrl","https://temp.web357.com/mediacms/deic-mediacms-embed-videos.html"),this.editor=editor}parseInput(input){if(!input||!input.trim())return null;const iframeMatch=(input=input.trim()).match(/]*src=["']([^"']+)["'][^>]*>/i);return iframeMatch?this.parseEmbedUrl(iframeMatch[1]):input.startsWith("http://")||input.startsWith("https://")?this.parseVideoUrl(input):null}parseVideoUrl(url){try{const urlObj=new URL(url),baseUrl="".concat(urlObj.protocol,"//").concat(urlObj.host);if("/view"===urlObj.pathname&&urlObj.searchParams.has("m"))return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!1};if("/embed"===urlObj.pathname&&urlObj.searchParams.has("m")){const tParam=urlObj.searchParams.get("t");return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!0,showTitle:"1"===urlObj.searchParams.get("showTitle"),linkTitle:"1"===urlObj.searchParams.get("linkTitle"),showRelated:"1"===urlObj.searchParams.get("showRelated"),showUserAvatar:"1"===urlObj.searchParams.get("showUserAvatar"),startAt:tParam?this.secondsToTimeString(parseInt(tParam)):null}}return{baseUrl:baseUrl,rawUrl:url,isGeneric:!0}}catch(e){return null}}parseEmbedUrl(url){return this.parseVideoUrl(url)}secondsToTimeString(seconds){const mins=Math.floor(seconds/60),secs=seconds%60;return"".concat(mins,":").concat(secs.toString().padStart(2,"0"))}timeStringToSeconds(timeStr){if(!timeStr||!timeStr.trim())return null;if((timeStr=timeStr.trim()).includes(":")){const parts=timeStr.split(":");return 60*(parseInt(parts[0])||0)+(parseInt(parts[1])||0)}const secs=parseInt(timeStr);return isNaN(secs)?null:secs}buildEmbedUrl(parsed,options){if(parsed.isGeneric)return parsed.rawUrl;const url=new URL("".concat(parsed.baseUrl,"/embed"));if(url.searchParams.set("m",parsed.videoId),url.searchParams.set("showTitle",options.showTitle?"1":"0"),url.searchParams.set("showRelated",options.showRelated?"1":"0"),url.searchParams.set("showUserAvatar",options.showUserAvatar?"1":"0"),url.searchParams.set("linkTitle",options.linkTitle?"1":"0"),options.startAtEnabled&&options.startAt){const seconds=this.timeStringToSeconds(options.startAt);null!==seconds&&seconds>0&&url.searchParams.set("t",seconds.toString())}return url.toString()}async getTemplateContext(){var _this=this;let data=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const editorData=(0,_options.getData)(this.editor),autoConvertOptions=(null==editorData?void 0:editorData.autoConvertOptions)||{},getDefault=function(key){let fallback=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return _this.isUpdating&&void 0!==data[key]?data[key]:void 0!==autoConvertOptions[key]?autoConvertOptions[key]:fallback};return{elementid:this.editor.getElement().id,isupdating:this.isUpdating,url:data.url||"",showTitle:getDefault("showTitle"),linkTitle:getDefault("linkTitle"),showRelated:getDefault("showRelated"),showUserAvatar:getDefault("showUserAvatar"),responsive:!1!==data.responsive,startAtEnabled:data.startAtEnabled||!1,startAt:data.startAt||"0:00",width:data.width||560,height:data.height||315,is16_9:!data.aspectRatio||"16:9"===data.aspectRatio,is4_3:"4:3"===data.aspectRatio,is1_1:"1:1"===data.aspectRatio,isCustom:"custom"===data.aspectRatio}}async displayDialogue(){this.selectedIframe=this.getSelectedIframe();const data=this.getCurrentIframeData();this.isUpdating=null!==data,this.iframeLibraryLoaded=!1,this.currentModal=await _iframemodal.default.create({title:(0,_str.getString)("iframemodaltitle",_common.component),templateContext:await this.getTemplateContext(data||{})}),await this.registerEventListeners(this.currentModal)}getSelectedIframe(){const node=this.editor.selection.getNode();if("iframe"===node.nodeName.toLowerCase())return node;const iframe=node.querySelector("iframe");if(iframe)return iframe;const wrapper=node.closest(".tiny-mediacms-iframe-wrapper")||node.closest(".tiny-iframe-responsive");return wrapper?wrapper.querySelector("iframe"):null}getCurrentIframeData(){var _parsed$showTitle,_parsed$linkTitle,_parsed$showRelated,_parsed$showUserAvata;if(!this.selectedIframe)return null;const src=this.selectedIframe.getAttribute("src"),parsed=this.parseInput(src),isResponsive=(this.selectedIframe.getAttribute("style")||"").includes("aspect-ratio");return{url:src,width:this.selectedIframe.getAttribute("width")||560,height:this.selectedIframe.getAttribute("height")||315,showTitle:null===(_parsed$showTitle=null==parsed?void 0:parsed.showTitle)||void 0===_parsed$showTitle||_parsed$showTitle,linkTitle:null===(_parsed$linkTitle=null==parsed?void 0:parsed.linkTitle)||void 0===_parsed$linkTitle||_parsed$linkTitle,showRelated:null===(_parsed$showRelated=null==parsed?void 0:parsed.showRelated)||void 0===_parsed$showRelated||_parsed$showRelated,showUserAvatar:null===(_parsed$showUserAvata=null==parsed?void 0:parsed.showUserAvatar)||void 0===_parsed$showUserAvata||_parsed$showUserAvata,responsive:isResponsive,startAtEnabled:null!==(null==parsed?void 0:parsed.startAt),startAt:(null==parsed?void 0:parsed.startAt)||"0:00"}}getFormValues(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form);return{url:form.querySelector(_selectors.default.IFRAME.elements.url).value.trim(),showTitle:form.querySelector(_selectors.default.IFRAME.elements.showTitle).checked,linkTitle:form.querySelector(_selectors.default.IFRAME.elements.linkTitle).checked,showRelated:form.querySelector(_selectors.default.IFRAME.elements.showRelated).checked,showUserAvatar:form.querySelector(_selectors.default.IFRAME.elements.showUserAvatar).checked,responsive:form.querySelector(_selectors.default.IFRAME.elements.responsive).checked,startAtEnabled:form.querySelector(_selectors.default.IFRAME.elements.startAtEnabled).checked,startAt:form.querySelector(_selectors.default.IFRAME.elements.startAt).value.trim(),aspectRatio:form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,width:parseInt(form.querySelector(_selectors.default.IFRAME.elements.width).value)||560,height:parseInt(form.querySelector(_selectors.default.IFRAME.elements.height).value)||315}}async generateIframeHtml(values){const parsed=this.parseInput(values.url);if(!parsed)return"";const embedUrl=this.buildEmbedUrl(parsed,values),aspectRatioCalcs={"16:9":"16 / 9","4:3":"4 / 3","1:1":"1 / 1",custom:"".concat(values.width," / ").concat(values.height)},context={src:embedUrl,width:values.width,height:values.height,responsive:values.responsive,aspectRatioCalc:aspectRatioCalcs[values.aspectRatio]||"16 / 9",aspectRatioValue:aspectRatioCalcs[values.aspectRatio]||"16 / 9"},{html:html}=await _templates.default.renderForPromise("tiny_mediacms/iframe_embed_output",context);return html}async updatePreview(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const values=this.getFormValues(root),previewContainer=root.querySelector(_selectors.default.IFRAME.elements.preview),urlWarning=root.querySelector(_selectors.default.IFRAME.elements.urlWarning);if(!values.url)return previewContainer.innerHTML='Enter a video URL to see preview',void urlWarning.classList.add("d-none");const parsed=this.parseInput(values.url);if(!parsed)return previewContainer.innerHTML='Invalid URL format',void urlWarning.classList.remove("d-none");urlWarning.classList.add("d-none");const embedUrl=this.buildEmbedUrl(parsed,values);if(updateUrlField&&!parsed.isGeneric){root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.url).value=embedUrl}const previewWidth=Math.min(values.width,400),scale=previewWidth/values.width,previewHeight=Math.round(values.height*scale);previewContainer.innerHTML='\n \n ')}handleInputChange(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout((()=>{this.updatePreview(root,updateUrlField)}),500)}handleAspectRatioChange(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,dimensions=_selectors.default.IFRAME.aspectRatios[aspectRatio];dimensions&&"custom"!==aspectRatio&&(form.querySelector(_selectors.default.IFRAME.elements.width).value=dimensions.width,form.querySelector(_selectors.default.IFRAME.elements.height).value=dimensions.height),this.updatePreview(root)}async handleDialogueSubmission(modal){const root=modal.getRoot()[0],values=this.getFormValues(root);if(!values.url)return;const html=await this.generateIframeHtml(values);if(html)if(this.isUpdating&&this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.outerHTML=html:this.selectedIframe.outerHTML=html,this.isUpdating=!1,this.editor.fire("Change")}else this.editor.insertContent(html)}async handleRemove(modal){const confirmMessage=await(0,_str.getString)("removeiframeconfirm",_common.component);if(window.confirm(confirmMessage)){if(this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.remove():this.selectedIframe.remove()}this.isUpdating=!1,modal.hide()}}async registerEventListeners(modal){await modal.getBody();const $root=modal.getRoot(),root=$root[0],form=root.querySelector(_selectors.default.IFRAME.elements.form);form.querySelector(_selectors.default.IFRAME.elements.url).addEventListener("input",(()=>this.handleInputChange(root))),[_selectors.default.IFRAME.elements.showTitle,_selectors.default.IFRAME.elements.linkTitle,_selectors.default.IFRAME.elements.showRelated,_selectors.default.IFRAME.elements.showUserAvatar,_selectors.default.IFRAME.elements.startAtEnabled].forEach((selector=>{form.querySelector(selector).addEventListener("change",(()=>this.handleInputChange(root,!0)))})),form.querySelector(_selectors.default.IFRAME.elements.responsive).addEventListener("change",(()=>this.handleInputChange(root,!1))),form.querySelector(_selectors.default.IFRAME.elements.startAt).addEventListener("input",(()=>this.handleInputChange(root,!0))),form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).addEventListener("change",(()=>this.handleAspectRatioChange(root))),form.querySelector(_selectors.default.IFRAME.elements.width).addEventListener("input",(()=>this.handleInputChange(root))),form.querySelector(_selectors.default.IFRAME.elements.height).addEventListener("input",(()=>this.handleInputChange(root))),$root.on(ModalEvents.save,(()=>this.handleDialogueSubmission(modal))),$root.on(ModalEvents.hidden,(()=>{this.currentModal.destroy()}));const removeBtn=root.querySelector(_selectors.default.IFRAME.actions.remove);removeBtn&&removeBtn.addEventListener("click",(()=>this.handleRemove(modal)));form.querySelector(_selectors.default.IFRAME.elements.url).value&&this.updatePreview(root);const iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn);if(iframeLibraryTabBtn){iframeLibraryTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToIframeLibraryTab(root),setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)})),iframeLibraryTabBtn.addEventListener("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)));const $iframeLibraryTabBtn=window.jQuery?window.jQuery(iframeLibraryTabBtn):null;$iframeLibraryTabBtn&&$iframeLibraryTabBtn.on("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)))}const urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn);urlTabBtn&&urlTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToUrlTab(root)})),this.registerIframeLibraryEventListeners(root),setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)}switchToUrlTab(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabBtn&&(urlTabBtn.classList.add("active"),urlTabBtn.setAttribute("aria-selected","true")),iframeLibraryTabBtn&&(iframeLibraryTabBtn.classList.remove("active"),iframeLibraryTabBtn.setAttribute("aria-selected","false")),urlPane&&urlPane.classList.add("show","active"),iframeLibraryPane&&iframeLibraryPane.classList.remove("show","active")}switchToIframeLibraryTab(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabBtn&&(urlTabBtn.classList.remove("active"),urlTabBtn.setAttribute("aria-selected","false")),iframeLibraryTabBtn&&(iframeLibraryTabBtn.classList.add("active"),iframeLibraryTabBtn.setAttribute("aria-selected","true")),urlPane&&urlPane.classList.remove("show","active"),iframeLibraryPane&&iframeLibraryPane.classList.add("show","active")}registerIframeLibraryEventListeners(root){window.addEventListener("message",(event=>{this.handleIframeLibraryMessage(root,event)}))}handleIframeLibraryTabClick(root){this.iframeLibraryLoaded=!1,this.loadIframeLibrary(root)}loadIframeLibrary(root){const ltiConfig=(0,_options.getLti)(this.editor);null!=ltiConfig&<iConfig.contentItemUrl?this.loadIframeLibraryViaLti(root):this.loadIframeLibraryStatic(root)}loadIframeLibraryViaLti(root){const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);if(!pane)return;const placeholderEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryPlaceholder),loadingEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryLoading),iframeEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame);if(!iframeEl)return;placeholderEl&&placeholderEl.classList.add("d-none"),loadingEl&&loadingEl.classList.remove("d-none"),iframeEl.classList.add("d-none");iframeEl.addEventListener("load",(()=>{this.handleIframeLibraryLoad(root)}));const ltiConfig=(0,_options.getLti)(this.editor);iframeEl.src=ltiConfig.contentItemUrl}loadIframeLibraryStatic(root){const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);if(!pane)return;const placeholderEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryPlaceholder),loadingEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryLoading),iframeEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame);if(!iframeEl)return;placeholderEl&&placeholderEl.classList.add("d-none"),loadingEl&&loadingEl.classList.remove("d-none"),iframeEl.classList.add("d-none");const loadHandler=()=>{iframeEl.src===this.iframeLibraryUrl&&(this.handleIframeLibraryLoad(root),iframeEl.removeEventListener("load",loadHandler))};iframeEl.addEventListener("load",loadHandler),iframeEl.src=this.iframeLibraryUrl}handleIframeLibraryLoad(root){const pane=root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);if(!pane)return;const placeholderEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryPlaceholder),loadingEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryLoading),iframeEl=pane.querySelector(_selectors.default.IFRAME.elements.iframeLibraryFrame);placeholderEl&&placeholderEl.classList.add("d-none"),loadingEl&&loadingEl.classList.add("d-none"),iframeEl&&iframeEl.classList.remove("d-none"),this.iframeLibraryLoaded=!0}handleIframeLibraryMessage(root,event){const data=event.data;if(data)if("videoSelected"===data.type&&data.embedUrl)this.selectIframeLibraryVideo(root,data.embedUrl,data.videoId);else if("ltiDeepLinkingResponse"!==data.type&&"LtiDeepLinkingResponse"!==data.messageType)if("selectMedia"!==data.action&&"mediaSelected"!==data.action);else{const embedUrl=data.embedUrl||data.url||"",videoId=data.mediaId||data.videoId||data.id||"";embedUrl&&this.selectIframeLibraryVideo(root,embedUrl,videoId)}else{const contentItems=data.content_items||data.contentItems||[];if(contentItems.length>0){const item=contentItems[0],embedUrl=item.url||item.embed_url||item.embedUrl||"",videoId=item.id||item.mediaId||"";embedUrl&&this.selectIframeLibraryVideo(root,embedUrl,videoId)}}}selectIframeLibraryVideo(root,embedUrl,videoId){root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.url).value=embedUrl;const configureTabItem=root.querySelector(".tiny_iframecms_tab_url_item");configureTabItem&&(configureTabItem.style.display=""),this.switchToUrlTab(root),this.updatePreview(root),this.selectedLibraryVideo={embedUrl:embedUrl,videoId:videoId}}},_exports.default})); //# sourceMappingURL=iframeembed.min.js.map \ No newline at end of file diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js.map b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js.map index 3168e79a..7639990a 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js.map +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js.map @@ -1 +1 @@ -{"version":3,"file":"iframeembed.min.js","sources":["../src/iframeembed.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Media2 Iframe Embed class.\n *\n * @module tiny_mediacms/iframeembed\n * @copyright 2024\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport { getString } from 'core/str';\nimport * as ModalEvents from 'core/modal_events';\nimport { component } from './common';\nimport IframeModal from './iframemodal';\nimport Selectors from './selectors';\nimport { getLti, getData } from './options';\n\nexport default class IframeEmbed {\n editor = null;\n currentModal = null;\n isUpdating = false;\n selectedIframe = null;\n debounceTimer = null;\n // Iframe library state\n iframeLibraryLoaded = false;\n selectedLibraryVideo = null;\n iframeLibraryUrl =\n 'https://temp.web357.com/mediacms/deic-mediacms-embed-videos.html';\n\n constructor(editor) {\n this.editor = editor;\n }\n\n /**\n * Parse input to extract video URL or iframe src.\n * Handles: iframe embed code, view URL, or embed URL.\n *\n * @param {string} input - The user input (URL or embed code)\n * @returns {Object|null} Parsed URL info or null if invalid\n */\n parseInput(input) {\n if (!input || !input.trim()) {\n return null;\n }\n\n input = input.trim();\n\n // Check if it's an iframe embed code\n const iframeMatch = input.match(\n /]*src=[\"']([^\"']+)[\"'][^>]*>/i,\n );\n if (iframeMatch) {\n return this.parseEmbedUrl(iframeMatch[1]);\n }\n\n // Check if it's a URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return this.parseVideoUrl(input);\n }\n\n return null;\n }\n\n /**\n * Parse a video view URL and convert to embed format.\n *\n * @param {string} url - The video URL\n * @returns {Object|null} Parsed info\n */\n parseVideoUrl(url) {\n try {\n const urlObj = new URL(url);\n const baseUrl = `${urlObj.protocol}//${urlObj.host}`;\n\n // MediaCMS view URL: /view?m=VIDEO_ID\n if (urlObj.pathname === '/view' && urlObj.searchParams.has('m')) {\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: false,\n };\n }\n\n // MediaCMS embed URL: /embed?m=VIDEO_ID&options\n if (urlObj.pathname === '/embed' && urlObj.searchParams.has('m')) {\n const tParam = urlObj.searchParams.get('t');\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: true,\n showTitle: urlObj.searchParams.get('showTitle') === '1',\n linkTitle: urlObj.searchParams.get('linkTitle') === '1',\n showRelated: urlObj.searchParams.get('showRelated') === '1',\n showUserAvatar:\n urlObj.searchParams.get('showUserAvatar') === '1',\n startAt: tParam\n ? this.secondsToTimeString(parseInt(tParam))\n : null,\n };\n }\n\n // Generic URL - just use as-is\n return {\n baseUrl: baseUrl,\n rawUrl: url,\n isGeneric: true,\n };\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Parse an embed URL.\n *\n * @param {string} url - The embed URL\n * @returns {Object|null} Parsed info\n */\n parseEmbedUrl(url) {\n return this.parseVideoUrl(url);\n }\n\n /**\n * Convert seconds to time string (M:SS format).\n *\n * @param {number} seconds - Time in seconds\n * @returns {string} Time string\n */\n secondsToTimeString(seconds) {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n /**\n * Convert time string to seconds.\n *\n * @param {string} timeStr - Time string (M:SS or just seconds)\n * @returns {number|null} Seconds or null if invalid\n */\n timeStringToSeconds(timeStr) {\n if (!timeStr || !timeStr.trim()) {\n return null;\n }\n timeStr = timeStr.trim();\n\n // Handle M:SS format\n if (timeStr.includes(':')) {\n const parts = timeStr.split(':');\n const mins = parseInt(parts[0]) || 0;\n const secs = parseInt(parts[1]) || 0;\n return mins * 60 + secs;\n }\n\n // Handle plain seconds\n const secs = parseInt(timeStr);\n return isNaN(secs) ? null : secs;\n }\n\n /**\n * Build the embed URL with options.\n *\n * @param {Object} parsed - Parsed URL info\n * @param {Object} options - Embed options\n * @returns {string} The complete embed URL\n */\n buildEmbedUrl(parsed, options) {\n if (parsed.isGeneric) {\n return parsed.rawUrl;\n }\n\n const url = new URL(`${parsed.baseUrl}/embed`);\n url.searchParams.set('m', parsed.videoId);\n\n // Always include all options with 1 or 0\n url.searchParams.set('showTitle', options.showTitle ? '1' : '0');\n url.searchParams.set('showRelated', options.showRelated ? '1' : '0');\n url.searchParams.set(\n 'showUserAvatar',\n options.showUserAvatar ? '1' : '0',\n );\n url.searchParams.set('linkTitle', options.linkTitle ? '1' : '0');\n\n // Add start time if enabled\n if (options.startAtEnabled && options.startAt) {\n const seconds = this.timeStringToSeconds(options.startAt);\n if (seconds !== null && seconds > 0) {\n url.searchParams.set('t', seconds.toString());\n }\n }\n\n return url.toString();\n }\n\n /**\n * Get the template context for the modal.\n *\n * @param {Object} data - Existing data for updating\n * @returns {Object} Template context\n */\n async getTemplateContext(data = {}) {\n // Get admin settings for default checkbox values.\n const editorData = getData(this.editor);\n const autoConvertOptions = editorData?.autoConvertOptions || {};\n\n // Use admin settings as defaults when creating new embed (not updating).\n // When updating, use the values from the existing iframe.\n const getDefault = (key, fallback = true) => {\n if (this.isUpdating && data[key] !== undefined) {\n return data[key];\n }\n return autoConvertOptions[key] !== undefined\n ? autoConvertOptions[key]\n : fallback;\n };\n\n return {\n elementid: this.editor.getElement().id,\n isupdating: this.isUpdating,\n url: data.url || '',\n showTitle: getDefault('showTitle'),\n linkTitle: getDefault('linkTitle'),\n showRelated: getDefault('showRelated'),\n showUserAvatar: getDefault('showUserAvatar'),\n responsive: data.responsive !== false,\n startAtEnabled: data.startAtEnabled || false,\n startAt: data.startAt || '0:00',\n width: data.width || 560,\n height: data.height || 315,\n is16_9: !data.aspectRatio || data.aspectRatio === '16:9',\n is4_3: data.aspectRatio === '4:3',\n is1_1: data.aspectRatio === '1:1',\n isCustom: data.aspectRatio === 'custom',\n };\n }\n\n /**\n * Display the iframe embed dialog.\n */\n async displayDialogue() {\n this.selectedIframe = this.getSelectedIframe();\n const data = this.getCurrentIframeData();\n this.isUpdating = data !== null;\n\n // Reset iframe library state for new modal\n this.iframeLibraryLoaded = false;\n\n this.currentModal = await IframeModal.create({\n title: getString('iframemodaltitle', component),\n templateContext: await this.getTemplateContext(data || {}),\n });\n\n await this.registerEventListeners(this.currentModal);\n }\n\n /**\n * Get the currently selected iframe in the editor.\n *\n * @returns {HTMLElement|null} The iframe element or null\n */\n getSelectedIframe() {\n const node = this.editor.selection.getNode();\n\n if (node.nodeName.toLowerCase() === 'iframe') {\n return node;\n }\n\n // Check if selection contains an iframe wrapper (including overlay wrapper)\n const iframe = node.querySelector('iframe');\n if (iframe) {\n return iframe;\n }\n\n // Check if we're on the overlay or wrapper and need to find the iframe\n const wrapper =\n node.closest('.tiny-mediacms-iframe-wrapper') ||\n node.closest('.tiny-iframe-responsive');\n if (wrapper) {\n return wrapper.querySelector('iframe');\n }\n\n return null;\n }\n\n /**\n * Get current iframe data for editing.\n *\n * @returns {Object|null} Current iframe data or null\n */\n getCurrentIframeData() {\n if (!this.selectedIframe) {\n return null;\n }\n\n const src = this.selectedIframe.getAttribute('src');\n const parsed = this.parseInput(src);\n\n // Check if responsive by looking at style\n const style = this.selectedIframe.getAttribute('style') || '';\n const isResponsive = style.includes('aspect-ratio');\n\n return {\n url: src,\n width: this.selectedIframe.getAttribute('width') || 560,\n height: this.selectedIframe.getAttribute('height') || 315,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\n responsive: isResponsive,\n startAtEnabled: parsed?.startAt !== null,\n startAt: parsed?.startAt || '0:00',\n };\n }\n\n /**\n * Get form values from the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @returns {Object} Form values\n */\n getFormValues(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n return {\n url: form.querySelector(Selectors.IFRAME.elements.url).value.trim(),\n showTitle: form.querySelector(Selectors.IFRAME.elements.showTitle)\n .checked,\n linkTitle: form.querySelector(Selectors.IFRAME.elements.linkTitle)\n .checked,\n showRelated: form.querySelector(\n Selectors.IFRAME.elements.showRelated,\n ).checked,\n showUserAvatar: form.querySelector(\n Selectors.IFRAME.elements.showUserAvatar,\n ).checked,\n responsive: form.querySelector(Selectors.IFRAME.elements.responsive)\n .checked,\n startAtEnabled: form.querySelector(\n Selectors.IFRAME.elements.startAtEnabled,\n ).checked,\n startAt: form\n .querySelector(Selectors.IFRAME.elements.startAt)\n .value.trim(),\n aspectRatio: form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).value,\n width:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.width).value,\n ) || 560,\n height:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.height).value,\n ) || 315,\n };\n }\n\n /**\n * Generate the iframe HTML.\n *\n * @param {Object} values - Form values\n * @returns {Promise} Generated HTML\n */\n async generateIframeHtml(values) {\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n return '';\n }\n\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Calculate aspect ratio values for CSS\n const aspectRatioCalcs = {\n '16:9': '16 / 9',\n '4:3': '4 / 3',\n '1:1': '1 / 1',\n custom: `${values.width} / ${values.height}`,\n };\n\n const context = {\n src: embedUrl,\n width: values.width,\n height: values.height,\n responsive: values.responsive,\n aspectRatioCalc: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n aspectRatioValue: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n };\n\n const { html } = await Templates.renderForPromise(\n 'tiny_mediacms/iframe_embed_output',\n context,\n );\n return html;\n }\n\n /**\n * Update the preview in the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field with new embed options\n */\n async updatePreview(root, updateUrlField = false) {\n const values = this.getFormValues(root);\n const previewContainer = root.querySelector(\n Selectors.IFRAME.elements.preview,\n );\n const urlWarning = root.querySelector(\n Selectors.IFRAME.elements.urlWarning,\n );\n\n if (!values.url) {\n previewContainer.innerHTML =\n 'Enter a video URL to see preview';\n urlWarning.classList.add('d-none');\n return;\n }\n\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n previewContainer.innerHTML =\n 'Invalid URL format';\n urlWarning.classList.remove('d-none');\n return;\n }\n\n urlWarning.classList.add('d-none');\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Update the URL field if requested (when embed options change)\n if (updateUrlField && !parsed.isGeneric) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n urlInput.value = embedUrl;\n }\n\n // Show a scaled preview\n const previewWidth = Math.min(values.width, 400);\n const scale = previewWidth / values.width;\n const previewHeight = Math.round(values.height * scale);\n\n previewContainer.innerHTML = `\n \n `;\n }\n\n /**\n * Handle form input changes with debounce.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field\n */\n handleInputChange(root, updateUrlField = false) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n this.updatePreview(root, updateUrlField);\n }, 500);\n }\n\n /**\n * Handle aspect ratio change - update dimensions.\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleAspectRatioChange(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).value;\n const dimensions = Selectors.IFRAME.aspectRatios[aspectRatio];\n\n // Only update dimensions for predefined ratios, not 'custom'\n if (dimensions && aspectRatio !== 'custom') {\n form.querySelector(Selectors.IFRAME.elements.width).value =\n dimensions.width;\n form.querySelector(Selectors.IFRAME.elements.height).value =\n dimensions.height;\n }\n\n this.updatePreview(root);\n }\n\n /**\n * Handle dialog submission.\n *\n * @param {Object} modal - The modal instance\n */\n async handleDialogueSubmission(modal) {\n const root = modal.getRoot()[0];\n const values = this.getFormValues(root);\n\n if (!values.url) {\n return;\n }\n\n const html = await this.generateIframeHtml(values);\n if (html) {\n if (this.isUpdating && this.selectedIframe) {\n // Replace existing iframe (including wrapper if present)\n // Check for both old wrapper and new overlay wrapper\n const wrapper =\n this.selectedIframe.closest(\n '.tiny-mediacms-iframe-wrapper',\n ) || this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.outerHTML = html;\n } else {\n this.selectedIframe.outerHTML = html;\n }\n this.isUpdating = false;\n // Fire change event to trigger overlay reprocessing\n this.editor.fire('Change');\n } else {\n this.editor.insertContent(html);\n }\n }\n }\n\n /**\n * Handle video removal from the editor.\n *\n * @param {Object} modal - The modal instance\n */\n async handleRemove(modal) {\n // Get confirmation string\n const confirmMessage = await getString(\n 'removeiframeconfirm',\n component,\n );\n\n // Show confirmation dialog\n // eslint-disable-next-line no-alert\n if (!window.confirm(confirmMessage)) {\n return;\n }\n\n if (this.selectedIframe) {\n // Remove the iframe (including wrapper if present)\n const wrapper =\n this.selectedIframe.closest('.tiny-mediacms-iframe-wrapper') ||\n this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.remove();\n } else {\n this.selectedIframe.remove();\n }\n }\n\n // Close the modal\n this.isUpdating = false;\n modal.hide();\n }\n\n /**\n * Register event listeners for the modal.\n *\n * @param {Object} modal - The modal instance\n */\n async registerEventListeners(modal) {\n await modal.getBody();\n const $root = modal.getRoot();\n const root = $root[0];\n\n // Input change handlers\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // URL input\n form.querySelector(Selectors.IFRAME.elements.url).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Embed option checkboxes - these should update the URL field when changed\n [\n Selectors.IFRAME.elements.showTitle,\n Selectors.IFRAME.elements.linkTitle,\n Selectors.IFRAME.elements.showRelated,\n Selectors.IFRAME.elements.showUserAvatar,\n Selectors.IFRAME.elements.startAtEnabled,\n ].forEach((selector) => {\n form.querySelector(selector).addEventListener('change', () =>\n this.handleInputChange(root, true), // Update URL field when embed options change\n );\n });\n\n // Responsive checkbox - doesn't affect URL, only display\n form.querySelector(Selectors.IFRAME.elements.responsive).addEventListener('change', () =>\n this.handleInputChange(root, false),\n );\n\n // Start at time input - should update URL field\n form.querySelector(Selectors.IFRAME.elements.startAt).addEventListener(\n 'input',\n () => this.handleInputChange(root, true),\n );\n\n // Aspect ratio change\n form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).addEventListener('change', () => this.handleAspectRatioChange(root));\n\n // Dimension inputs\n form.querySelector(Selectors.IFRAME.elements.width).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n form.querySelector(Selectors.IFRAME.elements.height).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Modal events\n $root.on(ModalEvents.save, () => this.handleDialogueSubmission(modal));\n $root.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n\n // Remove button handler (only present when updating)\n const removeBtn = root.querySelector(Selectors.IFRAME.actions.remove);\n if (removeBtn) {\n removeBtn.addEventListener('click', () => this.handleRemove(modal));\n }\n\n // Initial preview if we have a URL\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n if (urlInput.value) {\n this.updatePreview(root);\n }\n\n // Tab change handler - load iframe library when switching to iframe library tab\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching for Bootstrap 5\n this.switchToIframeLibraryTab(root);\n\n // Small delay to ensure tab pane is visible before loading iframe\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n });\n // Also handle Bootstrap tab events (if Bootstrap handles it)\n iframeLibraryTabBtn.addEventListener('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n // jQuery Bootstrap 4 event\n const $iframeLibraryTabBtn = window.jQuery\n ? window.jQuery(iframeLibraryTabBtn)\n : null;\n if ($iframeLibraryTabBtn) {\n $iframeLibraryTabBtn.on('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n }\n }\n\n // Tab change handler for URL tab\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n if (urlTabBtn) {\n urlTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching\n this.switchToUrlTab(root);\n });\n }\n\n // Iframe library event listeners\n this.registerIframeLibraryEventListeners(root);\n }\n\n /**\n * Switch to the URL tab.\n *\n * @param {HTMLElement} root - Modal root element\n */\n switchToUrlTab(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Get tab buttons\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n\n // Get tab panes\n const urlPane = form.querySelector(Selectors.IFRAME.elements.paneUrl);\n const iframeLibraryPane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n // Update tab button states\n if (urlTabBtn) {\n urlTabBtn.classList.add('active');\n urlTabBtn.setAttribute('aria-selected', 'true');\n }\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.classList.remove('active');\n iframeLibraryTabBtn.setAttribute('aria-selected', 'false');\n }\n\n // Update tab pane visibility\n if (urlPane) {\n urlPane.classList.add('show', 'active');\n }\n if (iframeLibraryPane) {\n iframeLibraryPane.classList.remove('show', 'active');\n }\n }\n\n /**\n * Switch to the iframe library tab.\n *\n * @param {HTMLElement} root - Modal root element\n */\n switchToIframeLibraryTab(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Get tab buttons\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n\n // Get tab panes\n const urlPane = form.querySelector(Selectors.IFRAME.elements.paneUrl);\n const iframeLibraryPane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n // Update tab button states\n if (urlTabBtn) {\n urlTabBtn.classList.remove('active');\n urlTabBtn.setAttribute('aria-selected', 'false');\n }\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.classList.add('active');\n iframeLibraryTabBtn.setAttribute('aria-selected', 'true');\n }\n\n // Update tab pane visibility\n if (urlPane) {\n urlPane.classList.remove('show', 'active');\n }\n if (iframeLibraryPane) {\n iframeLibraryPane.classList.add('show', 'active');\n }\n }\n\n /**\n * Register event listeners for the iframe library.\n *\n * @param {HTMLElement} root - Modal root element\n */\n registerIframeLibraryEventListeners(root) {\n // Listen for messages from the iframe (for video selection)\n window.addEventListener('message', (event) => {\n this.handleIframeLibraryMessage(root, event);\n });\n }\n\n /**\n * Handle iframe library tab click - always refetch content (no caching).\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleIframeLibraryTabClick(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n const iframeEl = pane\n ? pane.querySelector(Selectors.IFRAME.elements.iframeLibraryFrame)\n : null;\n\n // eslint-disable-next-line no-console\n console.log(\n 'handleIframeLibraryTabClick called, iframeLibraryLoaded:',\n this.iframeLibraryLoaded,\n 'iframe src:',\n iframeEl ? iframeEl.src : 'no iframe',\n 'pane:',\n pane,\n );\n\n // Always refetch content when tab is clicked (no caching)\n // Reset the loaded state to ensure fresh content is fetched\n this.iframeLibraryLoaded = false;\n this.loadIframeLibrary(root);\n }\n\n /**\n * Load the iframe library using LTI flow or fallback to static URL.\n *\n * @param {HTMLElement} root - Modal root element\n */\n loadIframeLibrary(root) {\n const ltiConfig = getLti(this.editor);\n\n // eslint-disable-next-line no-console\n console.log('loadIframeLibrary called, LTI config:', ltiConfig);\n\n // Check if LTI is configured with a content item URL\n if (ltiConfig?.contentItemUrl) {\n this.loadIframeLibraryViaLti(root, ltiConfig);\n } else {\n // Fallback to static URL if LTI not configured\n this.loadIframeLibraryStatic(root);\n }\n }\n\n /**\n * Load the iframe library via LTI Deep Linking (Content Item Selection).\n * Sets the iframe src to contentitem.php which initiates the LTI Deep Linking flow.\n * This sends an LtiDeepLinkingRequest message, which will redirect to the\n * tool's content selection interface (e.g., /lti/select-media/).\n *\n * @param {HTMLElement} root - Modal root element\n * @param {Object} ltiConfig - LTI configuration\n */\n loadIframeLibraryViaLti(root, ltiConfig) {\n // eslint-disable-next-line no-console\n console.log('loadIframeLibraryViaLti called, config:', ltiConfig);\n\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n if (!pane) {\n // eslint-disable-next-line no-console\n console.log('paneIframeLibrary not found!');\n return;\n }\n\n const placeholderEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryPlaceholder,\n );\n const loadingEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryLoading,\n );\n const iframeEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryFrame,\n );\n\n if (!iframeEl) {\n // eslint-disable-next-line no-console\n console.log('iframeEl not found!');\n return;\n }\n\n // Hide placeholder, show loading state\n if (placeholderEl) {\n placeholderEl.classList.add('d-none');\n }\n if (loadingEl) {\n loadingEl.classList.remove('d-none');\n }\n iframeEl.classList.add('d-none');\n\n // Set up load listener - note: this may fire multiple times during LTI redirects\n const loadHandler = () => {\n // eslint-disable-next-line no-console\n console.log('LTI iframe loaded');\n this.handleIframeLibraryLoad(root);\n };\n iframeEl.addEventListener('load', loadHandler);\n\n // Set the iframe src to the content item URL\n // This initiates the LTI Deep Linking flow:\n // 1. contentitem.php initiates OIDC login\n // 2. LTI provider authenticates\n // 3. Moodle sends LtiDeepLinkingRequest\n // 4. Tool provider shows content selection interface (e.g., /lti/select-media/)\n // eslint-disable-next-line no-console\n console.log(\n 'Setting iframe src to LTI content item URL:',\n ltiConfig.contentItemUrl,\n );\n iframeEl.src = ltiConfig.contentItemUrl;\n }\n\n /**\n * Load the iframe library using static URL (fallback).\n *\n * @param {HTMLElement} root - Modal root element\n */\n loadIframeLibraryStatic(root) {\n // eslint-disable-next-line no-console\n console.log(\n 'loadIframeLibraryStatic called, URL:',\n this.iframeLibraryUrl,\n );\n\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n if (!pane) {\n // eslint-disable-next-line no-console\n console.log('paneIframeLibrary not found!');\n return;\n }\n\n const placeholderEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryPlaceholder,\n );\n const loadingEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryLoading,\n );\n const iframeEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryFrame,\n );\n\n // eslint-disable-next-line no-console\n console.log('Elements found:', { placeholderEl, loadingEl, iframeEl });\n\n if (!iframeEl) {\n // eslint-disable-next-line no-console\n console.log('iframeEl not found!');\n return;\n }\n\n // Hide placeholder, show loading state\n if (placeholderEl) {\n placeholderEl.classList.add('d-none');\n }\n if (loadingEl) {\n loadingEl.classList.remove('d-none');\n }\n iframeEl.classList.add('d-none');\n\n // Set up load listener before setting src\n const loadHandler = () => {\n // eslint-disable-next-line no-console\n console.log('iframe loaded, src:', iframeEl.src);\n // Only handle if the src matches our target URL\n if (iframeEl.src === this.iframeLibraryUrl) {\n this.handleIframeLibraryLoad(root);\n // Remove the listener after successful load\n iframeEl.removeEventListener('load', loadHandler);\n }\n };\n iframeEl.addEventListener('load', loadHandler);\n\n // Set the iframe source\n iframeEl.src = this.iframeLibraryUrl;\n // eslint-disable-next-line no-console\n console.log('iframe src set to:', iframeEl.src);\n }\n\n /**\n * Handle iframe library load event.\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleIframeLibraryLoad(root) {\n // eslint-disable-next-line no-console\n console.log('handleIframeLibraryLoad called');\n\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n if (!pane) {\n return;\n }\n\n const placeholderEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryPlaceholder,\n );\n const loadingEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryLoading,\n );\n const iframeEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryFrame,\n );\n\n // Hide placeholder and loading, show iframe\n if (placeholderEl) {\n placeholderEl.classList.add('d-none');\n }\n if (loadingEl) {\n loadingEl.classList.add('d-none');\n }\n if (iframeEl) {\n iframeEl.classList.remove('d-none');\n }\n\n this.iframeLibraryLoaded = true;\n }\n\n /**\n * Handle messages from the iframe library (for video selection).\n * Supports both custom videoSelected messages and LTI Deep Linking responses.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {MessageEvent} event - The message event\n */\n handleIframeLibraryMessage(root, event) {\n // eslint-disable-next-line no-console\n console.log(\n 'handleIframeLibraryMessage received:',\n event.data,\n 'from origin:',\n event.origin,\n );\n\n const data = event.data;\n\n if (!data) {\n return;\n }\n\n // Handle custom videoSelected message format (from static iframe or custom MediaCMS implementation)\n if (data.type === 'videoSelected' && data.embedUrl) {\n // eslint-disable-next-line no-console\n console.log(\n 'Video selected (videoSelected):',\n data.embedUrl,\n 'videoId:',\n data.videoId,\n );\n this.selectIframeLibraryVideo(root, data.embedUrl, data.videoId);\n return;\n }\n\n // Handle LTI Deep Linking response format\n if (\n data.type === 'ltiDeepLinkingResponse' ||\n data.messageType === 'LtiDeepLinkingResponse'\n ) {\n // eslint-disable-next-line no-console\n console.log('LTI Deep Linking response received:', data);\n const contentItems = data.content_items || data.contentItems || [];\n if (contentItems.length > 0) {\n const item = contentItems[0];\n // Extract embed URL from the content item\n const embedUrl =\n item.url || item.embed_url || item.embedUrl || '';\n const videoId = item.id || item.mediaId || '';\n if (embedUrl) {\n // eslint-disable-next-line no-console\n console.log(\n 'Video selected (LTI):',\n embedUrl,\n 'videoId:',\n videoId,\n );\n this.selectIframeLibraryVideo(root, embedUrl, videoId);\n }\n }\n return;\n }\n\n // Handle MediaCMS specific message format (if different from above)\n if (data.action === 'selectMedia' || data.action === 'mediaSelected') {\n const embedUrl = data.embedUrl || data.url || '';\n const videoId = data.mediaId || data.videoId || data.id || '';\n if (embedUrl) {\n // eslint-disable-next-line no-console\n console.log(\n 'Video selected (mediaSelected):',\n embedUrl,\n 'videoId:',\n videoId,\n );\n this.selectIframeLibraryVideo(root, embedUrl, videoId);\n }\n return;\n }\n }\n\n /**\n * Select a video from the iframe library and populate the URL field.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {string} embedUrl - The embed URL for the video\n * @param {string} videoId - The video ID\n */\n selectIframeLibraryVideo(root, embedUrl, videoId) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Populate the URL field\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n urlInput.value = embedUrl;\n\n // Switch to the URL tab using our method\n this.switchToUrlTab(root);\n\n // Update the preview\n this.updatePreview(root);\n\n // Store the selected video\n this.selectedLibraryVideo = { embedUrl, videoId };\n }\n}\n"],"names":["constructor","editor","parseInput","input","trim","iframeMatch","match","this","parseEmbedUrl","startsWith","parseVideoUrl","url","urlObj","URL","baseUrl","protocol","host","pathname","searchParams","has","videoId","get","isEmbed","tParam","showTitle","linkTitle","showRelated","showUserAvatar","startAt","secondsToTimeString","parseInt","rawUrl","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","includes","parts","split","isNaN","buildEmbedUrl","parsed","options","set","startAtEnabled","data","editorData","autoConvertOptions","getDefault","key","fallback","_this","isUpdating","undefined","elementid","getElement","id","isupdating","responsive","width","height","is16_9","aspectRatio","is4_3","is1_1","isCustom","selectedIframe","getSelectedIframe","getCurrentIframeData","iframeLibraryLoaded","currentModal","IframeModal","create","title","component","templateContext","getTemplateContext","registerEventListeners","node","selection","getNode","nodeName","toLowerCase","iframe","querySelector","wrapper","closest","src","getAttribute","isResponsive","getFormValues","root","form","Selectors","IFRAME","elements","value","checked","values","embedUrl","aspectRatioCalcs","custom","context","aspectRatioCalc","aspectRatioValue","html","Templates","renderForPromise","updateUrlField","previewContainer","preview","urlWarning","innerHTML","classList","add","remove","previewWidth","min","scale","previewHeight","round","handleInputChange","clearTimeout","debounceTimer","setTimeout","updatePreview","handleAspectRatioChange","dimensions","aspectRatios","modal","getRoot","generateIframeHtml","outerHTML","fire","insertContent","confirmMessage","window","confirm","hide","getBody","$root","addEventListener","forEach","selector","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","removeBtn","actions","handleRemove","iframeLibraryTabBtn","tabIframeLibraryBtn","preventDefault","stopPropagation","switchToIframeLibraryTab","handleIframeLibraryTabClick","$iframeLibraryTabBtn","jQuery","urlTabBtn","tabUrlBtn","switchToUrlTab","registerIframeLibraryEventListeners","urlPane","paneUrl","iframeLibraryPane","paneIframeLibrary","setAttribute","event","handleIframeLibraryMessage","pane","iframeEl","iframeLibraryFrame","console","log","loadIframeLibrary","ltiConfig","contentItemUrl","loadIframeLibraryViaLti","loadIframeLibraryStatic","placeholderEl","iframeLibraryPlaceholder","loadingEl","iframeLibraryLoading","handleIframeLibraryLoad","iframeLibraryUrl","loadHandler","removeEventListener","origin","type","selectIframeLibraryVideo","messageType","action","mediaId","contentItems","content_items","length","item","embed_url","selectedLibraryVideo"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"iframeembed.min.js","sources":["../src/iframeembed.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Media2 Iframe Embed class.\n *\n * @module tiny_mediacms/iframeembed\n * @copyright 2024\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport { getString } from 'core/str';\nimport * as ModalEvents from 'core/modal_events';\nimport { component } from './common';\nimport IframeModal from './iframemodal';\nimport Selectors from './selectors';\nimport { getLti, getData } from './options';\n\nexport default class IframeEmbed {\n editor = null;\n currentModal = null;\n isUpdating = false;\n selectedIframe = null;\n debounceTimer = null;\n // Iframe library state\n iframeLibraryLoaded = false;\n selectedLibraryVideo = null;\n iframeLibraryUrl =\n 'https://temp.web357.com/mediacms/deic-mediacms-embed-videos.html';\n\n constructor(editor) {\n this.editor = editor;\n }\n\n /**\n * Parse input to extract video URL or iframe src.\n * Handles: iframe embed code, view URL, or embed URL.\n *\n * @param {string} input - The user input (URL or embed code)\n * @returns {Object|null} Parsed URL info or null if invalid\n */\n parseInput(input) {\n if (!input || !input.trim()) {\n return null;\n }\n\n input = input.trim();\n\n // Check if it's an iframe embed code\n const iframeMatch = input.match(\n /]*src=[\"']([^\"']+)[\"'][^>]*>/i,\n );\n if (iframeMatch) {\n return this.parseEmbedUrl(iframeMatch[1]);\n }\n\n // Check if it's a URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return this.parseVideoUrl(input);\n }\n\n return null;\n }\n\n /**\n * Parse a video view URL and convert to embed format.\n *\n * @param {string} url - The video URL\n * @returns {Object|null} Parsed info\n */\n parseVideoUrl(url) {\n try {\n const urlObj = new URL(url);\n const baseUrl = `${urlObj.protocol}//${urlObj.host}`;\n\n // MediaCMS view URL: /view?m=VIDEO_ID\n if (urlObj.pathname === '/view' && urlObj.searchParams.has('m')) {\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: false,\n };\n }\n\n // MediaCMS embed URL: /embed?m=VIDEO_ID&options\n if (urlObj.pathname === '/embed' && urlObj.searchParams.has('m')) {\n const tParam = urlObj.searchParams.get('t');\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: true,\n showTitle: urlObj.searchParams.get('showTitle') === '1',\n linkTitle: urlObj.searchParams.get('linkTitle') === '1',\n showRelated: urlObj.searchParams.get('showRelated') === '1',\n showUserAvatar:\n urlObj.searchParams.get('showUserAvatar') === '1',\n startAt: tParam\n ? this.secondsToTimeString(parseInt(tParam))\n : null,\n };\n }\n\n // Generic URL - just use as-is\n return {\n baseUrl: baseUrl,\n rawUrl: url,\n isGeneric: true,\n };\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Parse an embed URL.\n *\n * @param {string} url - The embed URL\n * @returns {Object|null} Parsed info\n */\n parseEmbedUrl(url) {\n return this.parseVideoUrl(url);\n }\n\n /**\n * Convert seconds to time string (M:SS format).\n *\n * @param {number} seconds - Time in seconds\n * @returns {string} Time string\n */\n secondsToTimeString(seconds) {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n /**\n * Convert time string to seconds.\n *\n * @param {string} timeStr - Time string (M:SS or just seconds)\n * @returns {number|null} Seconds or null if invalid\n */\n timeStringToSeconds(timeStr) {\n if (!timeStr || !timeStr.trim()) {\n return null;\n }\n timeStr = timeStr.trim();\n\n // Handle M:SS format\n if (timeStr.includes(':')) {\n const parts = timeStr.split(':');\n const mins = parseInt(parts[0]) || 0;\n const secs = parseInt(parts[1]) || 0;\n return mins * 60 + secs;\n }\n\n // Handle plain seconds\n const secs = parseInt(timeStr);\n return isNaN(secs) ? null : secs;\n }\n\n /**\n * Build the embed URL with options.\n *\n * @param {Object} parsed - Parsed URL info\n * @param {Object} options - Embed options\n * @returns {string} The complete embed URL\n */\n buildEmbedUrl(parsed, options) {\n if (parsed.isGeneric) {\n return parsed.rawUrl;\n }\n\n const url = new URL(`${parsed.baseUrl}/embed`);\n url.searchParams.set('m', parsed.videoId);\n\n // Always include all options with 1 or 0\n url.searchParams.set('showTitle', options.showTitle ? '1' : '0');\n url.searchParams.set('showRelated', options.showRelated ? '1' : '0');\n url.searchParams.set(\n 'showUserAvatar',\n options.showUserAvatar ? '1' : '0',\n );\n url.searchParams.set('linkTitle', options.linkTitle ? '1' : '0');\n\n // Add start time if enabled\n if (options.startAtEnabled && options.startAt) {\n const seconds = this.timeStringToSeconds(options.startAt);\n if (seconds !== null && seconds > 0) {\n url.searchParams.set('t', seconds.toString());\n }\n }\n\n return url.toString();\n }\n\n /**\n * Get the template context for the modal.\n *\n * @param {Object} data - Existing data for updating\n * @returns {Object} Template context\n */\n async getTemplateContext(data = {}) {\n // Get admin settings for default checkbox values.\n const editorData = getData(this.editor);\n const autoConvertOptions = editorData?.autoConvertOptions || {};\n\n // Use admin settings as defaults when creating new embed (not updating).\n // When updating, use the values from the existing iframe.\n const getDefault = (key, fallback = true) => {\n if (this.isUpdating && data[key] !== undefined) {\n return data[key];\n }\n return autoConvertOptions[key] !== undefined\n ? autoConvertOptions[key]\n : fallback;\n };\n\n return {\n elementid: this.editor.getElement().id,\n isupdating: this.isUpdating,\n url: data.url || '',\n showTitle: getDefault('showTitle'),\n linkTitle: getDefault('linkTitle'),\n showRelated: getDefault('showRelated'),\n showUserAvatar: getDefault('showUserAvatar'),\n responsive: data.responsive !== false,\n startAtEnabled: data.startAtEnabled || false,\n startAt: data.startAt || '0:00',\n width: data.width || 560,\n height: data.height || 315,\n is16_9: !data.aspectRatio || data.aspectRatio === '16:9',\n is4_3: data.aspectRatio === '4:3',\n is1_1: data.aspectRatio === '1:1',\n isCustom: data.aspectRatio === 'custom',\n };\n }\n\n /**\n * Display the iframe embed dialog.\n */\n async displayDialogue() {\n this.selectedIframe = this.getSelectedIframe();\n const data = this.getCurrentIframeData();\n this.isUpdating = data !== null;\n\n // Reset iframe library state for new modal\n this.iframeLibraryLoaded = false;\n\n this.currentModal = await IframeModal.create({\n title: getString('iframemodaltitle', component),\n templateContext: await this.getTemplateContext(data || {}),\n });\n\n await this.registerEventListeners(this.currentModal);\n }\n\n /**\n * Get the currently selected iframe in the editor.\n *\n * @returns {HTMLElement|null} The iframe element or null\n */\n getSelectedIframe() {\n const node = this.editor.selection.getNode();\n\n if (node.nodeName.toLowerCase() === 'iframe') {\n return node;\n }\n\n // Check if selection contains an iframe wrapper (including overlay wrapper)\n const iframe = node.querySelector('iframe');\n if (iframe) {\n return iframe;\n }\n\n // Check if we're on the overlay or wrapper and need to find the iframe\n const wrapper =\n node.closest('.tiny-mediacms-iframe-wrapper') ||\n node.closest('.tiny-iframe-responsive');\n if (wrapper) {\n return wrapper.querySelector('iframe');\n }\n\n return null;\n }\n\n /**\n * Get current iframe data for editing.\n *\n * @returns {Object|null} Current iframe data or null\n */\n getCurrentIframeData() {\n if (!this.selectedIframe) {\n return null;\n }\n\n const src = this.selectedIframe.getAttribute('src');\n const parsed = this.parseInput(src);\n\n // Check if responsive by looking at style\n const style = this.selectedIframe.getAttribute('style') || '';\n const isResponsive = style.includes('aspect-ratio');\n\n return {\n url: src,\n width: this.selectedIframe.getAttribute('width') || 560,\n height: this.selectedIframe.getAttribute('height') || 315,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\n responsive: isResponsive,\n startAtEnabled: parsed?.startAt !== null,\n startAt: parsed?.startAt || '0:00',\n };\n }\n\n /**\n * Get form values from the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @returns {Object} Form values\n */\n getFormValues(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n return {\n url: form.querySelector(Selectors.IFRAME.elements.url).value.trim(),\n showTitle: form.querySelector(Selectors.IFRAME.elements.showTitle)\n .checked,\n linkTitle: form.querySelector(Selectors.IFRAME.elements.linkTitle)\n .checked,\n showRelated: form.querySelector(\n Selectors.IFRAME.elements.showRelated,\n ).checked,\n showUserAvatar: form.querySelector(\n Selectors.IFRAME.elements.showUserAvatar,\n ).checked,\n responsive: form.querySelector(Selectors.IFRAME.elements.responsive)\n .checked,\n startAtEnabled: form.querySelector(\n Selectors.IFRAME.elements.startAtEnabled,\n ).checked,\n startAt: form\n .querySelector(Selectors.IFRAME.elements.startAt)\n .value.trim(),\n aspectRatio: form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).value,\n width:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.width).value,\n ) || 560,\n height:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.height).value,\n ) || 315,\n };\n }\n\n /**\n * Generate the iframe HTML.\n *\n * @param {Object} values - Form values\n * @returns {Promise} Generated HTML\n */\n async generateIframeHtml(values) {\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n return '';\n }\n\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Calculate aspect ratio values for CSS\n const aspectRatioCalcs = {\n '16:9': '16 / 9',\n '4:3': '4 / 3',\n '1:1': '1 / 1',\n custom: `${values.width} / ${values.height}`,\n };\n\n const context = {\n src: embedUrl,\n width: values.width,\n height: values.height,\n responsive: values.responsive,\n aspectRatioCalc: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n aspectRatioValue: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n };\n\n const { html } = await Templates.renderForPromise(\n 'tiny_mediacms/iframe_embed_output',\n context,\n );\n return html;\n }\n\n /**\n * Update the preview in the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field with new embed options\n */\n async updatePreview(root, updateUrlField = false) {\n const values = this.getFormValues(root);\n const previewContainer = root.querySelector(\n Selectors.IFRAME.elements.preview,\n );\n const urlWarning = root.querySelector(\n Selectors.IFRAME.elements.urlWarning,\n );\n\n if (!values.url) {\n previewContainer.innerHTML =\n 'Enter a video URL to see preview';\n urlWarning.classList.add('d-none');\n return;\n }\n\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n previewContainer.innerHTML =\n 'Invalid URL format';\n urlWarning.classList.remove('d-none');\n return;\n }\n\n urlWarning.classList.add('d-none');\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Update the URL field if requested (when embed options change)\n if (updateUrlField && !parsed.isGeneric) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n urlInput.value = embedUrl;\n }\n\n // Show a scaled preview\n const previewWidth = Math.min(values.width, 400);\n const scale = previewWidth / values.width;\n const previewHeight = Math.round(values.height * scale);\n\n previewContainer.innerHTML = `\n \n `;\n }\n\n /**\n * Handle form input changes with debounce.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field\n */\n handleInputChange(root, updateUrlField = false) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n this.updatePreview(root, updateUrlField);\n }, 500);\n }\n\n /**\n * Handle aspect ratio change - update dimensions.\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleAspectRatioChange(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).value;\n const dimensions = Selectors.IFRAME.aspectRatios[aspectRatio];\n\n // Only update dimensions for predefined ratios, not 'custom'\n if (dimensions && aspectRatio !== 'custom') {\n form.querySelector(Selectors.IFRAME.elements.width).value =\n dimensions.width;\n form.querySelector(Selectors.IFRAME.elements.height).value =\n dimensions.height;\n }\n\n this.updatePreview(root);\n }\n\n /**\n * Handle dialog submission.\n *\n * @param {Object} modal - The modal instance\n */\n async handleDialogueSubmission(modal) {\n const root = modal.getRoot()[0];\n const values = this.getFormValues(root);\n\n if (!values.url) {\n return;\n }\n\n const html = await this.generateIframeHtml(values);\n if (html) {\n if (this.isUpdating && this.selectedIframe) {\n // Replace existing iframe (including wrapper if present)\n // Check for both old wrapper and new overlay wrapper\n const wrapper =\n this.selectedIframe.closest(\n '.tiny-mediacms-iframe-wrapper',\n ) || this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.outerHTML = html;\n } else {\n this.selectedIframe.outerHTML = html;\n }\n this.isUpdating = false;\n // Fire change event to trigger overlay reprocessing\n this.editor.fire('Change');\n } else {\n this.editor.insertContent(html);\n }\n }\n }\n\n /**\n * Handle video removal from the editor.\n *\n * @param {Object} modal - The modal instance\n */\n async handleRemove(modal) {\n // Get confirmation string\n const confirmMessage = await getString(\n 'removeiframeconfirm',\n component,\n );\n\n // Show confirmation dialog\n // eslint-disable-next-line no-alert\n if (!window.confirm(confirmMessage)) {\n return;\n }\n\n if (this.selectedIframe) {\n // Remove the iframe (including wrapper if present)\n const wrapper =\n this.selectedIframe.closest('.tiny-mediacms-iframe-wrapper') ||\n this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.remove();\n } else {\n this.selectedIframe.remove();\n }\n }\n\n // Close the modal\n this.isUpdating = false;\n modal.hide();\n }\n\n /**\n * Register event listeners for the modal.\n *\n * @param {Object} modal - The modal instance\n */\n async registerEventListeners(modal) {\n await modal.getBody();\n const $root = modal.getRoot();\n const root = $root[0];\n\n // Input change handlers\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // URL input\n form.querySelector(Selectors.IFRAME.elements.url).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Embed option checkboxes - these should update the URL field when changed\n [\n Selectors.IFRAME.elements.showTitle,\n Selectors.IFRAME.elements.linkTitle,\n Selectors.IFRAME.elements.showRelated,\n Selectors.IFRAME.elements.showUserAvatar,\n Selectors.IFRAME.elements.startAtEnabled,\n ].forEach((selector) => {\n form.querySelector(selector).addEventListener('change', () =>\n this.handleInputChange(root, true), // Update URL field when embed options change\n );\n });\n\n // Responsive checkbox - doesn't affect URL, only display\n form.querySelector(Selectors.IFRAME.elements.responsive).addEventListener('change', () =>\n this.handleInputChange(root, false),\n );\n\n // Start at time input - should update URL field\n form.querySelector(Selectors.IFRAME.elements.startAt).addEventListener(\n 'input',\n () => this.handleInputChange(root, true),\n );\n\n // Aspect ratio change\n form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).addEventListener('change', () => this.handleAspectRatioChange(root));\n\n // Dimension inputs\n form.querySelector(Selectors.IFRAME.elements.width).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n form.querySelector(Selectors.IFRAME.elements.height).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Modal events\n $root.on(ModalEvents.save, () => this.handleDialogueSubmission(modal));\n $root.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n\n // Remove button handler (only present when updating)\n const removeBtn = root.querySelector(Selectors.IFRAME.actions.remove);\n if (removeBtn) {\n removeBtn.addEventListener('click', () => this.handleRemove(modal));\n }\n\n // Initial preview if we have a URL\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n if (urlInput.value) {\n this.updatePreview(root);\n }\n\n // Tab change handler - load iframe library when switching to iframe library tab\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching for Bootstrap 5\n this.switchToIframeLibraryTab(root);\n\n // Small delay to ensure tab pane is visible before loading iframe\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n });\n // Also handle Bootstrap tab events (if Bootstrap handles it)\n iframeLibraryTabBtn.addEventListener('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n // jQuery Bootstrap 4 event\n const $iframeLibraryTabBtn = window.jQuery\n ? window.jQuery(iframeLibraryTabBtn)\n : null;\n if ($iframeLibraryTabBtn) {\n $iframeLibraryTabBtn.on('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n }\n }\n\n // Tab change handler for URL tab\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n if (urlTabBtn) {\n urlTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching\n this.switchToUrlTab(root);\n });\n }\n\n // Iframe library event listeners\n this.registerIframeLibraryEventListeners(root);\n\n // Since My Media tab is now default/active, load it immediately on modal open\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n }\n\n /**\n * Switch to the URL tab.\n *\n * @param {HTMLElement} root - Modal root element\n */\n switchToUrlTab(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Get tab buttons\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n\n // Get tab panes\n const urlPane = form.querySelector(Selectors.IFRAME.elements.paneUrl);\n const iframeLibraryPane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n // Update tab button states\n if (urlTabBtn) {\n urlTabBtn.classList.add('active');\n urlTabBtn.setAttribute('aria-selected', 'true');\n }\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.classList.remove('active');\n iframeLibraryTabBtn.setAttribute('aria-selected', 'false');\n }\n\n // Update tab pane visibility\n if (urlPane) {\n urlPane.classList.add('show', 'active');\n }\n if (iframeLibraryPane) {\n iframeLibraryPane.classList.remove('show', 'active');\n }\n }\n\n /**\n * Switch to the iframe library tab.\n *\n * @param {HTMLElement} root - Modal root element\n */\n switchToIframeLibraryTab(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Get tab buttons\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n\n // Get tab panes\n const urlPane = form.querySelector(Selectors.IFRAME.elements.paneUrl);\n const iframeLibraryPane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n // Update tab button states\n if (urlTabBtn) {\n urlTabBtn.classList.remove('active');\n urlTabBtn.setAttribute('aria-selected', 'false');\n }\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.classList.add('active');\n iframeLibraryTabBtn.setAttribute('aria-selected', 'true');\n }\n\n // Update tab pane visibility\n if (urlPane) {\n urlPane.classList.remove('show', 'active');\n }\n if (iframeLibraryPane) {\n iframeLibraryPane.classList.add('show', 'active');\n }\n }\n\n /**\n * Register event listeners for the iframe library.\n *\n * @param {HTMLElement} root - Modal root element\n */\n registerIframeLibraryEventListeners(root) {\n // Listen for messages from the iframe (for video selection)\n window.addEventListener('message', (event) => {\n this.handleIframeLibraryMessage(root, event);\n });\n }\n\n /**\n * Handle iframe library tab click - always refetch content (no caching).\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleIframeLibraryTabClick(root) {\n // Always refetch content when tab is clicked (no caching)\n // Reset the loaded state to ensure fresh content is fetched\n this.iframeLibraryLoaded = false;\n this.loadIframeLibrary(root);\n }\n\n /**\n * Load the iframe library using LTI flow or fallback to static URL.\n *\n * @param {HTMLElement} root - Modal root element\n */\n loadIframeLibrary(root) {\n const ltiConfig = getLti(this.editor);\n // Check if LTI is configured with a content item URL\n if (ltiConfig?.contentItemUrl) {\n this.loadIframeLibraryViaLti(root);\n } else {\n // Fallback to static URL if LTI not configured\n this.loadIframeLibraryStatic(root);\n }\n }\n\n /**\n * Load the iframe library via LTI Deep Linking (Content Item Selection).\n * Sets the iframe src to contentitem.php which initiates the LTI Deep Linking flow.\n * This sends an LtiDeepLinkingRequest message, which will redirect to the\n * tool's content selection interface (e.g., /lti/select-media/).\n *\n * @param {HTMLElement} root - Modal root element\n */\n loadIframeLibraryViaLti(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n if (!pane) { return;\n }\n\n const placeholderEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryPlaceholder,\n );\n const loadingEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryLoading,\n );\n const iframeEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryFrame,\n );\n\n if (!iframeEl) { return;\n }\n\n // Hide placeholder, show loading state\n if (placeholderEl) {\n placeholderEl.classList.add('d-none');\n }\n if (loadingEl) {\n loadingEl.classList.remove('d-none');\n }\n iframeEl.classList.add('d-none');\n\n // Set up load listener - note: this may fire multiple times during LTI redirects\n const loadHandler = () => { this.handleIframeLibraryLoad(root);\n };\n iframeEl.addEventListener('load', loadHandler);\n\n // Set the iframe src to the content item URL\n // This initiates the LTI Deep Linking flow:\n // 1. contentitem.php initiates OIDC login\n // 2. LTI provider authenticates\n // 3. Moodle sends LtiDeepLinkingRequest\n // 4. Tool provider shows content selection interface\n const ltiConfig = getLti(this.editor);\n iframeEl.src = ltiConfig.contentItemUrl;\n }\n\n /**\n * Load the iframe library using static URL (fallback).\n *\n * @param {HTMLElement} root - Modal root element\n */\n loadIframeLibraryStatic(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n if (!pane) { return;\n }\n\n const placeholderEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryPlaceholder,\n );\n const loadingEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryLoading,\n );\n const iframeEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryFrame,\n );\n if (!iframeEl) { return;\n }\n\n // Hide placeholder, show loading state\n if (placeholderEl) {\n placeholderEl.classList.add('d-none');\n }\n if (loadingEl) {\n loadingEl.classList.remove('d-none');\n }\n iframeEl.classList.add('d-none');\n\n // Set up load listener before setting src\n const loadHandler = () => { // Only handle if the src matches our target URL\n if (iframeEl.src === this.iframeLibraryUrl) {\n this.handleIframeLibraryLoad(root);\n // Remove the listener after successful load\n iframeEl.removeEventListener('load', loadHandler);\n }\n };\n iframeEl.addEventListener('load', loadHandler);\n\n // Set the iframe source\n iframeEl.src = this.iframeLibraryUrl; }\n\n /**\n * Handle iframe library load event.\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleIframeLibraryLoad(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const pane = form.querySelector(\n Selectors.IFRAME.elements.paneIframeLibrary,\n );\n\n if (!pane) {\n return;\n }\n\n const placeholderEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryPlaceholder,\n );\n const loadingEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryLoading,\n );\n const iframeEl = pane.querySelector(\n Selectors.IFRAME.elements.iframeLibraryFrame,\n );\n\n // Hide placeholder and loading, show iframe\n if (placeholderEl) {\n placeholderEl.classList.add('d-none');\n }\n if (loadingEl) {\n loadingEl.classList.add('d-none');\n }\n if (iframeEl) {\n iframeEl.classList.remove('d-none');\n }\n\n this.iframeLibraryLoaded = true;\n }\n\n /**\n * Handle messages from the iframe library (for video selection).\n * Supports both custom videoSelected messages and LTI Deep Linking responses.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {MessageEvent} event - The message event\n */\n handleIframeLibraryMessage(root, event) {\n const data = event.data;\n\n if (!data) {\n return;\n }\n\n // Handle custom videoSelected message format\n if (data.type === 'videoSelected' && data.embedUrl) {\n this.selectIframeLibraryVideo(root, data.embedUrl, data.videoId);\n return;\n }\n\n // Handle LTI Deep Linking response format\n if (\n data.type === 'ltiDeepLinkingResponse' ||\n data.messageType === 'LtiDeepLinkingResponse'\n ) { const contentItems = data.content_items || data.contentItems || [];\n if (contentItems.length > 0) {\n const item = contentItems[0];\n // Extract embed URL from the content item\n const embedUrl =\n item.url || item.embed_url || item.embedUrl || '';\n const videoId = item.id || item.mediaId || '';\n if (embedUrl) { this.selectIframeLibraryVideo(root, embedUrl, videoId);\n }\n }\n return;\n }\n\n // Handle MediaCMS specific message format (if different from above)\n if (data.action === 'selectMedia' || data.action === 'mediaSelected') {\n const embedUrl = data.embedUrl || data.url || '';\n const videoId = data.mediaId || data.videoId || data.id || '';\n if (embedUrl) { this.selectIframeLibraryVideo(root, embedUrl, videoId);\n }\n return;\n }\n }\n\n /**\n * Select a video from the iframe library and populate the URL field.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {string} embedUrl - The embed URL for the video\n * @param {string} videoId - The video ID\n */\n selectIframeLibraryVideo(root, embedUrl, videoId) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Populate the URL field\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n urlInput.value = embedUrl;\n\n // Show the Configure tab (it starts hidden)\n const configureTabItem = root.querySelector('.tiny_iframecms_tab_url_item');\n if (configureTabItem) {\n configureTabItem.style.display = '';\n }\n\n // Switch to the Configure tab to show embed options\n this.switchToUrlTab(root);\n\n // Update the preview\n this.updatePreview(root);\n\n // Store the selected video\n this.selectedLibraryVideo = { embedUrl, videoId };\n }\n}\n"],"names":["constructor","editor","parseInput","input","trim","iframeMatch","match","this","parseEmbedUrl","startsWith","parseVideoUrl","url","urlObj","URL","baseUrl","protocol","host","pathname","searchParams","has","videoId","get","isEmbed","tParam","showTitle","linkTitle","showRelated","showUserAvatar","startAt","secondsToTimeString","parseInt","rawUrl","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","includes","parts","split","isNaN","buildEmbedUrl","parsed","options","set","startAtEnabled","data","editorData","autoConvertOptions","getDefault","key","fallback","_this","isUpdating","undefined","elementid","getElement","id","isupdating","responsive","width","height","is16_9","aspectRatio","is4_3","is1_1","isCustom","selectedIframe","getSelectedIframe","getCurrentIframeData","iframeLibraryLoaded","currentModal","IframeModal","create","title","component","templateContext","getTemplateContext","registerEventListeners","node","selection","getNode","nodeName","toLowerCase","iframe","querySelector","wrapper","closest","src","getAttribute","isResponsive","getFormValues","root","form","Selectors","IFRAME","elements","value","checked","values","embedUrl","aspectRatioCalcs","custom","context","aspectRatioCalc","aspectRatioValue","html","Templates","renderForPromise","updateUrlField","previewContainer","preview","urlWarning","innerHTML","classList","add","remove","previewWidth","min","scale","previewHeight","round","handleInputChange","clearTimeout","debounceTimer","setTimeout","updatePreview","handleAspectRatioChange","dimensions","aspectRatios","modal","getRoot","generateIframeHtml","outerHTML","fire","insertContent","confirmMessage","window","confirm","hide","getBody","$root","addEventListener","forEach","selector","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","removeBtn","actions","handleRemove","iframeLibraryTabBtn","tabIframeLibraryBtn","preventDefault","stopPropagation","switchToIframeLibraryTab","handleIframeLibraryTabClick","$iframeLibraryTabBtn","jQuery","urlTabBtn","tabUrlBtn","switchToUrlTab","registerIframeLibraryEventListeners","urlPane","paneUrl","iframeLibraryPane","paneIframeLibrary","setAttribute","event","handleIframeLibraryMessage","loadIframeLibrary","ltiConfig","contentItemUrl","loadIframeLibraryViaLti","loadIframeLibraryStatic","pane","placeholderEl","iframeLibraryPlaceholder","loadingEl","iframeLibraryLoading","iframeEl","iframeLibraryFrame","handleIframeLibraryLoad","loadHandler","iframeLibraryUrl","removeEventListener","type","selectIframeLibraryVideo","messageType","action","mediaId","contentItems","content_items","length","item","embed_url","configureTabItem","style","display","selectedLibraryVideo"],"mappings":"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"} \ No newline at end of file diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js index 9f7b41c4..2c5a97b4 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js @@ -694,6 +694,9 @@ export default class IframeEmbed { // Iframe library event listeners this.registerIframeLibraryEventListeners(root); + + // Since My Media tab is now default/active, load it immediately on modal open + setTimeout(() => this.handleIframeLibraryTabClick(root), 100); } /** @@ -796,24 +799,6 @@ export default class IframeEmbed { * @param {HTMLElement} root - Modal root element */ handleIframeLibraryTabClick(root) { - const form = root.querySelector(Selectors.IFRAME.elements.form); - const pane = form.querySelector( - Selectors.IFRAME.elements.paneIframeLibrary, - ); - const iframeEl = pane - ? pane.querySelector(Selectors.IFRAME.elements.iframeLibraryFrame) - : null; - - // eslint-disable-next-line no-console - console.log( - 'handleIframeLibraryTabClick called, iframeLibraryLoaded:', - this.iframeLibraryLoaded, - 'iframe src:', - iframeEl ? iframeEl.src : 'no iframe', - 'pane:', - pane, - ); - // Always refetch content when tab is clicked (no caching) // Reset the loaded state to ensure fresh content is fetched this.iframeLibraryLoaded = false; @@ -827,13 +812,9 @@ export default class IframeEmbed { */ loadIframeLibrary(root) { const ltiConfig = getLti(this.editor); - - // eslint-disable-next-line no-console - console.log('loadIframeLibrary called, LTI config:', ltiConfig); - // Check if LTI is configured with a content item URL if (ltiConfig?.contentItemUrl) { - this.loadIframeLibraryViaLti(root, ltiConfig); + this.loadIframeLibraryViaLti(root); } else { // Fallback to static URL if LTI not configured this.loadIframeLibraryStatic(root); @@ -847,21 +828,14 @@ export default class IframeEmbed { * tool's content selection interface (e.g., /lti/select-media/). * * @param {HTMLElement} root - Modal root element - * @param {Object} ltiConfig - LTI configuration */ - loadIframeLibraryViaLti(root, ltiConfig) { - // eslint-disable-next-line no-console - console.log('loadIframeLibraryViaLti called, config:', ltiConfig); - + loadIframeLibraryViaLti(root) { const form = root.querySelector(Selectors.IFRAME.elements.form); const pane = form.querySelector( Selectors.IFRAME.elements.paneIframeLibrary, ); - if (!pane) { - // eslint-disable-next-line no-console - console.log('paneIframeLibrary not found!'); - return; + if (!pane) { return; } const placeholderEl = pane.querySelector( @@ -874,10 +848,7 @@ export default class IframeEmbed { Selectors.IFRAME.elements.iframeLibraryFrame, ); - if (!iframeEl) { - // eslint-disable-next-line no-console - console.log('iframeEl not found!'); - return; + if (!iframeEl) { return; } // Hide placeholder, show loading state @@ -890,10 +861,7 @@ export default class IframeEmbed { iframeEl.classList.add('d-none'); // Set up load listener - note: this may fire multiple times during LTI redirects - const loadHandler = () => { - // eslint-disable-next-line no-console - console.log('LTI iframe loaded'); - this.handleIframeLibraryLoad(root); + const loadHandler = () => { this.handleIframeLibraryLoad(root); }; iframeEl.addEventListener('load', loadHandler); @@ -902,12 +870,8 @@ export default class IframeEmbed { // 1. contentitem.php initiates OIDC login // 2. LTI provider authenticates // 3. Moodle sends LtiDeepLinkingRequest - // 4. Tool provider shows content selection interface (e.g., /lti/select-media/) - // eslint-disable-next-line no-console - console.log( - 'Setting iframe src to LTI content item URL:', - ltiConfig.contentItemUrl, - ); + // 4. Tool provider shows content selection interface + const ltiConfig = getLti(this.editor); iframeEl.src = ltiConfig.contentItemUrl; } @@ -917,21 +881,12 @@ export default class IframeEmbed { * @param {HTMLElement} root - Modal root element */ loadIframeLibraryStatic(root) { - // eslint-disable-next-line no-console - console.log( - 'loadIframeLibraryStatic called, URL:', - this.iframeLibraryUrl, - ); - const form = root.querySelector(Selectors.IFRAME.elements.form); const pane = form.querySelector( Selectors.IFRAME.elements.paneIframeLibrary, ); - if (!pane) { - // eslint-disable-next-line no-console - console.log('paneIframeLibrary not found!'); - return; + if (!pane) { return; } const placeholderEl = pane.querySelector( @@ -943,14 +898,7 @@ export default class IframeEmbed { const iframeEl = pane.querySelector( Selectors.IFRAME.elements.iframeLibraryFrame, ); - - // eslint-disable-next-line no-console - console.log('Elements found:', { placeholderEl, loadingEl, iframeEl }); - - if (!iframeEl) { - // eslint-disable-next-line no-console - console.log('iframeEl not found!'); - return; + if (!iframeEl) { return; } // Hide placeholder, show loading state @@ -963,10 +911,7 @@ export default class IframeEmbed { iframeEl.classList.add('d-none'); // Set up load listener before setting src - const loadHandler = () => { - // eslint-disable-next-line no-console - console.log('iframe loaded, src:', iframeEl.src); - // Only handle if the src matches our target URL + const loadHandler = () => { // Only handle if the src matches our target URL if (iframeEl.src === this.iframeLibraryUrl) { this.handleIframeLibraryLoad(root); // Remove the listener after successful load @@ -976,10 +921,7 @@ export default class IframeEmbed { iframeEl.addEventListener('load', loadHandler); // Set the iframe source - iframeEl.src = this.iframeLibraryUrl; - // eslint-disable-next-line no-console - console.log('iframe src set to:', iframeEl.src); - } + iframeEl.src = this.iframeLibraryUrl; } /** * Handle iframe library load event. @@ -987,9 +929,6 @@ export default class IframeEmbed { * @param {HTMLElement} root - Modal root element */ handleIframeLibraryLoad(root) { - // eslint-disable-next-line no-console - console.log('handleIframeLibraryLoad called'); - const form = root.querySelector(Selectors.IFRAME.elements.form); const pane = form.querySelector( Selectors.IFRAME.elements.paneIframeLibrary, @@ -1031,29 +970,14 @@ export default class IframeEmbed { * @param {MessageEvent} event - The message event */ handleIframeLibraryMessage(root, event) { - // eslint-disable-next-line no-console - console.log( - 'handleIframeLibraryMessage received:', - event.data, - 'from origin:', - event.origin, - ); - const data = event.data; if (!data) { return; } - // Handle custom videoSelected message format (from static iframe or custom MediaCMS implementation) + // Handle custom videoSelected message format if (data.type === 'videoSelected' && data.embedUrl) { - // eslint-disable-next-line no-console - console.log( - 'Video selected (videoSelected):', - data.embedUrl, - 'videoId:', - data.videoId, - ); this.selectIframeLibraryVideo(root, data.embedUrl, data.videoId); return; } @@ -1062,25 +986,14 @@ export default class IframeEmbed { if ( data.type === 'ltiDeepLinkingResponse' || data.messageType === 'LtiDeepLinkingResponse' - ) { - // eslint-disable-next-line no-console - console.log('LTI Deep Linking response received:', data); - const contentItems = data.content_items || data.contentItems || []; + ) { const contentItems = data.content_items || data.contentItems || []; if (contentItems.length > 0) { const item = contentItems[0]; // Extract embed URL from the content item const embedUrl = item.url || item.embed_url || item.embedUrl || ''; const videoId = item.id || item.mediaId || ''; - if (embedUrl) { - // eslint-disable-next-line no-console - console.log( - 'Video selected (LTI):', - embedUrl, - 'videoId:', - videoId, - ); - this.selectIframeLibraryVideo(root, embedUrl, videoId); + if (embedUrl) { this.selectIframeLibraryVideo(root, embedUrl, videoId); } } return; @@ -1090,15 +1003,7 @@ export default class IframeEmbed { if (data.action === 'selectMedia' || data.action === 'mediaSelected') { const embedUrl = data.embedUrl || data.url || ''; const videoId = data.mediaId || data.videoId || data.id || ''; - if (embedUrl) { - // eslint-disable-next-line no-console - console.log( - 'Video selected (mediaSelected):', - embedUrl, - 'videoId:', - videoId, - ); - this.selectIframeLibraryVideo(root, embedUrl, videoId); + if (embedUrl) { this.selectIframeLibraryVideo(root, embedUrl, videoId); } return; } @@ -1118,7 +1023,13 @@ export default class IframeEmbed { const urlInput = form.querySelector(Selectors.IFRAME.elements.url); urlInput.value = embedUrl; - // Switch to the URL tab using our method + // Show the Configure tab (it starts hidden) + const configureTabItem = root.querySelector('.tiny_iframecms_tab_url_item'); + if (configureTabItem) { + configureTabItem.style.display = ''; + } + + // Switch to the Configure tab to show embed options this.switchToUrlTab(root); // Update the preview diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php b/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php index 0463f9eb..f88ffaa4 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php @@ -132,15 +132,15 @@ $string['aspectratio_1_1'] = '1:1'; $string['aspectratio_custom'] = 'Custom'; $string['dimensions'] = 'Dimensions'; $string['preview'] = 'Preview'; -$string['insertiframe'] = 'Insert video'; +$string['insertiframe'] = 'Insert'; $string['updateiframe'] = 'Update video'; $string['removeiframe'] = 'Remove video'; $string['removeiframeconfirm'] = 'Are you sure you want to remove this video from the editor?'; // Iframe modal tabs. -$string['tabembedurl'] = 'Embed URL'; +$string['tabembedurl'] = 'Configure'; $string['tabvideolibrary'] = 'Video Library'; -$string['tabvideolibraryiframe'] = 'Media Library'; +$string['tabvideolibraryiframe'] = 'My Media'; // Video library strings. $string['librarysearchplaceholder'] = 'Search videos...'; diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache b/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache index 6726bc99..6096f12e 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache @@ -31,66 +31,28 @@
- -
-
-
- -
- -
- - -
- {{#str}} iframeurlinvalid, tiny_mediacms {{/str}} -
-
- - {{> tiny_mediacms/iframe_embed_options }} -
- - -
- -
-
- {{#str}} iframeurlplaceholder, tiny_mediacms {{/str}} -
-
-
-
-
-
- - -
+ +

{{#str}} libraryloading, tiny_mediacms {{/str}}

@@ -110,6 +72,38 @@
+ + +
+
+
+ +
+ + +
+ + {{> tiny_mediacms/iframe_embed_options }} +
+ + +
+ +
+
+ {{#str}} iframeurlplaceholder, tiny_mediacms {{/str}} +
+
+
+
+
+
{{/body}}