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 bd4dc10b..0c3303fd 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"),widthParam=urlObj.searchParams.get("width"),heightParam=urlObj.searchParams.get("height");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"),width:widthParam?parseInt(widthParam):null,height:heightParam?parseInt(heightParam):null,startAt:tParam?this.secondsToTimeString(parseInt(tParam)):null}}return urlObj.pathname.includes("/filter/mediacms/launch.php")&&urlObj.searchParams.has("token")?{baseUrl:baseUrl,videoId:urlObj.searchParams.get("token"),rawUrl:url,isLtiLaunch:!0}:{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}parseWidthHeight(value){if(!value)return null;const parsed=parseInt(value.trim());return isNaN(parsed)?null:parsed}buildEmbedUrl(parsed,options){if(parsed.isGeneric||parsed.isLtiLaunch)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.width&&url.searchParams.set("width",options.width.toString()),options.height&&url.searchParams.set("height",options.height.toString()),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};let width,height;return this.isUpdating&&(data.width||data.height)?(width=data.width||640,height=data.height||360):(width=640,height=360),{elementid:this.editor.getElement().id,isupdating:this.isUpdating,url:data.url||"",showTitle:getDefault("showTitle"),linkTitle:getDefault("linkTitle"),showRelated:getDefault("showRelated"),showUserAvatar:getDefault("showUserAvatar"),textLinkOnly:data.textLinkOnly||!1,startAtEnabled:data.startAtEnabled||!1,startAt:data.startAt||"0:00",width:width,height:height,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("a"===node.nodeName.toLowerCase()&&"true"===node.getAttribute("data-mediacms-textlink"))return node;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");if(wrapper)return wrapper.querySelector("iframe");const textLink=node.closest('a[data-mediacms-textlink="true"]');return textLink||null}getCurrentIframeData(){var _parsed$showTitle2,_parsed$linkTitle2,_parsed$showRelated2,_parsed$showUserAvata2;if(!this.selectedIframe)return null;if("a"===this.selectedIframe.nodeName.toLowerCase()&&"true"===this.selectedIframe.getAttribute("data-mediacms-textlink")){var _parsed$showTitle,_parsed$linkTitle,_parsed$showRelated,_parsed$showUserAvata;const href=this.selectedIframe.getAttribute("href"),parsed=this.parseInput(href);return{url:href,width:(null==parsed?void 0:parsed.width)||560,height:(null==parsed?void 0:parsed.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:!0,textLinkOnly:!0,startAtEnabled:null!==(null==parsed?void 0:parsed.startAt),startAt:(null==parsed?void 0:parsed.startAt)||"0:00"}}const src=this.selectedIframe.getAttribute("src"),parsed=this.parseInput(src);let width=(null==parsed?void 0:parsed.width)||this.selectedIframe.getAttribute("width")||null,height=(null==parsed?void 0:parsed.height)||this.selectedIframe.getAttribute("height")||null;return width=width?parseInt(width):null,height=height?parseInt(height):null,{url:src,width:width,height:height,showTitle:null===(_parsed$showTitle2=null==parsed?void 0:parsed.showTitle)||void 0===_parsed$showTitle2||_parsed$showTitle2,linkTitle:null===(_parsed$linkTitle2=null==parsed?void 0:parsed.linkTitle)||void 0===_parsed$linkTitle2||_parsed$linkTitle2,showRelated:null===(_parsed$showRelated2=null==parsed?void 0:parsed.showRelated)||void 0===_parsed$showRelated2||_parsed$showRelated2,showUserAvatar:null===(_parsed$showUserAvata2=null==parsed?void 0:parsed.showUserAvatar)||void 0===_parsed$showUserAvata2||_parsed$showUserAvata2,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,textLinkOnly:form.querySelector(_selectors.default.IFRAME.elements.textLinkOnly).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:this.parseWidthHeight(form.querySelector(_selectors.default.IFRAME.elements.width).value),height:this.parseWidthHeight(form.querySelector(_selectors.default.IFRAME.elements.height).value)}}async generateIframeHtml(values){const parsed=this.parseInput(values.url);if(!parsed)return"";if(values.textLinkOnly){let viewUrl;viewUrl=parsed.isGeneric||parsed.isLtiLaunch?parsed.rawUrl:"".concat(parsed.baseUrl,"/view?m=").concat(parsed.videoId);const linkText=(str=>{const div=document.createElement("div");return div.textContent=str,div.innerHTML})(viewUrl),hrefUrl=viewUrl.replace(/"/g,""");return'

').concat(linkText,"

")}const context={src:this.buildEmbedUrl(parsed,values),width:values.width,height:values.height},{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}if(values.textLinkOnly){let viewUrl;viewUrl=parsed.isGeneric||parsed.isLtiLaunch?parsed.rawUrl:"".concat(parsed.baseUrl,"/view?m=").concat(parsed.videoId);const linkText=(str=>{const div=document.createElement("div");return div.textContent=str,div.innerHTML})(viewUrl),hrefUrl=viewUrl.replace(/"/g,""");previewContainer.innerHTML='\n
\n Text link preview:
\n ').concat(linkText,'\n
This link will not be auto-converted by the MediaCMS filter.\n
\n ')}else{const previewWidth=Math.min(values.width,400),scale=previewWidth/values.width,previewHeight=Math.round(values.height*scale);previewContainer.innerHTML='\n \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)}handleWidthChange(root,newWidth){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,heightInput=form.querySelector(_selectors.default.IFRAME.elements.height);if("custom"!==aspectRatio&&newWidth){const width=parseInt(newWidth)||0,arr=aspectRatio.split(":"),x=parseInt(arr[0]),y=parseInt(arr[1]),calculatedHeight=Math.round(width*y/x);heightInput.value=calculatedHeight}this.handleInputChange(root)}handleHeightChange(root,newHeight){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,widthInput=form.querySelector(_selectors.default.IFRAME.elements.width);if("custom"!==aspectRatio&&newHeight){const height=parseInt(newHeight)||0,arr=aspectRatio.split(":"),x=parseInt(arr[0]),y=parseInt(arr[1]),calculatedWidth=Math.round(height*x/y);widthInput.value=calculatedWidth}this.handleInputChange(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"),paragraphWrapper=wrapper?wrapper.closest("p"):this.selectedIframe.closest("p");paragraphWrapper?paragraphWrapper.outerHTML=html:wrapper?wrapper.outerHTML=html:this.selectedIframe.outerHTML=html,this.isUpdating=!1,setTimeout((()=>{this.editor.getBody().querySelectorAll("p:empty, p:blank").forEach((p=>{""!==p.innerHTML.trim()&&"
"!==p.innerHTML||p.remove()}))}),10),this.editor.fire("Change")}else{const node=this.editor.selection.getNode();"P"===node.nodeName&&""===node.innerHTML.trim()?node.outerHTML=html: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.textLinkOnly).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",(e=>this.handleWidthChange(root,e.target.value))),form.querySelector(_selectors.default.IFRAME.elements.height).addEventListener("input",(e=>this.handleHeightChange(root,e.target.value))),$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),this.isUpdating?setTimeout((()=>this.updatePreview(root)),100):setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)}switchToUrlTab(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn),urlTabItem=form.querySelector(".tiny_iframecms_tab_url_item"),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabItem&&(urlTabItem.style.display=""),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),urlTabItem=form.querySelector(".tiny_iframecms_tab_url_item"),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabItem&&(urlTabItem.style.display="none"),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})); +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"),widthParam=urlObj.searchParams.get("width"),heightParam=urlObj.searchParams.get("height");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"),width:widthParam?parseInt(widthParam):null,height:heightParam?parseInt(heightParam):null,startAt:tParam?this.secondsToTimeString(parseInt(tParam)):null}}if(urlObj.pathname.includes("/filter/mediacms/launch.php")&&urlObj.searchParams.has("token")){const tParam=urlObj.searchParams.get("t"),widthParam=urlObj.searchParams.get("width"),heightParam=urlObj.searchParams.get("height");return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("token"),rawUrl:url,isLtiLaunch:!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"),width:widthParam?parseInt(widthParam):null,height:heightParam?parseInt(heightParam):null,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}parseWidthHeight(value){if(!value)return null;const parsed=parseInt(value.trim());return isNaN(parsed)?null:parsed}buildEmbedUrl(parsed,options){if(parsed.isGeneric)return parsed.rawUrl;let url;if(parsed.isLtiLaunch){url=new URL(parsed.rawUrl);const token=url.searchParams.get("token"),courseid=url.searchParams.get("courseid");url.search="",url.searchParams.set("token",token),courseid&&url.searchParams.set("courseid",courseid)}else url=new URL("".concat(parsed.baseUrl,"/embed")),url.searchParams.set("m",parsed.videoId);if(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.width&&url.searchParams.set("width",options.width.toString()),options.height&&url.searchParams.set("height",options.height.toString()),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};let width,height;return this.isUpdating&&(data.width||data.height)?(width=data.width||640,height=data.height||360):(width=640,height=360),{elementid:this.editor.getElement().id,isupdating:this.isUpdating,url:data.url||"",showTitle:getDefault("showTitle"),linkTitle:getDefault("linkTitle"),showRelated:getDefault("showRelated"),showUserAvatar:getDefault("showUserAvatar"),textLinkOnly:data.textLinkOnly||!1,startAtEnabled:data.startAtEnabled||!1,startAt:data.startAt||"0:00",width:width,height:height,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("a"===node.nodeName.toLowerCase()&&"true"===node.getAttribute("data-mediacms-textlink"))return node;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");if(wrapper)return wrapper.querySelector("iframe");const textLink=node.closest('a[data-mediacms-textlink="true"]');return textLink||null}getCurrentIframeData(){var _parsed$showTitle2,_parsed$linkTitle2,_parsed$showRelated2,_parsed$showUserAvata2;if(!this.selectedIframe)return null;if("a"===this.selectedIframe.nodeName.toLowerCase()&&"true"===this.selectedIframe.getAttribute("data-mediacms-textlink")){var _parsed$showTitle,_parsed$linkTitle,_parsed$showRelated,_parsed$showUserAvata;const href=this.selectedIframe.getAttribute("href"),parsed=this.parseInput(href);return{url:href,width:(null==parsed?void 0:parsed.width)||560,height:(null==parsed?void 0:parsed.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:!0,textLinkOnly:!0,startAtEnabled:null!==(null==parsed?void 0:parsed.startAt),startAt:(null==parsed?void 0:parsed.startAt)||"0:00"}}const src=this.selectedIframe.getAttribute("src"),parsed=this.parseInput(src);let width=(null==parsed?void 0:parsed.width)||this.selectedIframe.getAttribute("width")||null,height=(null==parsed?void 0:parsed.height)||this.selectedIframe.getAttribute("height")||null;return width=width?parseInt(width):null,height=height?parseInt(height):null,{url:src,width:width,height:height,showTitle:null===(_parsed$showTitle2=null==parsed?void 0:parsed.showTitle)||void 0===_parsed$showTitle2||_parsed$showTitle2,linkTitle:null===(_parsed$linkTitle2=null==parsed?void 0:parsed.linkTitle)||void 0===_parsed$linkTitle2||_parsed$linkTitle2,showRelated:null===(_parsed$showRelated2=null==parsed?void 0:parsed.showRelated)||void 0===_parsed$showRelated2||_parsed$showRelated2,showUserAvatar:null===(_parsed$showUserAvata2=null==parsed?void 0:parsed.showUserAvatar)||void 0===_parsed$showUserAvata2||_parsed$showUserAvata2,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,textLinkOnly:form.querySelector(_selectors.default.IFRAME.elements.textLinkOnly).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:this.parseWidthHeight(form.querySelector(_selectors.default.IFRAME.elements.width).value),height:this.parseWidthHeight(form.querySelector(_selectors.default.IFRAME.elements.height).value)}}async generateIframeHtml(values){const parsed=this.parseInput(values.url);if(!parsed)return"";if(values.textLinkOnly){let viewUrl;viewUrl=parsed.isGeneric||parsed.isLtiLaunch?parsed.rawUrl:"".concat(parsed.baseUrl,"/view?m=").concat(parsed.videoId);const linkText=(str=>{const div=document.createElement("div");return div.textContent=str,div.innerHTML})(viewUrl),hrefUrl=viewUrl.replace(/"/g,""");return'

').concat(linkText,"

")}const context={src:this.buildEmbedUrl(parsed,values),width:values.width,height:values.height},{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}if(values.textLinkOnly){let viewUrl;viewUrl=parsed.isGeneric||parsed.isLtiLaunch?parsed.rawUrl:"".concat(parsed.baseUrl,"/view?m=").concat(parsed.videoId);const linkText=(str=>{const div=document.createElement("div");return div.textContent=str,div.innerHTML})(viewUrl),hrefUrl=viewUrl.replace(/"/g,""");previewContainer.innerHTML='\n
\n Text link preview:
\n ').concat(linkText,'\n
This link will not be auto-converted by the MediaCMS filter.\n
\n ')}else{const previewWidth=Math.min(values.width,400),scale=previewWidth/values.width,previewHeight=Math.round(values.height*scale);previewContainer.innerHTML='\n \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)}handleWidthChange(root,newWidth){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,heightInput=form.querySelector(_selectors.default.IFRAME.elements.height);if("custom"!==aspectRatio&&newWidth){const width=parseInt(newWidth)||0,arr=aspectRatio.split(":"),x=parseInt(arr[0]),y=parseInt(arr[1]),calculatedHeight=Math.round(width*y/x);heightInput.value=calculatedHeight}this.handleInputChange(root)}handleHeightChange(root,newHeight){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,widthInput=form.querySelector(_selectors.default.IFRAME.elements.width);if("custom"!==aspectRatio&&newHeight){const height=parseInt(newHeight)||0,arr=aspectRatio.split(":"),x=parseInt(arr[0]),y=parseInt(arr[1]),calculatedWidth=Math.round(height*x/y);widthInput.value=calculatedWidth}this.handleInputChange(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"),paragraphWrapper=wrapper?wrapper.closest("p"):this.selectedIframe.closest("p");paragraphWrapper?paragraphWrapper.outerHTML=html:wrapper?wrapper.outerHTML=html:this.selectedIframe.outerHTML=html,this.isUpdating=!1,setTimeout((()=>{this.editor.getBody().querySelectorAll("p:empty, p:blank").forEach((p=>{""!==p.innerHTML.trim()&&"
"!==p.innerHTML||p.remove()}))}),10),this.editor.fire("Change")}else{const node=this.editor.selection.getNode();"P"===node.nodeName&&""===node.innerHTML.trim()?node.outerHTML=html: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.textLinkOnly).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",(e=>this.handleWidthChange(root,e.target.value))),form.querySelector(_selectors.default.IFRAME.elements.height).addEventListener("input",(e=>this.handleHeightChange(root,e.target.value))),$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),this.isUpdating?setTimeout((()=>this.updatePreview(root)),100):setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)}switchToUrlTab(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn),urlTabItem=form.querySelector(".tiny_iframecms_tab_url_item"),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabItem&&(urlTabItem.style.display=""),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),urlTabItem=form.querySelector(".tiny_iframecms_tab_url_item"),iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn),urlPane=form.querySelector(_selectors.default.IFRAME.elements.paneUrl),iframeLibraryPane=form.querySelector(_selectors.default.IFRAME.elements.paneIframeLibrary);urlTabItem&&(urlTabItem.style.display="none"),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 83deb581..4d1c0493 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 const widthParam = urlObj.searchParams.get('width');\n const heightParam = urlObj.searchParams.get('height');\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 width: widthParam ? parseInt(widthParam) : null,\n height: heightParam ? parseInt(heightParam) : null,\n startAt: tParam\n ? this.secondsToTimeString(parseInt(tParam))\n : null,\n };\n }\n\n // Moodle LTI launch.php URL: /filter/mediacms/launch.php?token=TOKEN\n // This is used when selecting from \"My Media\" via LTI\n // We treat it as a generic iframe URL (keep as-is)\n if (urlObj.pathname.includes('/filter/mediacms/launch.php') && urlObj.searchParams.has('token')) {\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('token'),\n rawUrl: url,\n isLtiLaunch: true,\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 * Parse width/height value.\n *\n * @param {string} value - Width or height value\n * @returns {number|null} Parsed value\n */\n parseWidthHeight(value) {\n if (!value) {\n return null;\n }\n const parsed = parseInt(value.trim());\n return isNaN(parsed) ? null : parsed;\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 || parsed.isLtiLaunch) {\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 width and height if provided\n if (options.width) {\n url.searchParams.set('width', options.width.toString());\n }\n if (options.height) {\n url.searchParams.set('height', options.height.toString());\n }\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 // Determine dimensions:\n // - For editing: preserve existing dimensions\n // - For new embeds: default to 640x360\n let width, height;\n\n if (this.isUpdating && (data.width || data.height)) {\n // Editing: preserve existing dimensions\n width = data.width || 640;\n height = data.height || 360;\n } else {\n // New embed: default to 640x360 (16:9 aspect ratio)\n width = 640;\n height = 360;\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 textLinkOnly: data.textLinkOnly || false,\n startAtEnabled: data.startAtEnabled || false,\n startAt: data.startAt || '0:00',\n width: width,\n height: height,\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 or text link in the editor.\n *\n * @returns {HTMLElement|null} The iframe element, text link, or null\n */\n getSelectedIframe() {\n const node = this.editor.selection.getNode();\n\n // Check if it's a text-only link\n if (node.nodeName.toLowerCase() === 'a' && node.getAttribute('data-mediacms-textlink') === 'true') {\n return node;\n }\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 // Check if we're inside a text-only link\n const textLink = node.closest('a[data-mediacms-textlink=\"true\"]');\n if (textLink) {\n return textLink;\n }\n\n return null;\n }\n\n /**\n * Get current iframe or text link data for editing.\n *\n * @returns {Object|null} Current iframe/link data or null\n */\n getCurrentIframeData() {\n if (!this.selectedIframe) {\n return null;\n }\n\n // Check if it's a text-only link\n if (this.selectedIframe.nodeName.toLowerCase() === 'a' &&\n this.selectedIframe.getAttribute('data-mediacms-textlink') === 'true') {\n const href = this.selectedIframe.getAttribute('href');\n const parsed = this.parseInput(href);\n\n return {\n url: href,\n width: parsed?.width || 560,\n height: parsed?.height || 315,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\n responsive: true,\n textLinkOnly: true,\n startAtEnabled: parsed?.startAt !== null,\n startAt: parsed?.startAt || '0:00',\n };\n }\n\n // Handle iframe\n const src = this.selectedIframe.getAttribute('src');\n const parsed = this.parseInput(src);\n\n // Get width/height from URL params or iframe attributes\n let width = parsed?.width || this.selectedIframe.getAttribute('width') || null;\n let height = parsed?.height || this.selectedIframe.getAttribute('height') || null;\n\n // Parse numeric values\n width = width ? parseInt(width) : null;\n height = height ? parseInt(height) : null;\n\n return {\n url: src,\n width: width,\n height: height,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\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 textLinkOnly: form.querySelector(Selectors.IFRAME.elements.textLinkOnly)\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: this.parseWidthHeight(\n form.querySelector(Selectors.IFRAME.elements.width).value,\n ),\n height: this.parseWidthHeight(\n form.querySelector(Selectors.IFRAME.elements.height).value,\n ),\n };\n }\n\n /**\n * Generate the iframe HTML or text link.\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 // If user selected \"text link only\", generate a link instead of iframe\n if (values.textLinkOnly) {\n // Build the view URL (not embed URL) for the link\n let viewUrl;\n if (parsed.isGeneric || parsed.isLtiLaunch) {\n // For generic URLs and LTI launch URLs, use as-is\n viewUrl = parsed.rawUrl;\n } else {\n // For MediaCMS URLs, convert to view URL\n viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`;\n }\n\n // Use the full URL as the link text\n // Escape HTML entities in the URL for safe display\n const escapeHtml = (str) => {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n };\n\n const linkText = escapeHtml(viewUrl);\n const hrefUrl = viewUrl.replace(/\"/g, '"');\n\n // Add data attribute to mark this as a text-only link\n // This prevents the filter from converting it to an LTI launch iframe\n return `

${linkText}

`;\n }\n\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n const context = {\n src: embedUrl,\n width: values.width,\n height: values.height,\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 // If text link only is selected, show link preview\n if (values.textLinkOnly) {\n let viewUrl;\n if (parsed.isGeneric || parsed.isLtiLaunch) {\n viewUrl = parsed.rawUrl;\n } else {\n viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`;\n }\n\n // Escape HTML entities for safe display\n const escapeHtml = (str) => {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n };\n\n const linkText = escapeHtml(viewUrl);\n const hrefUrl = viewUrl.replace(/\"/g, '"');\n\n previewContainer.innerHTML = `\n
\n Text link preview:
\n ${linkText}\n
This link will not be auto-converted by the MediaCMS filter.\n
\n `;\n } else {\n // Show a scaled preview - scale to fit preview area\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\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 * Toggle width/height input editability based on responsive mode.\n *\n * @param {HTMLElement} root - Modal root element\n */\n\n /**\n * Handle width change - adjust height to maintain aspect ratio.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {string} newWidth - New width value\n */\n handleWidthChange(root, newWidth) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(Selectors.IFRAME.elements.aspectRatio).value;\n const heightInput = form.querySelector(Selectors.IFRAME.elements.height);\n\n // Only adjust height if aspect ratio is not 'custom'\n if (aspectRatio !== 'custom' && newWidth) {\n const width = parseInt(newWidth) || 0;\n const arr = aspectRatio.split(':');\n const x = parseInt(arr[0]);\n const y = parseInt(arr[1]);\n\n // Calculate height based on aspect ratio: height = (width * y) / x\n const calculatedHeight = Math.round((width * y) / x);\n heightInput.value = calculatedHeight;\n }\n\n this.handleInputChange(root);\n }\n\n /**\n * Handle height change - adjust width to maintain aspect ratio.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {string} newHeight - New height value\n */\n handleHeightChange(root, newHeight) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(Selectors.IFRAME.elements.aspectRatio).value;\n const widthInput = form.querySelector(Selectors.IFRAME.elements.width);\n\n // Only adjust width if aspect ratio is not 'custom'\n if (aspectRatio !== 'custom' && newHeight) {\n const height = parseInt(newHeight) || 0;\n const arr = aspectRatio.split(':');\n const x = parseInt(arr[0]);\n const y = parseInt(arr[1]);\n\n // Calculate width based on aspect ratio: width = (height * x) / y\n const calculatedWidth = Math.round((height * x) / y);\n widthInput.value = calculatedWidth;\n }\n\n this.handleInputChange(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\n // Check if iframe is inside a

tag\n const paragraphWrapper = wrapper ? wrapper.closest('p') : this.selectedIframe.closest('p');\n\n if (paragraphWrapper) {\n // Replace the entire paragraph to avoid empty

tags\n paragraphWrapper.outerHTML = html;\n } else if (wrapper) {\n wrapper.outerHTML = html;\n } else {\n this.selectedIframe.outerHTML = html;\n }\n this.isUpdating = false;\n\n // Clean up any empty paragraph tags left behind\n setTimeout(() => {\n const body = this.editor.getBody();\n const emptyPs = body.querySelectorAll('p:empty, p:blank');\n emptyPs.forEach(p => {\n if (p.innerHTML.trim() === '' || p.innerHTML === '
') {\n p.remove();\n }\n });\n }, 10);\n\n // Fire change event to trigger overlay reprocessing\n this.editor.fire('Change');\n } else {\n // Insert content without wrapping in paragraph tags\n // Use setContent if cursor is in an empty paragraph, otherwise insertContent\n const node = this.editor.selection.getNode();\n if (node.nodeName === 'P' && node.innerHTML.trim() === '') {\n // Replace empty paragraph with iframe\n node.outerHTML = html;\n } else {\n this.editor.insertContent(html);\n }\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 // Text link only checkbox - doesn't affect URL, only output format\n form.querySelector(Selectors.IFRAME.elements.textLinkOnly).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 - maintain aspect ratio when changed\n form.querySelector(Selectors.IFRAME.elements.width).addEventListener(\n 'input',\n (e) => this.handleWidthChange(root, e.target.value),\n );\n form.querySelector(Selectors.IFRAME.elements.height).addEventListener(\n 'input',\n (e) => this.handleHeightChange(root, e.target.value),\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 // If editing, Configure tab is active - update preview immediately\n // If inserting, My Media tab is active - load the library\n if (this.isUpdating) {\n // When editing, Configure tab is already active, just update preview\n setTimeout(() => this.updatePreview(root), 100);\n } else {\n // When inserting, load My Media library tab\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n }\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 urlTabItem = form.querySelector('.tiny_iframecms_tab_url_item');\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 // Show the Configure tab\n if (urlTabItem) {\n urlTabItem.style.display = '';\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 urlTabItem = form.querySelector('.tiny_iframecms_tab_url_item');\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 // Hide the Configure tab when switching to My Media\n if (urlTabItem) {\n urlTabItem.style.display = 'none';\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","widthParam","heightParam","showTitle","linkTitle","showRelated","showUserAvatar","width","parseInt","height","startAt","secondsToTimeString","includes","rawUrl","isLtiLaunch","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","parts","split","isNaN","parseWidthHeight","value","parsed","buildEmbedUrl","options","set","startAtEnabled","data","editorData","autoConvertOptions","getDefault","key","fallback","_this","isUpdating","undefined","elementid","getElement","id","isupdating","textLinkOnly","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","getAttribute","iframe","querySelector","wrapper","closest","textLink","href","responsive","src","getFormValues","root","form","Selectors","IFRAME","elements","checked","values","viewUrl","linkText","str","div","document","createElement","textContent","innerHTML","escapeHtml","hrefUrl","replace","context","html","Templates","renderForPromise","updateUrlField","previewContainer","preview","urlWarning","classList","add","remove","embedUrl","previewWidth","min","scale","previewHeight","round","handleInputChange","clearTimeout","debounceTimer","setTimeout","updatePreview","handleAspectRatioChange","dimensions","aspectRatios","handleWidthChange","newWidth","heightInput","arr","x","y","calculatedHeight","handleHeightChange","newHeight","widthInput","calculatedWidth","modal","getRoot","generateIframeHtml","paragraphWrapper","outerHTML","getBody","querySelectorAll","forEach","p","fire","insertContent","confirmMessage","window","confirm","hide","$root","addEventListener","selector","target","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","removeBtn","actions","handleRemove","iframeLibraryTabBtn","tabIframeLibraryBtn","preventDefault","stopPropagation","switchToIframeLibraryTab","handleIframeLibraryTabClick","$iframeLibraryTabBtn","jQuery","urlTabBtn","tabUrlBtn","switchToUrlTab","registerIframeLibraryEventListeners","urlTabItem","urlPane","paneUrl","iframeLibraryPane","paneIframeLibrary","style","display","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","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 const widthParam = urlObj.searchParams.get('width');\n const heightParam = urlObj.searchParams.get('height');\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 width: widthParam ? parseInt(widthParam) : null,\n height: heightParam ? parseInt(heightParam) : null,\n startAt: tParam\n ? this.secondsToTimeString(parseInt(tParam))\n : null,\n };\n }\n\n // Moodle LTI launch.php URL: /filter/mediacms/launch.php?token=TOKEN\n // This is used when the filter processes MediaCMS URLs\n // Extract all embed parameters so they're preserved when editing\n if (urlObj.pathname.includes('/filter/mediacms/launch.php') && urlObj.searchParams.has('token')) {\n const tParam = urlObj.searchParams.get('t');\n const widthParam = urlObj.searchParams.get('width');\n const heightParam = urlObj.searchParams.get('height');\n\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('token'),\n rawUrl: url,\n isLtiLaunch: true,\n showTitle: urlObj.searchParams.get('showTitle') === '1',\n linkTitle: urlObj.searchParams.get('linkTitle') === '1',\n showRelated: urlObj.searchParams.get('showRelated') === '1',\n showUserAvatar: urlObj.searchParams.get('showUserAvatar') === '1',\n width: widthParam ? parseInt(widthParam) : null,\n height: heightParam ? parseInt(heightParam) : null,\n startAt: tParam ? this.secondsToTimeString(parseInt(tParam)) : 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 * Parse width/height value.\n *\n * @param {string} value - Width or height value\n * @returns {number|null} Parsed value\n */\n parseWidthHeight(value) {\n if (!value) {\n return null;\n }\n const parsed = parseInt(value.trim());\n return isNaN(parsed) ? null : parsed;\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 // Build URL - either LTI launch.php or direct embed URL\n let url;\n if (parsed.isLtiLaunch) {\n // Rebuild LTI launch URL with updated parameters\n url = new URL(parsed.rawUrl);\n // Keep existing token, clear other params to rebuild them\n const token = url.searchParams.get('token');\n const courseid = url.searchParams.get('courseid');\n url.search = '';\n url.searchParams.set('token', token);\n if (courseid) {\n url.searchParams.set('courseid', courseid);\n }\n } else {\n // Build direct MediaCMS embed URL\n url = new URL(`${parsed.baseUrl}/embed`);\n url.searchParams.set('m', parsed.videoId);\n }\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 width and height if provided\n if (options.width) {\n url.searchParams.set('width', options.width.toString());\n }\n if (options.height) {\n url.searchParams.set('height', options.height.toString());\n }\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 // Determine dimensions:\n // - For editing: preserve existing dimensions\n // - For new embeds: default to 640x360\n let width, height;\n\n if (this.isUpdating && (data.width || data.height)) {\n // Editing: preserve existing dimensions\n width = data.width || 640;\n height = data.height || 360;\n } else {\n // New embed: default to 640x360 (16:9 aspect ratio)\n width = 640;\n height = 360;\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 textLinkOnly: data.textLinkOnly || false,\n startAtEnabled: data.startAtEnabled || false,\n startAt: data.startAt || '0:00',\n width: width,\n height: height,\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 or text link in the editor.\n *\n * @returns {HTMLElement|null} The iframe element, text link, or null\n */\n getSelectedIframe() {\n const node = this.editor.selection.getNode();\n\n // Check if it's a text-only link\n if (node.nodeName.toLowerCase() === 'a' && node.getAttribute('data-mediacms-textlink') === 'true') {\n return node;\n }\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 // Check if we're inside a text-only link\n const textLink = node.closest('a[data-mediacms-textlink=\"true\"]');\n if (textLink) {\n return textLink;\n }\n\n return null;\n }\n\n /**\n * Get current iframe or text link data for editing.\n *\n * @returns {Object|null} Current iframe/link data or null\n */\n getCurrentIframeData() {\n if (!this.selectedIframe) {\n return null;\n }\n\n // Check if it's a text-only link\n if (this.selectedIframe.nodeName.toLowerCase() === 'a' &&\n this.selectedIframe.getAttribute('data-mediacms-textlink') === 'true') {\n const href = this.selectedIframe.getAttribute('href');\n const parsed = this.parseInput(href);\n\n return {\n url: href,\n width: parsed?.width || 560,\n height: parsed?.height || 315,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\n responsive: true,\n textLinkOnly: true,\n startAtEnabled: parsed?.startAt !== null,\n startAt: parsed?.startAt || '0:00',\n };\n }\n\n // Handle iframe\n const src = this.selectedIframe.getAttribute('src');\n const parsed = this.parseInput(src);\n\n // Get width/height from URL params or iframe attributes\n let width = parsed?.width || this.selectedIframe.getAttribute('width') || null;\n let height = parsed?.height || this.selectedIframe.getAttribute('height') || null;\n\n // Parse numeric values\n width = width ? parseInt(width) : null;\n height = height ? parseInt(height) : null;\n\n return {\n url: src,\n width: width,\n height: height,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\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 textLinkOnly: form.querySelector(Selectors.IFRAME.elements.textLinkOnly)\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: this.parseWidthHeight(\n form.querySelector(Selectors.IFRAME.elements.width).value,\n ),\n height: this.parseWidthHeight(\n form.querySelector(Selectors.IFRAME.elements.height).value,\n ),\n };\n }\n\n /**\n * Generate the iframe HTML or text link.\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 // If user selected \"text link only\", generate a link instead of iframe\n if (values.textLinkOnly) {\n // Build the view URL (not embed URL) for the link\n let viewUrl;\n if (parsed.isGeneric || parsed.isLtiLaunch) {\n // For generic URLs and LTI launch URLs, use as-is\n viewUrl = parsed.rawUrl;\n } else {\n // For MediaCMS URLs, convert to view URL\n viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`;\n }\n\n // Use the full URL as the link text\n // Escape HTML entities in the URL for safe display\n const escapeHtml = (str) => {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n };\n\n const linkText = escapeHtml(viewUrl);\n const hrefUrl = viewUrl.replace(/\"/g, '"');\n\n // Add data attribute to mark this as a text-only link\n // This prevents the filter from converting it to an LTI launch iframe\n return `

${linkText}

`;\n }\n\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n const context = {\n src: embedUrl,\n width: values.width,\n height: values.height,\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 // If text link only is selected, show link preview\n if (values.textLinkOnly) {\n let viewUrl;\n if (parsed.isGeneric || parsed.isLtiLaunch) {\n viewUrl = parsed.rawUrl;\n } else {\n viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`;\n }\n\n // Escape HTML entities for safe display\n const escapeHtml = (str) => {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n };\n\n const linkText = escapeHtml(viewUrl);\n const hrefUrl = viewUrl.replace(/\"/g, '"');\n\n previewContainer.innerHTML = `\n
\n Text link preview:
\n ${linkText}\n
This link will not be auto-converted by the MediaCMS filter.\n
\n `;\n } else {\n // Show a scaled preview - scale to fit preview area\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\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 * Toggle width/height input editability based on responsive mode.\n *\n * @param {HTMLElement} root - Modal root element\n */\n\n /**\n * Handle width change - adjust height to maintain aspect ratio.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {string} newWidth - New width value\n */\n handleWidthChange(root, newWidth) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(Selectors.IFRAME.elements.aspectRatio).value;\n const heightInput = form.querySelector(Selectors.IFRAME.elements.height);\n\n // Only adjust height if aspect ratio is not 'custom'\n if (aspectRatio !== 'custom' && newWidth) {\n const width = parseInt(newWidth) || 0;\n const arr = aspectRatio.split(':');\n const x = parseInt(arr[0]);\n const y = parseInt(arr[1]);\n\n // Calculate height based on aspect ratio: height = (width * y) / x\n const calculatedHeight = Math.round((width * y) / x);\n heightInput.value = calculatedHeight;\n }\n\n this.handleInputChange(root);\n }\n\n /**\n * Handle height change - adjust width to maintain aspect ratio.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {string} newHeight - New height value\n */\n handleHeightChange(root, newHeight) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(Selectors.IFRAME.elements.aspectRatio).value;\n const widthInput = form.querySelector(Selectors.IFRAME.elements.width);\n\n // Only adjust width if aspect ratio is not 'custom'\n if (aspectRatio !== 'custom' && newHeight) {\n const height = parseInt(newHeight) || 0;\n const arr = aspectRatio.split(':');\n const x = parseInt(arr[0]);\n const y = parseInt(arr[1]);\n\n // Calculate width based on aspect ratio: width = (height * x) / y\n const calculatedWidth = Math.round((height * x) / y);\n widthInput.value = calculatedWidth;\n }\n\n this.handleInputChange(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\n // Check if iframe is inside a

tag\n const paragraphWrapper = wrapper ? wrapper.closest('p') : this.selectedIframe.closest('p');\n\n if (paragraphWrapper) {\n // Replace the entire paragraph to avoid empty

tags\n paragraphWrapper.outerHTML = html;\n } else if (wrapper) {\n wrapper.outerHTML = html;\n } else {\n this.selectedIframe.outerHTML = html;\n }\n this.isUpdating = false;\n\n // Clean up any empty paragraph tags left behind\n setTimeout(() => {\n const body = this.editor.getBody();\n const emptyPs = body.querySelectorAll('p:empty, p:blank');\n emptyPs.forEach(p => {\n if (p.innerHTML.trim() === '' || p.innerHTML === '
') {\n p.remove();\n }\n });\n }, 10);\n\n // Fire change event to trigger overlay reprocessing\n this.editor.fire('Change');\n } else {\n // Insert content without wrapping in paragraph tags\n // Use setContent if cursor is in an empty paragraph, otherwise insertContent\n const node = this.editor.selection.getNode();\n if (node.nodeName === 'P' && node.innerHTML.trim() === '') {\n // Replace empty paragraph with iframe\n node.outerHTML = html;\n } else {\n this.editor.insertContent(html);\n }\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 // Text link only checkbox - doesn't affect URL, only output format\n form.querySelector(Selectors.IFRAME.elements.textLinkOnly).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 - maintain aspect ratio when changed\n form.querySelector(Selectors.IFRAME.elements.width).addEventListener(\n 'input',\n (e) => this.handleWidthChange(root, e.target.value),\n );\n form.querySelector(Selectors.IFRAME.elements.height).addEventListener(\n 'input',\n (e) => this.handleHeightChange(root, e.target.value),\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 // If editing, Configure tab is active - update preview immediately\n // If inserting, My Media tab is active - load the library\n if (this.isUpdating) {\n // When editing, Configure tab is already active, just update preview\n setTimeout(() => this.updatePreview(root), 100);\n } else {\n // When inserting, load My Media library tab\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n }\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 urlTabItem = form.querySelector('.tiny_iframecms_tab_url_item');\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 // Show the Configure tab\n if (urlTabItem) {\n urlTabItem.style.display = '';\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 urlTabItem = form.querySelector('.tiny_iframecms_tab_url_item');\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 // Hide the Configure tab when switching to My Media\n if (urlTabItem) {\n urlTabItem.style.display = 'none';\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","widthParam","heightParam","showTitle","linkTitle","showRelated","showUserAvatar","width","parseInt","height","startAt","secondsToTimeString","includes","rawUrl","isLtiLaunch","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","parts","split","isNaN","parseWidthHeight","value","parsed","buildEmbedUrl","options","token","courseid","search","set","startAtEnabled","data","editorData","autoConvertOptions","getDefault","key","fallback","_this","isUpdating","undefined","elementid","getElement","id","isupdating","textLinkOnly","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","getAttribute","iframe","querySelector","wrapper","closest","textLink","href","responsive","src","getFormValues","root","form","Selectors","IFRAME","elements","checked","values","viewUrl","linkText","str","div","document","createElement","textContent","innerHTML","escapeHtml","hrefUrl","replace","context","html","Templates","renderForPromise","updateUrlField","previewContainer","preview","urlWarning","classList","add","remove","embedUrl","previewWidth","min","scale","previewHeight","round","handleInputChange","clearTimeout","debounceTimer","setTimeout","updatePreview","handleAspectRatioChange","dimensions","aspectRatios","handleWidthChange","newWidth","heightInput","arr","x","y","calculatedHeight","handleHeightChange","newHeight","widthInput","calculatedWidth","modal","getRoot","generateIframeHtml","paragraphWrapper","outerHTML","getBody","querySelectorAll","forEach","p","fire","insertContent","confirmMessage","window","confirm","hide","$root","addEventListener","selector","target","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","removeBtn","actions","handleRemove","iframeLibraryTabBtn","tabIframeLibraryBtn","preventDefault","stopPropagation","switchToIframeLibraryTab","handleIframeLibraryTabClick","$iframeLibraryTabBtn","jQuery","urlTabBtn","tabUrlBtn","switchToUrlTab","registerIframeLibraryEventListeners","urlTabItem","urlPane","paneUrl","iframeLibraryPane","paneIframeLibrary","style","display","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","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 59ed5314..f422e5ad 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js @@ -118,14 +118,25 @@ export default class IframeEmbed { } // Moodle LTI launch.php URL: /filter/mediacms/launch.php?token=TOKEN - // This is used when selecting from "My Media" via LTI - // We treat it as a generic iframe URL (keep as-is) + // This is used when the filter processes MediaCMS URLs + // Extract all embed parameters so they're preserved when editing if (urlObj.pathname.includes('/filter/mediacms/launch.php') && urlObj.searchParams.has('token')) { + const tParam = urlObj.searchParams.get('t'); + const widthParam = urlObj.searchParams.get('width'); + const heightParam = urlObj.searchParams.get('height'); + return { baseUrl: baseUrl, videoId: urlObj.searchParams.get('token'), rawUrl: url, isLtiLaunch: true, + showTitle: urlObj.searchParams.get('showTitle') === '1', + linkTitle: urlObj.searchParams.get('linkTitle') === '1', + showRelated: urlObj.searchParams.get('showRelated') === '1', + showUserAvatar: urlObj.searchParams.get('showUserAvatar') === '1', + width: widthParam ? parseInt(widthParam) : null, + height: heightParam ? parseInt(heightParam) : null, + startAt: tParam ? this.secondsToTimeString(parseInt(tParam)) : null, }; } @@ -209,12 +220,28 @@ export default class IframeEmbed { * @returns {string} The complete embed URL */ buildEmbedUrl(parsed, options) { - if (parsed.isGeneric || parsed.isLtiLaunch) { + if (parsed.isGeneric) { return parsed.rawUrl; } - const url = new URL(`${parsed.baseUrl}/embed`); - url.searchParams.set('m', parsed.videoId); + // Build URL - either LTI launch.php or direct embed URL + let url; + if (parsed.isLtiLaunch) { + // Rebuild LTI launch URL with updated parameters + url = new URL(parsed.rawUrl); + // Keep existing token, clear other params to rebuild them + const token = url.searchParams.get('token'); + const courseid = url.searchParams.get('courseid'); + url.search = ''; + url.searchParams.set('token', token); + if (courseid) { + url.searchParams.set('courseid', courseid); + } + } else { + // Build direct MediaCMS embed URL + url = new URL(`${parsed.baseUrl}/embed`); + url.searchParams.set('m', parsed.videoId); + } // Always include all options with 1 or 0 url.searchParams.set('showTitle', options.showTitle ? '1' : '0');