From 86f4484c36d4af83a5f3096cf8de1457d1033499 Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Wed, 11 Feb 2026 21:34:52 +0200 Subject: [PATCH] a --- .../filter/mediacms/classes/text_filter.php | 30 ++++- .../mediacms-moodle/tiny/mediacms/README.md | 3 +- .../mediacms/amd/build/iframeembed.min.js | 2 +- .../mediacms/amd/build/iframeembed.min.js.map | 2 +- .../tiny/mediacms/amd/build/selectors.min.js | 2 +- .../mediacms/amd/build/selectors.min.js.map | 2 +- .../tiny/mediacms/amd/src/iframeembed.js | 103 +++++++++++++++--- .../tiny/mediacms/amd/src/selectors.js | 1 + .../tiny/mediacms/lang/en/tiny_mediacms.php | 1 + .../templates/iframe_embed_modal.mustache | 27 ++--- .../templates/iframe_embed_options.mustache | 15 ++- 11 files changed, 144 insertions(+), 44 deletions(-) diff --git a/lms-plugins/mediacms-moodle/filter/mediacms/classes/text_filter.php b/lms-plugins/mediacms-moodle/filter/mediacms/classes/text_filter.php index 493f4f55..96da26b5 100644 --- a/lms-plugins/mediacms-moodle/filter/mediacms/classes/text_filter.php +++ b/lms-plugins/mediacms-moodle/filter/mediacms/classes/text_filter.php @@ -41,20 +41,36 @@ class text_filter extends \core_filters\text_filter { // 2. Handle Auto-convert URLs if enabled if (get_config('filter_mediacms', 'enableautoconvert')) { + // First, protect text-only links from being converted + // by temporarily replacing them with placeholders + $textlink_placeholders = []; + $textlink_pattern = '/]*data-mediacms-textlink=["\']true["\'][^>]*>.*?<\/a>/is'; + + $newtext = preg_replace_callback($textlink_pattern, function($matches) use (&$textlink_placeholders) { + $placeholder = '###MEDIACMS_TEXTLINK_' . count($textlink_placeholders) . '###'; + $textlink_placeholders[$placeholder] = $matches[0]; + return $placeholder; + }, $newtext); + // Regex for MediaCMS view URLs: https://domain/view?m=TOKEN // We need to be careful to match the configured domain $parsed_url = parse_url($mediacmsurl); $host = preg_quote($parsed_url['host'] ?? '', '/'); $scheme = preg_quote($parsed_url['scheme'] ?? 'https', '/'); - + // Allow http or https, and optional path prefix $path_prefix = preg_quote(rtrim($parsed_url['path'] ?? '', '/'), '/'); // Pattern: https://HOST/PREFIX/view?m=TOKEN // Also handle /embed?m=TOKEN $pattern_url = '/(' . $scheme . ':\/\/' . $host . $path_prefix . '\/(view|embed)\?m=([a-zA-Z0-9]+)(?:&[^\s<]*)?)/'; - + $newtext = preg_replace_callback($pattern_url, [$this, 'callback_url'], $newtext); + + // Restore protected text-only links + foreach ($textlink_placeholders as $placeholder => $original) { + $newtext = str_replace($placeholder, $original, $newtext); + } } return $newtext; @@ -71,7 +87,17 @@ class text_filter extends \core_filters\text_filter { * Callback for URLs */ public function callback_url($matches) { + // matches[0] is the full matched string // matches[1] is full URL, matches[3] is token + + // Check if this URL is inside a text-only link + // by looking at the context around the match + $fullmatch = $matches[0]; + + // If this is already inside an tag with data-mediacms-textlink="true", + // return the original URL unchanged + // We'll check this in the main filter method instead + $token = $matches[3]; return $this->generate_iframe($token); } diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/README.md b/lms-plugins/mediacms-moodle/tiny/mediacms/README.md index a5eb915d..c4352f7b 100644 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/README.md +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/README.md @@ -12,6 +12,7 @@ A TinyMCE editor plugin for Moodle that provides media embedding capabilities wi # i've noticed that this fails, so this should work: npx grunt amd 5. To test the output: - cp lib/editor/tiny/plugins/mediacms/* ../../lms-plugins/mediacms-moodle/tiny/mediacms/ -r +cp * ../../../../../../../lms-plugins/mediacms-moodle/tiny/mediacms/ -r + 6. Then copy to Moodle server and purge caches 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 ab266919..cb6e25ef 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js @@ -1,3 +1,3 @@ -define("tiny_mediacms/iframeembed",["exports","core/templates","core/str","core/modal_events","./common","./iframemodal","./selectors","./options"],(function(_exports,_templates,_str,ModalEvents,_common,_iframemodal,_selectors,_options){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),ModalEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalEvents),_iframemodal=_interopRequireDefault(_iframemodal),_selectors=_interopRequireDefault(_selectors);return _exports.default=class{constructor(editor){_defineProperty(this,"editor",null),_defineProperty(this,"currentModal",null),_defineProperty(this,"isUpdating",!1),_defineProperty(this,"selectedIframe",null),_defineProperty(this,"debounceTimer",null),_defineProperty(this,"iframeLibraryLoaded",!1),_defineProperty(this,"selectedLibraryVideo",null),_defineProperty(this,"iframeLibraryUrl","https://temp.web357.com/mediacms/deic-mediacms-embed-videos.html"),this.editor=editor}parseInput(input){if(!input||!input.trim())return null;const iframeMatch=(input=input.trim()).match(/]*src=["']([^"']+)["'][^>]*>/i);return iframeMatch?this.parseEmbedUrl(iframeMatch[1]):input.startsWith("http://")||input.startsWith("https://")?this.parseVideoUrl(input):null}parseVideoUrl(url){try{const urlObj=new URL(url),baseUrl="".concat(urlObj.protocol,"//").concat(urlObj.host);if("/view"===urlObj.pathname&&urlObj.searchParams.has("m"))return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!1};if("/embed"===urlObj.pathname&&urlObj.searchParams.has("m")){const tParam=urlObj.searchParams.get("t");return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!0,showTitle:"1"===urlObj.searchParams.get("showTitle"),linkTitle:"1"===urlObj.searchParams.get("linkTitle"),showRelated:"1"===urlObj.searchParams.get("showRelated"),showUserAvatar:"1"===urlObj.searchParams.get("showUserAvatar"),startAt:tParam?this.secondsToTimeString(parseInt(tParam)):null}}return{baseUrl:baseUrl,rawUrl:url,isGeneric:!0}}catch(e){return null}}parseEmbedUrl(url){return this.parseVideoUrl(url)}secondsToTimeString(seconds){const mins=Math.floor(seconds/60),secs=seconds%60;return"".concat(mins,":").concat(secs.toString().padStart(2,"0"))}timeStringToSeconds(timeStr){if(!timeStr||!timeStr.trim())return null;if((timeStr=timeStr.trim()).includes(":")){const parts=timeStr.split(":");return 60*(parseInt(parts[0])||0)+(parseInt(parts[1])||0)}const secs=parseInt(timeStr);return isNaN(secs)?null:secs}buildEmbedUrl(parsed,options){if(parsed.isGeneric)return parsed.rawUrl;const url=new URL("".concat(parsed.baseUrl,"/embed"));if(url.searchParams.set("m",parsed.videoId),url.searchParams.set("showTitle",options.showTitle?"1":"0"),url.searchParams.set("showRelated",options.showRelated?"1":"0"),url.searchParams.set("showUserAvatar",options.showUserAvatar?"1":"0"),url.searchParams.set("linkTitle",options.linkTitle?"1":"0"),options.startAtEnabled&&options.startAt){const seconds=this.timeStringToSeconds(options.startAt);null!==seconds&&seconds>0&&url.searchParams.set("t",seconds.toString())}return url.toString()}async getTemplateContext(){var _this=this;let data=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const editorData=(0,_options.getData)(this.editor),autoConvertOptions=(null==editorData?void 0:editorData.autoConvertOptions)||{},getDefault=function(key){let fallback=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return _this.isUpdating&&void 0!==data[key]?data[key]:void 0!==autoConvertOptions[key]?autoConvertOptions[key]:fallback};return{elementid:this.editor.getElement().id,isupdating:this.isUpdating,url:data.url||"",showTitle:getDefault("showTitle"),linkTitle:getDefault("linkTitle"),showRelated:getDefault("showRelated"),showUserAvatar:getDefault("showUserAvatar"),responsive:!1!==data.responsive,startAtEnabled:data.startAtEnabled||!1,startAt:data.startAt||"0:00",width:data.width||560,height:data.height||315,is16_9:!data.aspectRatio||"16:9"===data.aspectRatio,is4_3:"4:3"===data.aspectRatio,is1_1:"1:1"===data.aspectRatio,isCustom:"custom"===data.aspectRatio}}async displayDialogue(){this.selectedIframe=this.getSelectedIframe();const data=this.getCurrentIframeData();this.isUpdating=null!==data,this.iframeLibraryLoaded=!1,this.currentModal=await _iframemodal.default.create({title:(0,_str.getString)("iframemodaltitle",_common.component),templateContext:await this.getTemplateContext(data||{})}),await this.registerEventListeners(this.currentModal)}getSelectedIframe(){const node=this.editor.selection.getNode();if("iframe"===node.nodeName.toLowerCase())return node;const iframe=node.querySelector("iframe");if(iframe)return iframe;const wrapper=node.closest(".tiny-mediacms-iframe-wrapper")||node.closest(".tiny-iframe-responsive");return wrapper?wrapper.querySelector("iframe"):null}getCurrentIframeData(){var _parsed$showTitle,_parsed$linkTitle,_parsed$showRelated,_parsed$showUserAvata;if(!this.selectedIframe)return null;const src=this.selectedIframe.getAttribute("src"),parsed=this.parseInput(src),isResponsive=(this.selectedIframe.getAttribute("style")||"").includes("aspect-ratio");return{url:src,width:this.selectedIframe.getAttribute("width")||560,height:this.selectedIframe.getAttribute("height")||315,showTitle:null===(_parsed$showTitle=null==parsed?void 0:parsed.showTitle)||void 0===_parsed$showTitle||_parsed$showTitle,linkTitle:null===(_parsed$linkTitle=null==parsed?void 0:parsed.linkTitle)||void 0===_parsed$linkTitle||_parsed$linkTitle,showRelated:null===(_parsed$showRelated=null==parsed?void 0:parsed.showRelated)||void 0===_parsed$showRelated||_parsed$showRelated,showUserAvatar:null===(_parsed$showUserAvata=null==parsed?void 0:parsed.showUserAvatar)||void 0===_parsed$showUserAvata||_parsed$showUserAvata,responsive:isResponsive,startAtEnabled:null!==(null==parsed?void 0:parsed.startAt),startAt:(null==parsed?void 0:parsed.startAt)||"0:00"}}getFormValues(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form);return{url:form.querySelector(_selectors.default.IFRAME.elements.url).value.trim(),showTitle:form.querySelector(_selectors.default.IFRAME.elements.showTitle).checked,linkTitle:form.querySelector(_selectors.default.IFRAME.elements.linkTitle).checked,showRelated:form.querySelector(_selectors.default.IFRAME.elements.showRelated).checked,showUserAvatar:form.querySelector(_selectors.default.IFRAME.elements.showUserAvatar).checked,responsive:form.querySelector(_selectors.default.IFRAME.elements.responsive).checked,startAtEnabled:form.querySelector(_selectors.default.IFRAME.elements.startAtEnabled).checked,startAt:form.querySelector(_selectors.default.IFRAME.elements.startAt).value.trim(),aspectRatio:form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,width:parseInt(form.querySelector(_selectors.default.IFRAME.elements.width).value)||560,height:parseInt(form.querySelector(_selectors.default.IFRAME.elements.height).value)||315}}async generateIframeHtml(values){const parsed=this.parseInput(values.url);if(!parsed)return"";const embedUrl=this.buildEmbedUrl(parsed,values),aspectRatioCalcs={"16:9":"16 / 9","4:3":"4 / 3","1:1":"1 / 1",custom:"".concat(values.width," / ").concat(values.height)},context={src:embedUrl,width:values.width,height:values.height,responsive:values.responsive,aspectRatioCalc:aspectRatioCalcs[values.aspectRatio]||"16 / 9",aspectRatioValue:aspectRatioCalcs[values.aspectRatio]||"16 / 9"},{html:html}=await _templates.default.renderForPromise("tiny_mediacms/iframe_embed_output",context);return html}async updatePreview(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const values=this.getFormValues(root),previewContainer=root.querySelector(_selectors.default.IFRAME.elements.preview),urlWarning=root.querySelector(_selectors.default.IFRAME.elements.urlWarning);if(!values.url)return previewContainer.innerHTML='Enter a video URL to see preview',void urlWarning.classList.add("d-none");const parsed=this.parseInput(values.url);if(!parsed)return previewContainer.innerHTML='Invalid URL format',void urlWarning.classList.remove("d-none");urlWarning.classList.add("d-none");const embedUrl=this.buildEmbedUrl(parsed,values);if(updateUrlField&&!parsed.isGeneric){root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.url).value=embedUrl}const previewWidth=Math.min(values.width,400),scale=previewWidth/values.width,previewHeight=Math.round(values.height*scale);previewContainer.innerHTML='\n \n ')}handleInputChange(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout((()=>{this.updatePreview(root,updateUrlField)}),500)}handleAspectRatioChange(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,dimensions=_selectors.default.IFRAME.aspectRatios[aspectRatio];dimensions&&"custom"!==aspectRatio&&(form.querySelector(_selectors.default.IFRAME.elements.width).value=dimensions.width,form.querySelector(_selectors.default.IFRAME.elements.height).value=dimensions.height),this.updatePreview(root)}async handleDialogueSubmission(modal){const root=modal.getRoot()[0],values=this.getFormValues(root);if(!values.url)return;const html=await this.generateIframeHtml(values);if(html)if(this.isUpdating&&this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.outerHTML=html:this.selectedIframe.outerHTML=html,this.isUpdating=!1,this.editor.fire("Change")}else this.editor.insertContent(html)}async handleRemove(modal){const confirmMessage=await(0,_str.getString)("removeiframeconfirm",_common.component);if(window.confirm(confirmMessage)){if(this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.remove():this.selectedIframe.remove()}this.isUpdating=!1,modal.hide()}}async registerEventListeners(modal){await modal.getBody();const $root=modal.getRoot(),root=$root[0],form=root.querySelector(_selectors.default.IFRAME.elements.form);form.querySelector(_selectors.default.IFRAME.elements.url).addEventListener("input",(()=>this.handleInputChange(root))),[_selectors.default.IFRAME.elements.showTitle,_selectors.default.IFRAME.elements.linkTitle,_selectors.default.IFRAME.elements.showRelated,_selectors.default.IFRAME.elements.showUserAvatar,_selectors.default.IFRAME.elements.startAtEnabled].forEach((selector=>{form.querySelector(selector).addEventListener("change",(()=>this.handleInputChange(root,!0)))})),form.querySelector(_selectors.default.IFRAME.elements.responsive).addEventListener("change",(()=>this.handleInputChange(root,!1))),form.querySelector(_selectors.default.IFRAME.elements.startAt).addEventListener("input",(()=>this.handleInputChange(root,!0))),form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).addEventListener("change",(()=>this.handleAspectRatioChange(root))),form.querySelector(_selectors.default.IFRAME.elements.width).addEventListener("input",(()=>this.handleInputChange(root))),form.querySelector(_selectors.default.IFRAME.elements.height).addEventListener("input",(()=>this.handleInputChange(root))),$root.on(ModalEvents.save,(()=>this.handleDialogueSubmission(modal))),$root.on(ModalEvents.hidden,(()=>{this.currentModal.destroy()}));const removeBtn=root.querySelector(_selectors.default.IFRAME.actions.remove);removeBtn&&removeBtn.addEventListener("click",(()=>this.handleRemove(modal)));form.querySelector(_selectors.default.IFRAME.elements.url).value&&this.updatePreview(root);const iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn);if(iframeLibraryTabBtn){iframeLibraryTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToIframeLibraryTab(root),setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)})),iframeLibraryTabBtn.addEventListener("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)));const $iframeLibraryTabBtn=window.jQuery?window.jQuery(iframeLibraryTabBtn):null;$iframeLibraryTabBtn&&$iframeLibraryTabBtn.on("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)))}const urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn);urlTabBtn&&urlTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToUrlTab(root)})),this.registerIframeLibraryEventListeners(root),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");return{baseUrl:baseUrl,videoId:urlObj.searchParams.get("m"),isEmbed:!0,showTitle:"1"===urlObj.searchParams.get("showTitle"),linkTitle:"1"===urlObj.searchParams.get("linkTitle"),showRelated:"1"===urlObj.searchParams.get("showRelated"),showUserAvatar:"1"===urlObj.searchParams.get("showUserAvatar"),startAt:tParam?this.secondsToTimeString(parseInt(tParam)):null}}return{baseUrl:baseUrl,rawUrl:url,isGeneric:!0}}catch(e){return null}}parseEmbedUrl(url){return this.parseVideoUrl(url)}secondsToTimeString(seconds){const mins=Math.floor(seconds/60),secs=seconds%60;return"".concat(mins,":").concat(secs.toString().padStart(2,"0"))}timeStringToSeconds(timeStr){if(!timeStr||!timeStr.trim())return null;if((timeStr=timeStr.trim()).includes(":")){const parts=timeStr.split(":");return 60*(parseInt(parts[0])||0)+(parseInt(parts[1])||0)}const secs=parseInt(timeStr);return isNaN(secs)?null:secs}buildEmbedUrl(parsed,options){if(parsed.isGeneric)return parsed.rawUrl;const url=new URL("".concat(parsed.baseUrl,"/embed"));if(url.searchParams.set("m",parsed.videoId),url.searchParams.set("showTitle",options.showTitle?"1":"0"),url.searchParams.set("showRelated",options.showRelated?"1":"0"),url.searchParams.set("showUserAvatar",options.showUserAvatar?"1":"0"),url.searchParams.set("linkTitle",options.linkTitle?"1":"0"),options.startAtEnabled&&options.startAt){const seconds=this.timeStringToSeconds(options.startAt);null!==seconds&&seconds>0&&url.searchParams.set("t",seconds.toString())}return url.toString()}async getTemplateContext(){var _this=this;let data=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const editorData=(0,_options.getData)(this.editor),autoConvertOptions=(null==editorData?void 0:editorData.autoConvertOptions)||{},getDefault=function(key){let fallback=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return _this.isUpdating&&void 0!==data[key]?data[key]:void 0!==autoConvertOptions[key]?autoConvertOptions[key]:fallback};return{elementid:this.editor.getElement().id,isupdating:this.isUpdating,url:data.url||"",showTitle:getDefault("showTitle"),linkTitle:getDefault("linkTitle"),showRelated:getDefault("showRelated"),showUserAvatar:getDefault("showUserAvatar"),responsive:!1!==data.responsive,textLinkOnly:data.textLinkOnly||!1,startAtEnabled:data.startAtEnabled||!1,startAt:data.startAt||"0:00",width:data.width||560,height:data.height||315,is16_9:!data.aspectRatio||"16:9"===data.aspectRatio,is4_3:"4:3"===data.aspectRatio,is1_1:"1:1"===data.aspectRatio,isCustom:"custom"===data.aspectRatio}}async displayDialogue(){this.selectedIframe=this.getSelectedIframe();const data=this.getCurrentIframeData();this.isUpdating=null!==data,this.iframeLibraryLoaded=!1,this.currentModal=await _iframemodal.default.create({title:(0,_str.getString)("iframemodaltitle",_common.component),templateContext:await this.getTemplateContext(data||{})}),await this.registerEventListeners(this.currentModal)}getSelectedIframe(){const node=this.editor.selection.getNode();if("iframe"===node.nodeName.toLowerCase())return node;const iframe=node.querySelector("iframe");if(iframe)return iframe;const wrapper=node.closest(".tiny-mediacms-iframe-wrapper")||node.closest(".tiny-iframe-responsive");return wrapper?wrapper.querySelector("iframe"):null}getCurrentIframeData(){var _parsed$showTitle,_parsed$linkTitle,_parsed$showRelated,_parsed$showUserAvata;if(!this.selectedIframe)return null;const src=this.selectedIframe.getAttribute("src"),parsed=this.parseInput(src),isResponsive=(this.selectedIframe.getAttribute("style")||"").includes("aspect-ratio");return{url:src,width:this.selectedIframe.getAttribute("width")||560,height:this.selectedIframe.getAttribute("height")||315,showTitle:null===(_parsed$showTitle=null==parsed?void 0:parsed.showTitle)||void 0===_parsed$showTitle||_parsed$showTitle,linkTitle:null===(_parsed$linkTitle=null==parsed?void 0:parsed.linkTitle)||void 0===_parsed$linkTitle||_parsed$linkTitle,showRelated:null===(_parsed$showRelated=null==parsed?void 0:parsed.showRelated)||void 0===_parsed$showRelated||_parsed$showRelated,showUserAvatar:null===(_parsed$showUserAvata=null==parsed?void 0:parsed.showUserAvatar)||void 0===_parsed$showUserAvata||_parsed$showUserAvata,responsive:isResponsive,startAtEnabled:null!==(null==parsed?void 0:parsed.startAt),startAt:(null==parsed?void 0:parsed.startAt)||"0:00"}}getFormValues(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form);return{url:form.querySelector(_selectors.default.IFRAME.elements.url).value.trim(),showTitle:form.querySelector(_selectors.default.IFRAME.elements.showTitle).checked,linkTitle:form.querySelector(_selectors.default.IFRAME.elements.linkTitle).checked,showRelated:form.querySelector(_selectors.default.IFRAME.elements.showRelated).checked,showUserAvatar:form.querySelector(_selectors.default.IFRAME.elements.showUserAvatar).checked,responsive:form.querySelector(_selectors.default.IFRAME.elements.responsive).checked,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:parseInt(form.querySelector(_selectors.default.IFRAME.elements.width).value)||560,height:parseInt(form.querySelector(_selectors.default.IFRAME.elements.height).value)||315}}async generateIframeHtml(values){const parsed=this.parseInput(values.url);if(!parsed)return"";if(values.textLinkOnly){let viewUrl;viewUrl=parsed.isGeneric?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 embedUrl=this.buildEmbedUrl(parsed,values),aspectRatioCalcs={"16:9":"16 / 9","4:3":"4 / 3","1:1":"1 / 1",custom:"".concat(values.width," / ").concat(values.height)},context={src:embedUrl,width:values.width,height:values.height,responsive:values.responsive,aspectRatioCalc:aspectRatioCalcs[values.aspectRatio]||"16 / 9",aspectRatioValue:aspectRatioCalcs[values.aspectRatio]||"16 / 9"},{html:html}=await _templates.default.renderForPromise("tiny_mediacms/iframe_embed_output",context);return html}async updatePreview(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const values=this.getFormValues(root),previewContainer=root.querySelector(_selectors.default.IFRAME.elements.preview),urlWarning=root.querySelector(_selectors.default.IFRAME.elements.urlWarning);if(!values.url)return previewContainer.innerHTML='Enter a video URL to see preview',void urlWarning.classList.add("d-none");const parsed=this.parseInput(values.url);if(!parsed)return previewContainer.innerHTML='Invalid URL format',void urlWarning.classList.remove("d-none");urlWarning.classList.add("d-none");const embedUrl=this.buildEmbedUrl(parsed,values);if(updateUrlField&&!parsed.isGeneric){root.querySelector(_selectors.default.IFRAME.elements.form).querySelector(_selectors.default.IFRAME.elements.url).value=embedUrl}if(values.textLinkOnly){let viewUrl;viewUrl=parsed.isGeneric?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)}async handleDialogueSubmission(modal){const root=modal.getRoot()[0],values=this.getFormValues(root);if(!values.url)return;const html=await this.generateIframeHtml(values);if(html)if(this.isUpdating&&this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.outerHTML=html:this.selectedIframe.outerHTML=html,this.isUpdating=!1,this.editor.fire("Change")}else this.editor.insertContent(html)}async handleRemove(modal){const confirmMessage=await(0,_str.getString)("removeiframeconfirm",_common.component);if(window.confirm(confirmMessage)){if(this.selectedIframe){const wrapper=this.selectedIframe.closest(".tiny-mediacms-iframe-wrapper")||this.selectedIframe.closest(".tiny-iframe-responsive");wrapper?wrapper.remove():this.selectedIframe.remove()}this.isUpdating=!1,modal.hide()}}async registerEventListeners(modal){await modal.getBody();const $root=modal.getRoot(),root=$root[0],form=root.querySelector(_selectors.default.IFRAME.elements.form);form.querySelector(_selectors.default.IFRAME.elements.url).addEventListener("input",(()=>this.handleInputChange(root))),[_selectors.default.IFRAME.elements.showTitle,_selectors.default.IFRAME.elements.linkTitle,_selectors.default.IFRAME.elements.showRelated,_selectors.default.IFRAME.elements.showUserAvatar,_selectors.default.IFRAME.elements.startAtEnabled].forEach((selector=>{form.querySelector(selector).addEventListener("change",(()=>this.handleInputChange(root,!0)))})),form.querySelector(_selectors.default.IFRAME.elements.responsive).addEventListener("change",(()=>this.handleInputChange(root,!1))),form.querySelector(_selectors.default.IFRAME.elements.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",(()=>this.handleInputChange(root))),form.querySelector(_selectors.default.IFRAME.elements.height).addEventListener("input",(()=>this.handleInputChange(root))),$root.on(ModalEvents.save,(()=>this.handleDialogueSubmission(modal))),$root.on(ModalEvents.hidden,(()=>{this.currentModal.destroy()}));const removeBtn=root.querySelector(_selectors.default.IFRAME.actions.remove);removeBtn&&removeBtn.addEventListener("click",(()=>this.handleRemove(modal)));form.querySelector(_selectors.default.IFRAME.elements.url).value&&this.updatePreview(root);const iframeLibraryTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabIframeLibraryBtn);if(iframeLibraryTabBtn){iframeLibraryTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToIframeLibraryTab(root),setTimeout((()=>this.handleIframeLibraryTabClick(root)),100)})),iframeLibraryTabBtn.addEventListener("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)));const $iframeLibraryTabBtn=window.jQuery?window.jQuery(iframeLibraryTabBtn):null;$iframeLibraryTabBtn&&$iframeLibraryTabBtn.on("shown.bs.tab",(()=>this.handleIframeLibraryTabClick(root)))}const urlTabBtn=form.querySelector(_selectors.default.IFRAME.elements.tabUrlBtn);urlTabBtn&&urlTabBtn.addEventListener("click",(e=>{e.preventDefault(),e.stopPropagation(),this.switchToUrlTab(root)})),this.registerIframeLibraryEventListeners(root),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 b01efa3f..2f44518d 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js.map +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js.map @@ -1 +1 @@ -{"version":3,"file":"iframeembed.min.js","sources":["../src/iframeembed.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Media2 Iframe Embed class.\n *\n * @module tiny_mediacms/iframeembed\n * @copyright 2024\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport { getString } from 'core/str';\nimport * as ModalEvents from 'core/modal_events';\nimport { component } from './common';\nimport IframeModal from './iframemodal';\nimport Selectors from './selectors';\nimport { getLti, getData } from './options';\n\nexport default class IframeEmbed {\n editor = null;\n currentModal = null;\n isUpdating = false;\n selectedIframe = null;\n debounceTimer = null;\n // Iframe library state\n iframeLibraryLoaded = false;\n selectedLibraryVideo = null;\n iframeLibraryUrl =\n 'https://temp.web357.com/mediacms/deic-mediacms-embed-videos.html';\n\n constructor(editor) {\n this.editor = editor;\n }\n\n /**\n * Parse input to extract video URL or iframe src.\n * Handles: iframe embed code, view URL, or embed URL.\n *\n * @param {string} input - The user input (URL or embed code)\n * @returns {Object|null} Parsed URL info or null if invalid\n */\n parseInput(input) {\n if (!input || !input.trim()) {\n return null;\n }\n\n input = input.trim();\n\n // Check if it's an iframe embed code\n const iframeMatch = input.match(\n /]*src=[\"']([^\"']+)[\"'][^>]*>/i,\n );\n if (iframeMatch) {\n return this.parseEmbedUrl(iframeMatch[1]);\n }\n\n // Check if it's a URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return this.parseVideoUrl(input);\n }\n\n return null;\n }\n\n /**\n * Parse a video view URL and convert to embed format.\n *\n * @param {string} url - The video URL\n * @returns {Object|null} Parsed info\n */\n parseVideoUrl(url) {\n try {\n const urlObj = new URL(url);\n const baseUrl = `${urlObj.protocol}//${urlObj.host}`;\n\n // MediaCMS view URL: /view?m=VIDEO_ID\n if (urlObj.pathname === '/view' && urlObj.searchParams.has('m')) {\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: false,\n };\n }\n\n // MediaCMS embed URL: /embed?m=VIDEO_ID&options\n if (urlObj.pathname === '/embed' && urlObj.searchParams.has('m')) {\n const tParam = urlObj.searchParams.get('t');\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: true,\n showTitle: urlObj.searchParams.get('showTitle') === '1',\n linkTitle: urlObj.searchParams.get('linkTitle') === '1',\n showRelated: urlObj.searchParams.get('showRelated') === '1',\n showUserAvatar:\n urlObj.searchParams.get('showUserAvatar') === '1',\n startAt: tParam\n ? this.secondsToTimeString(parseInt(tParam))\n : null,\n };\n }\n\n // Generic URL - just use as-is\n return {\n baseUrl: baseUrl,\n rawUrl: url,\n isGeneric: true,\n };\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Parse an embed URL.\n *\n * @param {string} url - The embed URL\n * @returns {Object|null} Parsed info\n */\n parseEmbedUrl(url) {\n return this.parseVideoUrl(url);\n }\n\n /**\n * Convert seconds to time string (M:SS format).\n *\n * @param {number} seconds - Time in seconds\n * @returns {string} Time string\n */\n secondsToTimeString(seconds) {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n /**\n * Convert time string to seconds.\n *\n * @param {string} timeStr - Time string (M:SS or just seconds)\n * @returns {number|null} Seconds or null if invalid\n */\n timeStringToSeconds(timeStr) {\n if (!timeStr || !timeStr.trim()) {\n return null;\n }\n timeStr = timeStr.trim();\n\n // Handle M:SS format\n if (timeStr.includes(':')) {\n const parts = timeStr.split(':');\n const mins = parseInt(parts[0]) || 0;\n const secs = parseInt(parts[1]) || 0;\n return mins * 60 + secs;\n }\n\n // Handle plain seconds\n const secs = parseInt(timeStr);\n return isNaN(secs) ? null : secs;\n }\n\n /**\n * Build the embed URL with options.\n *\n * @param {Object} parsed - Parsed URL info\n * @param {Object} options - Embed options\n * @returns {string} The complete embed URL\n */\n buildEmbedUrl(parsed, options) {\n if (parsed.isGeneric) {\n return parsed.rawUrl;\n }\n\n const url = new URL(`${parsed.baseUrl}/embed`);\n url.searchParams.set('m', parsed.videoId);\n\n // Always include all options with 1 or 0\n url.searchParams.set('showTitle', options.showTitle ? '1' : '0');\n url.searchParams.set('showRelated', options.showRelated ? '1' : '0');\n url.searchParams.set(\n 'showUserAvatar',\n options.showUserAvatar ? '1' : '0',\n );\n url.searchParams.set('linkTitle', options.linkTitle ? '1' : '0');\n\n // Add start time if enabled\n if (options.startAtEnabled && options.startAt) {\n const seconds = this.timeStringToSeconds(options.startAt);\n if (seconds !== null && seconds > 0) {\n url.searchParams.set('t', seconds.toString());\n }\n }\n\n return url.toString();\n }\n\n /**\n * Get the template context for the modal.\n *\n * @param {Object} data - Existing data for updating\n * @returns {Object} Template context\n */\n async getTemplateContext(data = {}) {\n // Get admin settings for default checkbox values.\n const editorData = getData(this.editor);\n const autoConvertOptions = editorData?.autoConvertOptions || {};\n\n // Use admin settings as defaults when creating new embed (not updating).\n // When updating, use the values from the existing iframe.\n const getDefault = (key, fallback = true) => {\n if (this.isUpdating && data[key] !== undefined) {\n return data[key];\n }\n return autoConvertOptions[key] !== undefined\n ? autoConvertOptions[key]\n : fallback;\n };\n\n return {\n elementid: this.editor.getElement().id,\n isupdating: this.isUpdating,\n url: data.url || '',\n showTitle: getDefault('showTitle'),\n linkTitle: getDefault('linkTitle'),\n showRelated: getDefault('showRelated'),\n showUserAvatar: getDefault('showUserAvatar'),\n responsive: data.responsive !== false,\n startAtEnabled: data.startAtEnabled || false,\n startAt: data.startAt || '0:00',\n width: data.width || 560,\n height: data.height || 315,\n is16_9: !data.aspectRatio || data.aspectRatio === '16:9',\n is4_3: data.aspectRatio === '4:3',\n is1_1: data.aspectRatio === '1:1',\n isCustom: data.aspectRatio === 'custom',\n };\n }\n\n /**\n * Display the iframe embed dialog.\n */\n async displayDialogue() {\n this.selectedIframe = this.getSelectedIframe();\n const data = this.getCurrentIframeData();\n this.isUpdating = data !== null;\n\n // Reset iframe library state for new modal\n this.iframeLibraryLoaded = false;\n\n this.currentModal = await IframeModal.create({\n title: getString('iframemodaltitle', component),\n templateContext: await this.getTemplateContext(data || {}),\n });\n\n await this.registerEventListeners(this.currentModal);\n }\n\n /**\n * Get the currently selected iframe in the editor.\n *\n * @returns {HTMLElement|null} The iframe element or null\n */\n getSelectedIframe() {\n const node = this.editor.selection.getNode();\n\n if (node.nodeName.toLowerCase() === 'iframe') {\n return node;\n }\n\n // Check if selection contains an iframe wrapper (including overlay wrapper)\n const iframe = node.querySelector('iframe');\n if (iframe) {\n return iframe;\n }\n\n // Check if we're on the overlay or wrapper and need to find the iframe\n const wrapper =\n node.closest('.tiny-mediacms-iframe-wrapper') ||\n node.closest('.tiny-iframe-responsive');\n if (wrapper) {\n return wrapper.querySelector('iframe');\n }\n\n return null;\n }\n\n /**\n * Get current iframe data for editing.\n *\n * @returns {Object|null} Current iframe data or null\n */\n getCurrentIframeData() {\n if (!this.selectedIframe) {\n return null;\n }\n\n const src = this.selectedIframe.getAttribute('src');\n const parsed = this.parseInput(src);\n\n // Check if responsive by looking at style\n const style = this.selectedIframe.getAttribute('style') || '';\n const isResponsive = style.includes('aspect-ratio');\n\n return {\n url: src,\n width: this.selectedIframe.getAttribute('width') || 560,\n height: this.selectedIframe.getAttribute('height') || 315,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\n responsive: isResponsive,\n startAtEnabled: parsed?.startAt !== null,\n startAt: parsed?.startAt || '0:00',\n };\n }\n\n /**\n * Get form values from the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @returns {Object} Form values\n */\n getFormValues(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n return {\n url: form.querySelector(Selectors.IFRAME.elements.url).value.trim(),\n showTitle: form.querySelector(Selectors.IFRAME.elements.showTitle)\n .checked,\n linkTitle: form.querySelector(Selectors.IFRAME.elements.linkTitle)\n .checked,\n showRelated: form.querySelector(\n Selectors.IFRAME.elements.showRelated,\n ).checked,\n showUserAvatar: form.querySelector(\n Selectors.IFRAME.elements.showUserAvatar,\n ).checked,\n responsive: form.querySelector(Selectors.IFRAME.elements.responsive)\n .checked,\n startAtEnabled: form.querySelector(\n Selectors.IFRAME.elements.startAtEnabled,\n ).checked,\n startAt: form\n .querySelector(Selectors.IFRAME.elements.startAt)\n .value.trim(),\n aspectRatio: form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).value,\n width:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.width).value,\n ) || 560,\n height:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.height).value,\n ) || 315,\n };\n }\n\n /**\n * Generate the iframe HTML.\n *\n * @param {Object} values - Form values\n * @returns {Promise} Generated HTML\n */\n async generateIframeHtml(values) {\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n return '';\n }\n\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Calculate aspect ratio values for CSS\n const aspectRatioCalcs = {\n '16:9': '16 / 9',\n '4:3': '4 / 3',\n '1:1': '1 / 1',\n custom: `${values.width} / ${values.height}`,\n };\n\n const context = {\n src: embedUrl,\n width: values.width,\n height: values.height,\n responsive: values.responsive,\n aspectRatioCalc: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n aspectRatioValue: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n };\n\n const { html } = await Templates.renderForPromise(\n 'tiny_mediacms/iframe_embed_output',\n context,\n );\n return html;\n }\n\n /**\n * Update the preview in the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field with new embed options\n */\n async updatePreview(root, updateUrlField = false) {\n const values = this.getFormValues(root);\n const previewContainer = root.querySelector(\n Selectors.IFRAME.elements.preview,\n );\n const urlWarning = root.querySelector(\n Selectors.IFRAME.elements.urlWarning,\n );\n\n if (!values.url) {\n previewContainer.innerHTML =\n 'Enter a video URL to see preview';\n urlWarning.classList.add('d-none');\n return;\n }\n\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n previewContainer.innerHTML =\n 'Invalid URL format';\n urlWarning.classList.remove('d-none');\n return;\n }\n\n urlWarning.classList.add('d-none');\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Update the URL field if requested (when embed options change)\n if (updateUrlField && !parsed.isGeneric) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n urlInput.value = embedUrl;\n }\n\n // Show a scaled preview\n const previewWidth = Math.min(values.width, 400);\n const scale = previewWidth / values.width;\n const previewHeight = Math.round(values.height * scale);\n\n previewContainer.innerHTML = `\n \n `;\n }\n\n /**\n * Handle form input changes with debounce.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field\n */\n handleInputChange(root, updateUrlField = false) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n this.updatePreview(root, updateUrlField);\n }, 500);\n }\n\n /**\n * Handle aspect ratio change - update dimensions.\n *\n * @param {HTMLElement} root - Modal root element\n */\n handleAspectRatioChange(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const aspectRatio = form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).value;\n const dimensions = Selectors.IFRAME.aspectRatios[aspectRatio];\n\n // Only update dimensions for predefined ratios, not 'custom'\n if (dimensions && aspectRatio !== 'custom') {\n form.querySelector(Selectors.IFRAME.elements.width).value =\n dimensions.width;\n form.querySelector(Selectors.IFRAME.elements.height).value =\n dimensions.height;\n }\n\n this.updatePreview(root);\n }\n\n /**\n * Handle dialog submission.\n *\n * @param {Object} modal - The modal instance\n */\n async handleDialogueSubmission(modal) {\n const root = modal.getRoot()[0];\n const values = this.getFormValues(root);\n\n if (!values.url) {\n return;\n }\n\n const html = await this.generateIframeHtml(values);\n if (html) {\n if (this.isUpdating && this.selectedIframe) {\n // Replace existing iframe (including wrapper if present)\n // Check for both old wrapper and new overlay wrapper\n const wrapper =\n this.selectedIframe.closest(\n '.tiny-mediacms-iframe-wrapper',\n ) || this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.outerHTML = html;\n } else {\n this.selectedIframe.outerHTML = html;\n }\n this.isUpdating = false;\n // Fire change event to trigger overlay reprocessing\n this.editor.fire('Change');\n } else {\n this.editor.insertContent(html);\n }\n }\n }\n\n /**\n * Handle video removal from the editor.\n *\n * @param {Object} modal - The modal instance\n */\n async handleRemove(modal) {\n // Get confirmation string\n const confirmMessage = await getString(\n 'removeiframeconfirm',\n component,\n );\n\n // Show confirmation dialog\n // eslint-disable-next-line no-alert\n if (!window.confirm(confirmMessage)) {\n return;\n }\n\n if (this.selectedIframe) {\n // Remove the iframe (including wrapper if present)\n const wrapper =\n this.selectedIframe.closest('.tiny-mediacms-iframe-wrapper') ||\n this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.remove();\n } else {\n this.selectedIframe.remove();\n }\n }\n\n // Close the modal\n this.isUpdating = false;\n modal.hide();\n }\n\n /**\n * Register event listeners for the modal.\n *\n * @param {Object} modal - The modal instance\n */\n async registerEventListeners(modal) {\n await modal.getBody();\n const $root = modal.getRoot();\n const root = $root[0];\n\n // Input change handlers\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // URL input\n form.querySelector(Selectors.IFRAME.elements.url).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Embed option checkboxes - these should update the URL field when changed\n [\n Selectors.IFRAME.elements.showTitle,\n Selectors.IFRAME.elements.linkTitle,\n Selectors.IFRAME.elements.showRelated,\n Selectors.IFRAME.elements.showUserAvatar,\n Selectors.IFRAME.elements.startAtEnabled,\n ].forEach((selector) => {\n form.querySelector(selector).addEventListener('change', () =>\n this.handleInputChange(root, true), // Update URL field when embed options change\n );\n });\n\n // Responsive checkbox - doesn't affect URL, only display\n form.querySelector(Selectors.IFRAME.elements.responsive).addEventListener('change', () =>\n this.handleInputChange(root, false),\n );\n\n // Start at time input - should update URL field\n form.querySelector(Selectors.IFRAME.elements.startAt).addEventListener(\n 'input',\n () => this.handleInputChange(root, true),\n );\n\n // Aspect ratio change\n form.querySelector(\n Selectors.IFRAME.elements.aspectRatio,\n ).addEventListener('change', () => this.handleAspectRatioChange(root));\n\n // Dimension inputs\n form.querySelector(Selectors.IFRAME.elements.width).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n form.querySelector(Selectors.IFRAME.elements.height).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Modal events\n $root.on(ModalEvents.save, () => this.handleDialogueSubmission(modal));\n $root.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n\n // Remove button handler (only present when updating)\n const removeBtn = root.querySelector(Selectors.IFRAME.actions.remove);\n if (removeBtn) {\n removeBtn.addEventListener('click', () => this.handleRemove(modal));\n }\n\n // Initial preview if we have a URL\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n if (urlInput.value) {\n this.updatePreview(root);\n }\n\n // Tab change handler - load iframe library when switching to iframe library tab\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching for Bootstrap 5\n this.switchToIframeLibraryTab(root);\n\n // Small delay to ensure tab pane is visible before loading iframe\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n });\n // Also handle Bootstrap tab events (if Bootstrap handles it)\n iframeLibraryTabBtn.addEventListener('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n // jQuery Bootstrap 4 event\n const $iframeLibraryTabBtn = window.jQuery\n ? window.jQuery(iframeLibraryTabBtn)\n : null;\n if ($iframeLibraryTabBtn) {\n $iframeLibraryTabBtn.on('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n }\n }\n\n // Tab change handler for URL tab\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n if (urlTabBtn) {\n urlTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching\n this.switchToUrlTab(root);\n });\n }\n\n // Iframe library event listeners\n this.registerIframeLibraryEventListeners(root);\n\n // Since My Media tab is now default/active, load it immediately on modal open\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n }\n\n /**\n * Switch to the URL tab.\n *\n * @param {HTMLElement} root - Modal root element\n */\n switchToUrlTab(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // Get tab buttons\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n const 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","showTitle","linkTitle","showRelated","showUserAvatar","startAt","secondsToTimeString","parseInt","rawUrl","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","includes","parts","split","isNaN","buildEmbedUrl","parsed","options","set","startAtEnabled","data","editorData","autoConvertOptions","getDefault","key","fallback","_this","isUpdating","undefined","elementid","getElement","id","isupdating","responsive","width","height","is16_9","aspectRatio","is4_3","is1_1","isCustom","selectedIframe","getSelectedIframe","getCurrentIframeData","iframeLibraryLoaded","currentModal","IframeModal","create","title","component","templateContext","getTemplateContext","registerEventListeners","node","selection","getNode","nodeName","toLowerCase","iframe","querySelector","wrapper","closest","src","getAttribute","isResponsive","getFormValues","root","form","Selectors","IFRAME","elements","value","checked","values","embedUrl","aspectRatioCalcs","custom","context","aspectRatioCalc","aspectRatioValue","html","Templates","renderForPromise","updateUrlField","previewContainer","preview","urlWarning","innerHTML","classList","add","remove","previewWidth","min","scale","previewHeight","round","handleInputChange","clearTimeout","debounceTimer","setTimeout","updatePreview","handleAspectRatioChange","dimensions","aspectRatios","modal","getRoot","generateIframeHtml","outerHTML","fire","insertContent","confirmMessage","window","confirm","hide","getBody","$root","addEventListener","forEach","selector","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","removeBtn","actions","handleRemove","iframeLibraryTabBtn","tabIframeLibraryBtn","preventDefault","stopPropagation","switchToIframeLibraryTab","handleIframeLibraryTabClick","$iframeLibraryTabBtn","jQuery","urlTabBtn","tabUrlBtn","switchToUrlTab","registerIframeLibraryEventListeners","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 return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('m'),\n isEmbed: true,\n showTitle: urlObj.searchParams.get('showTitle') === '1',\n linkTitle: urlObj.searchParams.get('linkTitle') === '1',\n showRelated: urlObj.searchParams.get('showRelated') === '1',\n showUserAvatar:\n urlObj.searchParams.get('showUserAvatar') === '1',\n startAt: tParam\n ? this.secondsToTimeString(parseInt(tParam))\n : null,\n };\n }\n\n // Generic URL - just use as-is\n return {\n baseUrl: baseUrl,\n rawUrl: url,\n isGeneric: true,\n };\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Parse an embed URL.\n *\n * @param {string} url - The embed URL\n * @returns {Object|null} Parsed info\n */\n parseEmbedUrl(url) {\n return this.parseVideoUrl(url);\n }\n\n /**\n * Convert seconds to time string (M:SS format).\n *\n * @param {number} seconds - Time in seconds\n * @returns {string} Time string\n */\n secondsToTimeString(seconds) {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n /**\n * Convert time string to seconds.\n *\n * @param {string} timeStr - Time string (M:SS or just seconds)\n * @returns {number|null} Seconds or null if invalid\n */\n timeStringToSeconds(timeStr) {\n if (!timeStr || !timeStr.trim()) {\n return null;\n }\n timeStr = timeStr.trim();\n\n // Handle M:SS format\n if (timeStr.includes(':')) {\n const parts = timeStr.split(':');\n const mins = parseInt(parts[0]) || 0;\n const secs = parseInt(parts[1]) || 0;\n return mins * 60 + secs;\n }\n\n // Handle plain seconds\n const secs = parseInt(timeStr);\n return isNaN(secs) ? null : secs;\n }\n\n /**\n * Build the embed URL with options.\n *\n * @param {Object} parsed - Parsed URL info\n * @param {Object} options - Embed options\n * @returns {string} The complete embed URL\n */\n buildEmbedUrl(parsed, options) {\n if (parsed.isGeneric) {\n return parsed.rawUrl;\n }\n\n const url = new URL(`${parsed.baseUrl}/embed`);\n url.searchParams.set('m', parsed.videoId);\n\n // Always include all options with 1 or 0\n url.searchParams.set('showTitle', options.showTitle ? '1' : '0');\n url.searchParams.set('showRelated', options.showRelated ? '1' : '0');\n url.searchParams.set(\n 'showUserAvatar',\n options.showUserAvatar ? '1' : '0',\n );\n url.searchParams.set('linkTitle', options.linkTitle ? '1' : '0');\n\n // Add start time if enabled\n if (options.startAtEnabled && options.startAt) {\n const seconds = this.timeStringToSeconds(options.startAt);\n if (seconds !== null && seconds > 0) {\n url.searchParams.set('t', seconds.toString());\n }\n }\n\n return url.toString();\n }\n\n /**\n * Get the template context for the modal.\n *\n * @param {Object} data - Existing data for updating\n * @returns {Object} Template context\n */\n async getTemplateContext(data = {}) {\n // Get admin settings for default checkbox values.\n const editorData = getData(this.editor);\n const autoConvertOptions = editorData?.autoConvertOptions || {};\n\n // Use admin settings as defaults when creating new embed (not updating).\n // When updating, use the values from the existing iframe.\n const getDefault = (key, fallback = true) => {\n if (this.isUpdating && data[key] !== undefined) {\n return data[key];\n }\n return autoConvertOptions[key] !== undefined\n ? autoConvertOptions[key]\n : fallback;\n };\n\n return {\n elementid: this.editor.getElement().id,\n isupdating: this.isUpdating,\n url: data.url || '',\n showTitle: getDefault('showTitle'),\n linkTitle: getDefault('linkTitle'),\n showRelated: getDefault('showRelated'),\n showUserAvatar: getDefault('showUserAvatar'),\n responsive: data.responsive !== false,\n textLinkOnly: data.textLinkOnly || false,\n startAtEnabled: data.startAtEnabled || false,\n startAt: data.startAt || '0:00',\n width: data.width || 560,\n height: data.height || 315,\n is16_9: !data.aspectRatio || data.aspectRatio === '16:9',\n is4_3: data.aspectRatio === '4:3',\n is1_1: data.aspectRatio === '1:1',\n isCustom: data.aspectRatio === 'custom',\n };\n }\n\n /**\n * Display the iframe embed dialog.\n */\n async displayDialogue() {\n this.selectedIframe = this.getSelectedIframe();\n const data = this.getCurrentIframeData();\n this.isUpdating = data !== null;\n\n // Reset iframe library state for new modal\n this.iframeLibraryLoaded = false;\n\n this.currentModal = await IframeModal.create({\n title: getString('iframemodaltitle', component),\n templateContext: await this.getTemplateContext(data || {}),\n });\n\n await this.registerEventListeners(this.currentModal);\n }\n\n /**\n * Get the currently selected iframe in the editor.\n *\n * @returns {HTMLElement|null} The iframe element or null\n */\n getSelectedIframe() {\n const node = this.editor.selection.getNode();\n\n if (node.nodeName.toLowerCase() === 'iframe') {\n return node;\n }\n\n // Check if selection contains an iframe wrapper (including overlay wrapper)\n const iframe = node.querySelector('iframe');\n if (iframe) {\n return iframe;\n }\n\n // Check if we're on the overlay or wrapper and need to find the iframe\n const wrapper =\n node.closest('.tiny-mediacms-iframe-wrapper') ||\n node.closest('.tiny-iframe-responsive');\n if (wrapper) {\n return wrapper.querySelector('iframe');\n }\n\n return null;\n }\n\n /**\n * Get current iframe data for editing.\n *\n * @returns {Object|null} Current iframe data or null\n */\n getCurrentIframeData() {\n if (!this.selectedIframe) {\n return null;\n }\n\n const src = this.selectedIframe.getAttribute('src');\n const parsed = this.parseInput(src);\n\n // Check if responsive by looking at style\n const style = this.selectedIframe.getAttribute('style') || '';\n const isResponsive = style.includes('aspect-ratio');\n\n return {\n url: src,\n width: this.selectedIframe.getAttribute('width') || 560,\n height: this.selectedIframe.getAttribute('height') || 315,\n showTitle: parsed?.showTitle ?? true,\n linkTitle: parsed?.linkTitle ?? true,\n showRelated: parsed?.showRelated ?? true,\n showUserAvatar: parsed?.showUserAvatar ?? true,\n responsive: isResponsive,\n startAtEnabled: parsed?.startAt !== null,\n startAt: parsed?.startAt || '0:00',\n };\n }\n\n /**\n * Get form values from the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @returns {Object} Form values\n */\n getFormValues(root) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n return {\n url: form.querySelector(Selectors.IFRAME.elements.url).value.trim(),\n showTitle: form.querySelector(Selectors.IFRAME.elements.showTitle)\n .checked,\n linkTitle: form.querySelector(Selectors.IFRAME.elements.linkTitle)\n .checked,\n showRelated: form.querySelector(\n Selectors.IFRAME.elements.showRelated,\n ).checked,\n showUserAvatar: form.querySelector(\n Selectors.IFRAME.elements.showUserAvatar,\n ).checked,\n responsive: form.querySelector(Selectors.IFRAME.elements.responsive)\n .checked,\n 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:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.width).value,\n ) || 560,\n height:\n parseInt(\n form.querySelector(Selectors.IFRAME.elements.height).value,\n ) || 315,\n };\n }\n\n /**\n * Generate the iframe HTML 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) {\n viewUrl = parsed.rawUrl;\n } else {\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 // Calculate aspect ratio values for CSS\n const aspectRatioCalcs = {\n '16:9': '16 / 9',\n '4:3': '4 / 3',\n '1:1': '1 / 1',\n custom: `${values.width} / ${values.height}`,\n };\n\n const context = {\n src: embedUrl,\n width: values.width,\n height: values.height,\n responsive: values.responsive,\n aspectRatioCalc: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n aspectRatioValue: aspectRatioCalcs[values.aspectRatio] || '16 / 9',\n };\n\n const { html } = await Templates.renderForPromise(\n 'tiny_mediacms/iframe_embed_output',\n context,\n );\n return html;\n }\n\n /**\n * Update the preview in the modal.\n *\n * @param {HTMLElement} root - Modal root element\n * @param {boolean} updateUrlField - Whether to update the URL field with new embed options\n */\n async updatePreview(root, updateUrlField = false) {\n const values = this.getFormValues(root);\n const previewContainer = root.querySelector(\n Selectors.IFRAME.elements.preview,\n );\n const urlWarning = root.querySelector(\n Selectors.IFRAME.elements.urlWarning,\n );\n\n if (!values.url) {\n previewContainer.innerHTML =\n 'Enter a video URL to see preview';\n urlWarning.classList.add('d-none');\n return;\n }\n\n const parsed = this.parseInput(values.url);\n if (!parsed) {\n previewContainer.innerHTML =\n 'Invalid URL format';\n urlWarning.classList.remove('d-none');\n return;\n }\n\n urlWarning.classList.add('d-none');\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // Update the URL field if requested (when embed options change)\n if (updateUrlField && !parsed.isGeneric) {\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n urlInput.value = embedUrl;\n }\n\n // If text link only is selected, show link preview\n if (values.textLinkOnly) {\n let viewUrl;\n if (parsed.isGeneric) {\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\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 * Handle dialog submission.\n *\n * @param {Object} modal - The modal instance\n */\n async handleDialogueSubmission(modal) {\n const root = modal.getRoot()[0];\n const values = this.getFormValues(root);\n\n if (!values.url) {\n return;\n }\n\n const html = await this.generateIframeHtml(values);\n if (html) {\n if (this.isUpdating && this.selectedIframe) {\n // Replace existing iframe (including wrapper if present)\n // Check for both old wrapper and new overlay wrapper\n const wrapper =\n this.selectedIframe.closest(\n '.tiny-mediacms-iframe-wrapper',\n ) || this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.outerHTML = html;\n } else {\n this.selectedIframe.outerHTML = html;\n }\n this.isUpdating = false;\n // Fire change event to trigger overlay reprocessing\n this.editor.fire('Change');\n } else {\n this.editor.insertContent(html);\n }\n }\n }\n\n /**\n * Handle video removal from the editor.\n *\n * @param {Object} modal - The modal instance\n */\n async handleRemove(modal) {\n // Get confirmation string\n const confirmMessage = await getString(\n 'removeiframeconfirm',\n component,\n );\n\n // Show confirmation dialog\n // eslint-disable-next-line no-alert\n if (!window.confirm(confirmMessage)) {\n return;\n }\n\n if (this.selectedIframe) {\n // Remove the iframe (including wrapper if present)\n const wrapper =\n this.selectedIframe.closest('.tiny-mediacms-iframe-wrapper') ||\n this.selectedIframe.closest('.tiny-iframe-responsive');\n if (wrapper) {\n wrapper.remove();\n } else {\n this.selectedIframe.remove();\n }\n }\n\n // Close the modal\n this.isUpdating = false;\n modal.hide();\n }\n\n /**\n * Register event listeners for the modal.\n *\n * @param {Object} modal - The modal instance\n */\n async registerEventListeners(modal) {\n await modal.getBody();\n const $root = modal.getRoot();\n const root = $root[0];\n\n // Input change handlers\n const form = root.querySelector(Selectors.IFRAME.elements.form);\n\n // URL input\n form.querySelector(Selectors.IFRAME.elements.url).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Embed option checkboxes - these should update the URL field when changed\n [\n Selectors.IFRAME.elements.showTitle,\n Selectors.IFRAME.elements.linkTitle,\n Selectors.IFRAME.elements.showRelated,\n Selectors.IFRAME.elements.showUserAvatar,\n Selectors.IFRAME.elements.startAtEnabled,\n ].forEach((selector) => {\n form.querySelector(selector).addEventListener('change', () =>\n this.handleInputChange(root, true), // Update URL field when embed options change\n );\n });\n\n // Responsive checkbox - doesn't affect URL, only display\n form.querySelector(Selectors.IFRAME.elements.responsive).addEventListener('change', () =>\n this.handleInputChange(root, false),\n );\n\n // 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\n form.querySelector(Selectors.IFRAME.elements.width).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n form.querySelector(Selectors.IFRAME.elements.height).addEventListener(\n 'input',\n () => this.handleInputChange(root),\n );\n\n // Modal events\n $root.on(ModalEvents.save, () => this.handleDialogueSubmission(modal));\n $root.on(ModalEvents.hidden, () => {\n this.currentModal.destroy();\n });\n\n // Remove button handler (only present when updating)\n const removeBtn = root.querySelector(Selectors.IFRAME.actions.remove);\n if (removeBtn) {\n removeBtn.addEventListener('click', () => this.handleRemove(modal));\n }\n\n // Initial preview if we have a URL\n const urlInput = form.querySelector(Selectors.IFRAME.elements.url);\n if (urlInput.value) {\n this.updatePreview(root);\n }\n\n // Tab change handler - load iframe library when switching to iframe library tab\n const iframeLibraryTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabIframeLibraryBtn,\n );\n if (iframeLibraryTabBtn) {\n iframeLibraryTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching for Bootstrap 5\n this.switchToIframeLibraryTab(root);\n\n // Small delay to ensure tab pane is visible before loading iframe\n setTimeout(() => this.handleIframeLibraryTabClick(root), 100);\n });\n // Also handle Bootstrap tab events (if Bootstrap handles it)\n iframeLibraryTabBtn.addEventListener('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n // jQuery Bootstrap 4 event\n const $iframeLibraryTabBtn = window.jQuery\n ? window.jQuery(iframeLibraryTabBtn)\n : null;\n if ($iframeLibraryTabBtn) {\n $iframeLibraryTabBtn.on('shown.bs.tab', () =>\n this.handleIframeLibraryTabClick(root),\n );\n }\n }\n\n // Tab change handler for URL tab\n const urlTabBtn = form.querySelector(\n Selectors.IFRAME.elements.tabUrlBtn,\n );\n if (urlTabBtn) {\n urlTabBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Manually handle tab switching\n this.switchToUrlTab(root);\n });\n }\n\n // Iframe library event listeners\n this.registerIframeLibraryEventListeners(root);\n\n // 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","showTitle","linkTitle","showRelated","showUserAvatar","startAt","secondsToTimeString","parseInt","rawUrl","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","includes","parts","split","isNaN","buildEmbedUrl","parsed","options","set","startAtEnabled","data","editorData","autoConvertOptions","getDefault","key","fallback","_this","isUpdating","undefined","elementid","getElement","id","isupdating","responsive","textLinkOnly","width","height","is16_9","aspectRatio","is4_3","is1_1","isCustom","selectedIframe","getSelectedIframe","getCurrentIframeData","iframeLibraryLoaded","currentModal","IframeModal","create","title","component","templateContext","getTemplateContext","registerEventListeners","node","selection","getNode","nodeName","toLowerCase","iframe","querySelector","wrapper","closest","src","getAttribute","isResponsive","getFormValues","root","form","Selectors","IFRAME","elements","value","checked","values","viewUrl","linkText","str","div","document","createElement","textContent","innerHTML","escapeHtml","hrefUrl","replace","embedUrl","aspectRatioCalcs","custom","context","aspectRatioCalc","aspectRatioValue","html","Templates","renderForPromise","updateUrlField","previewContainer","preview","urlWarning","classList","add","remove","previewWidth","min","scale","previewHeight","round","handleInputChange","clearTimeout","debounceTimer","setTimeout","updatePreview","handleAspectRatioChange","dimensions","aspectRatios","modal","getRoot","generateIframeHtml","outerHTML","fire","insertContent","confirmMessage","window","confirm","hide","getBody","$root","addEventListener","forEach","selector","on","ModalEvents","save","handleDialogueSubmission","hidden","destroy","removeBtn","actions","handleRemove","iframeLibraryTabBtn","tabIframeLibraryBtn","preventDefault","stopPropagation","switchToIframeLibraryTab","handleIframeLibraryTabClick","$iframeLibraryTabBtn","jQuery","urlTabBtn","tabUrlBtn","switchToUrlTab","registerIframeLibraryEventListeners","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/build/selectors.min.js b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js index 06c5ee30..e8d66b3a 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js @@ -1,3 +1,3 @@ -define("tiny_mediacms/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_imagecms_urlentrysubmit",imageBrowser:".openimagecmsbrowser",addUrl:".tiny_imagecms_addurl",deleteImage:".tiny_imagecms_deleteicon"},elements:{form:"form.tiny_imagecms_form",alignSettings:".tiny_imagecms_button",alt:".tiny_imagecms_altentry",altWarning:".tiny_imagecms_altwarning",height:".tiny_imagecms_heightentry",width:".tiny_imagecms_widthentry",url:".tiny_imagecms_urlentry",urlWarning:".tiny_imagecms_urlwarning",size:".tiny_imagecms_size",presentation:".tiny_imagecms_presentation",constrain:".tiny_imagecms_constrain",customStyle:".tiny_imagecms_customstyle",preview:".tiny_imagecms_preview",previewBox:".tiny_imagecms_preview_box",loaderIcon:".tiny_imagecms_loader",loaderIconContainer:".tiny_imagecms_loader_container",insertImage:".tiny_imagecms_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_imagecms_dropzone_container",fileInput:"#tiny_imagecms_fileinput",fileNameLabel:".tiny_imagecms_filename",sizeOriginal:".tiny_imagecms_sizeoriginal",sizeCustom:".tiny_imagecms_sizecustom",properties:".tiny_imagecms_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_mediacms_submit",mediaBrowser:".openmediacmsbrowser"},elements:{form:"form.tiny_mediacms_form",source:".tiny_mediacms_source",track:".tiny_mediacms_track",mediaSource:".tiny_mediacms_media_source",linkSource:".tiny_mediacms_link_source",linkSize:".tiny_mediacms_link_size",posterSource:".tiny_mediacms_poster_source",posterSize:".tiny_mediacms_poster_size",displayOptions:".tiny_mediacms_display_options",name:".tiny_mediacms_name_entry",title:".tiny_mediacms_title_entry",url:".tiny_mediacms_url_entry",width:".tiny_mediacms_width_entry",height:".tiny_mediacms_height_entry",trackSource:".tiny_mediacms_track_source",trackKind:".tiny_mediacms_track_kind_entry",trackLabel:".tiny_mediacms_track_label_entry",trackLang:".tiny_mediacms_track_lang_entry",trackDefault:".tiny_mediacms_track_default",mediaControl:".tiny_mediacms_controls",mediaAutoplay:".tiny_mediacms_autoplay",mediaMute:".tiny_mediacms_mute",mediaLoop:".tiny_mediacms_loop",advancedSettings:".tiny_mediacms_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}},IFRAME:{actions:{remove:'[data-action="remove"]'},elements:{form:"form.tiny_iframecms_form",url:".tiny_iframecms_url",urlWarning:".tiny_iframecms_url_warning",showTitle:".tiny_iframecms_showtitle",linkTitle:".tiny_iframecms_linktitle",showRelated:".tiny_iframecms_showrelated",showUserAvatar:".tiny_iframecms_showuseravatar",responsive:".tiny_iframecms_responsive",startAt:".tiny_iframecms_startat",startAtEnabled:".tiny_iframecms_startat_enabled",aspectRatio:".tiny_iframecms_aspectratio",width:".tiny_iframecms_width",height:".tiny_iframecms_height",preview:".tiny_iframecms_preview",previewContainer:".tiny_iframecms_preview_container",tabs:".tiny_iframecms_tabs",tabUrlBtn:".tiny_iframecms_tab_url_btn",tabIframeLibraryBtn:".tiny_iframecms_tab_iframe_library_btn",paneUrl:".tiny_iframecms_pane_url",paneIframeLibrary:".tiny_iframecms_pane_iframe_library",iframeLibraryContainer:".tiny_iframecms_iframe_library_container",iframeLibraryPlaceholder:".tiny_iframecms_iframe_library_placeholder",iframeLibraryLoading:".tiny_iframecms_iframe_library_loading",iframeLibraryFrame:".tiny_iframecms_iframe_library_frame"},aspectRatios:{"16:9":{width:560,height:315},"4:3":{width:560,height:420},"1:1":{width:400,height:400},custom:null}}},_exports.default})); +define("tiny_mediacms/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_imagecms_urlentrysubmit",imageBrowser:".openimagecmsbrowser",addUrl:".tiny_imagecms_addurl",deleteImage:".tiny_imagecms_deleteicon"},elements:{form:"form.tiny_imagecms_form",alignSettings:".tiny_imagecms_button",alt:".tiny_imagecms_altentry",altWarning:".tiny_imagecms_altwarning",height:".tiny_imagecms_heightentry",width:".tiny_imagecms_widthentry",url:".tiny_imagecms_urlentry",urlWarning:".tiny_imagecms_urlwarning",size:".tiny_imagecms_size",presentation:".tiny_imagecms_presentation",constrain:".tiny_imagecms_constrain",customStyle:".tiny_imagecms_customstyle",preview:".tiny_imagecms_preview",previewBox:".tiny_imagecms_preview_box",loaderIcon:".tiny_imagecms_loader",loaderIconContainer:".tiny_imagecms_loader_container",insertImage:".tiny_imagecms_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_imagecms_dropzone_container",fileInput:"#tiny_imagecms_fileinput",fileNameLabel:".tiny_imagecms_filename",sizeOriginal:".tiny_imagecms_sizeoriginal",sizeCustom:".tiny_imagecms_sizecustom",properties:".tiny_imagecms_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_mediacms_submit",mediaBrowser:".openmediacmsbrowser"},elements:{form:"form.tiny_mediacms_form",source:".tiny_mediacms_source",track:".tiny_mediacms_track",mediaSource:".tiny_mediacms_media_source",linkSource:".tiny_mediacms_link_source",linkSize:".tiny_mediacms_link_size",posterSource:".tiny_mediacms_poster_source",posterSize:".tiny_mediacms_poster_size",displayOptions:".tiny_mediacms_display_options",name:".tiny_mediacms_name_entry",title:".tiny_mediacms_title_entry",url:".tiny_mediacms_url_entry",width:".tiny_mediacms_width_entry",height:".tiny_mediacms_height_entry",trackSource:".tiny_mediacms_track_source",trackKind:".tiny_mediacms_track_kind_entry",trackLabel:".tiny_mediacms_track_label_entry",trackLang:".tiny_mediacms_track_lang_entry",trackDefault:".tiny_mediacms_track_default",mediaControl:".tiny_mediacms_controls",mediaAutoplay:".tiny_mediacms_autoplay",mediaMute:".tiny_mediacms_mute",mediaLoop:".tiny_mediacms_loop",advancedSettings:".tiny_mediacms_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}},IFRAME:{actions:{remove:'[data-action="remove"]'},elements:{form:"form.tiny_iframecms_form",url:".tiny_iframecms_url",urlWarning:".tiny_iframecms_url_warning",showTitle:".tiny_iframecms_showtitle",linkTitle:".tiny_iframecms_linktitle",showRelated:".tiny_iframecms_showrelated",showUserAvatar:".tiny_iframecms_showuseravatar",responsive:".tiny_iframecms_responsive",textLinkOnly:".tiny_iframecms_textlinkonly",startAt:".tiny_iframecms_startat",startAtEnabled:".tiny_iframecms_startat_enabled",aspectRatio:".tiny_iframecms_aspectratio",width:".tiny_iframecms_width",height:".tiny_iframecms_height",preview:".tiny_iframecms_preview",previewContainer:".tiny_iframecms_preview_container",tabs:".tiny_iframecms_tabs",tabUrlBtn:".tiny_iframecms_tab_url_btn",tabIframeLibraryBtn:".tiny_iframecms_tab_iframe_library_btn",paneUrl:".tiny_iframecms_pane_url",paneIframeLibrary:".tiny_iframecms_pane_iframe_library",iframeLibraryContainer:".tiny_iframecms_iframe_library_container",iframeLibraryPlaceholder:".tiny_iframecms_iframe_library_placeholder",iframeLibraryLoading:".tiny_iframecms_iframe_library_loading",iframeLibraryFrame:".tiny_iframecms_iframe_library_frame"},aspectRatios:{"16:9":{width:560,height:315},"4:3":{width:560,height:420},"1:1":{width:400,height:400},custom:null}}},_exports.default})); //# sourceMappingURL=selectors.min.js.map \ No newline at end of file diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js.map b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js.map index e90972a5..84c823ec 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js.map +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Media plugin helper function to build queryable data selectors.\n *\n * @module tiny_mediacms/selectors\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n IMAGE: {\n actions: {\n submit: '.tiny_imagecms_urlentrysubmit',\n imageBrowser: '.openimagecmsbrowser',\n addUrl: '.tiny_imagecms_addurl',\n deleteImage: '.tiny_imagecms_deleteicon',\n },\n elements: {\n form: 'form.tiny_imagecms_form',\n alignSettings: '.tiny_imagecms_button',\n alt: '.tiny_imagecms_altentry',\n altWarning: '.tiny_imagecms_altwarning',\n height: '.tiny_imagecms_heightentry',\n width: '.tiny_imagecms_widthentry',\n url: '.tiny_imagecms_urlentry',\n urlWarning: '.tiny_imagecms_urlwarning',\n size: '.tiny_imagecms_size',\n presentation: '.tiny_imagecms_presentation',\n constrain: '.tiny_imagecms_constrain',\n customStyle: '.tiny_imagecms_customstyle',\n preview: '.tiny_imagecms_preview',\n previewBox: '.tiny_imagecms_preview_box',\n loaderIcon: '.tiny_imagecms_loader',\n loaderIconContainer: '.tiny_imagecms_loader_container',\n insertImage: '.tiny_imagecms_insert_image',\n modalFooter: '.modal-footer',\n dropzoneContainer: '.tiny_imagecms_dropzone_container',\n fileInput: '#tiny_imagecms_fileinput',\n fileNameLabel: '.tiny_imagecms_filename',\n sizeOriginal: '.tiny_imagecms_sizeoriginal',\n sizeCustom: '.tiny_imagecms_sizecustom',\n properties: '.tiny_imagecms_properties',\n },\n styles: {\n responsive: 'img-fluid',\n },\n },\n EMBED: {\n actions: {\n submit: '.tiny_mediacms_submit',\n mediaBrowser: '.openmediacmsbrowser',\n },\n elements: {\n form: 'form.tiny_mediacms_form',\n source: '.tiny_mediacms_source',\n track: '.tiny_mediacms_track',\n mediaSource: '.tiny_mediacms_media_source',\n linkSource: '.tiny_mediacms_link_source',\n linkSize: '.tiny_mediacms_link_size',\n posterSource: '.tiny_mediacms_poster_source',\n posterSize: '.tiny_mediacms_poster_size',\n displayOptions: '.tiny_mediacms_display_options',\n name: '.tiny_mediacms_name_entry',\n title: '.tiny_mediacms_title_entry',\n url: '.tiny_mediacms_url_entry',\n width: '.tiny_mediacms_width_entry',\n height: '.tiny_mediacms_height_entry',\n trackSource: '.tiny_mediacms_track_source',\n trackKind: '.tiny_mediacms_track_kind_entry',\n trackLabel: '.tiny_mediacms_track_label_entry',\n trackLang: '.tiny_mediacms_track_lang_entry',\n trackDefault: '.tiny_mediacms_track_default',\n mediaControl: '.tiny_mediacms_controls',\n mediaAutoplay: '.tiny_mediacms_autoplay',\n mediaMute: '.tiny_mediacms_mute',\n mediaLoop: '.tiny_mediacms_loop',\n advancedSettings: '.tiny_mediacms_advancedsettings',\n linkTab: 'li[data-medium-type=\"link\"]',\n videoTab: 'li[data-medium-type=\"video\"]',\n audioTab: 'li[data-medium-type=\"audio\"]',\n linkPane: '.tab-pane[data-medium-type=\"link\"]',\n videoPane: '.tab-pane[data-medium-type=\"video\"]',\n audioPane: '.tab-pane[data-medium-type=\"audio\"]',\n trackSubtitlesTab: 'li[data-track-kind=\"subtitles\"]',\n trackCaptionsTab: 'li[data-track-kind=\"captions\"]',\n trackDescriptionsTab: 'li[data-track-kind=\"descriptions\"]',\n trackChaptersTab: 'li[data-track-kind=\"chapters\"]',\n trackMetadataTab: 'li[data-track-kind=\"metadata\"]',\n trackSubtitlesPane: '.tab-pane[data-track-kind=\"subtitles\"]',\n trackCaptionsPane: '.tab-pane[data-track-kind=\"captions\"]',\n trackDescriptionsPane: '.tab-pane[data-track-kind=\"descriptions\"]',\n trackChaptersPane: '.tab-pane[data-track-kind=\"chapters\"]',\n trackMetadataPane: '.tab-pane[data-track-kind=\"metadata\"]',\n },\n mediaTypes: {\n link: 'LINK',\n video: 'VIDEO',\n audio: 'AUDIO',\n },\n trackKinds: {\n subtitles: 'SUBTITLES',\n captions: 'CAPTIONS',\n descriptions: 'DESCRIPTIONS',\n chapters: 'CHAPTERS',\n metadata: 'METADATA',\n },\n },\n IFRAME: {\n actions: {\n remove: '[data-action=\"remove\"]',\n },\n elements: {\n form: 'form.tiny_iframecms_form',\n url: '.tiny_iframecms_url',\n urlWarning: '.tiny_iframecms_url_warning',\n showTitle: '.tiny_iframecms_showtitle',\n linkTitle: '.tiny_iframecms_linktitle',\n showRelated: '.tiny_iframecms_showrelated',\n showUserAvatar: '.tiny_iframecms_showuseravatar',\n responsive: '.tiny_iframecms_responsive',\n startAt: '.tiny_iframecms_startat',\n startAtEnabled: '.tiny_iframecms_startat_enabled',\n aspectRatio: '.tiny_iframecms_aspectratio',\n width: '.tiny_iframecms_width',\n height: '.tiny_iframecms_height',\n preview: '.tiny_iframecms_preview',\n previewContainer: '.tiny_iframecms_preview_container',\n // Tab elements\n tabs: '.tiny_iframecms_tabs',\n tabUrlBtn: '.tiny_iframecms_tab_url_btn',\n tabIframeLibraryBtn: '.tiny_iframecms_tab_iframe_library_btn',\n paneUrl: '.tiny_iframecms_pane_url',\n paneIframeLibrary: '.tiny_iframecms_pane_iframe_library',\n // Iframe library elements\n iframeLibraryContainer: '.tiny_iframecms_iframe_library_container',\n iframeLibraryPlaceholder:\n '.tiny_iframecms_iframe_library_placeholder',\n iframeLibraryLoading: '.tiny_iframecms_iframe_library_loading',\n iframeLibraryFrame: '.tiny_iframecms_iframe_library_frame',\n },\n aspectRatios: {\n '16:9': { width: 560, height: 315 },\n '4:3': { width: 560, height: 420 },\n '1:1': { width: 400, height: 400 },\n custom: null,\n },\n },\n};\n"],"names":["IMAGE","actions","submit","imageBrowser","addUrl","deleteImage","elements","form","alignSettings","alt","altWarning","height","width","url","urlWarning","size","presentation","constrain","customStyle","preview","previewBox","loaderIcon","loaderIconContainer","insertImage","modalFooter","dropzoneContainer","fileInput","fileNameLabel","sizeOriginal","sizeCustom","properties","styles","responsive","EMBED","mediaBrowser","source","track","mediaSource","linkSource","linkSize","posterSource","posterSize","displayOptions","name","title","trackSource","trackKind","trackLabel","trackLang","trackDefault","mediaControl","mediaAutoplay","mediaMute","mediaLoop","advancedSettings","linkTab","videoTab","audioTab","linkPane","videoPane","audioPane","trackSubtitlesTab","trackCaptionsTab","trackDescriptionsTab","trackChaptersTab","trackMetadataTab","trackSubtitlesPane","trackCaptionsPane","trackDescriptionsPane","trackChaptersPane","trackMetadataPane","mediaTypes","link","video","audio","trackKinds","subtitles","captions","descriptions","chapters","metadata","IFRAME","remove","showTitle","linkTitle","showRelated","showUserAvatar","startAt","startAtEnabled","aspectRatio","previewContainer","tabs","tabUrlBtn","tabIframeLibraryBtn","paneUrl","paneIframeLibrary","iframeLibraryContainer","iframeLibraryPlaceholder","iframeLibraryLoading","iframeLibraryFrame","aspectRatios","custom"],"mappings":"yKAuBe,CACXA,MAAO,CACHC,QAAS,CACLC,OAAQ,gCACRC,aAAc,uBACdC,OAAQ,wBACRC,YAAa,6BAEjBC,SAAU,CACNC,KAAM,0BACNC,cAAe,wBACfC,IAAK,0BACLC,WAAY,4BACZC,OAAQ,6BACRC,MAAO,4BACPC,IAAK,0BACLC,WAAY,4BACZC,KAAM,sBACNC,aAAc,8BACdC,UAAW,2BACXC,YAAa,6BACbC,QAAS,yBACTC,WAAY,6BACZC,WAAY,wBACZC,oBAAqB,kCACrBC,YAAa,8BACbC,YAAa,gBACbC,kBAAmB,oCACnBC,UAAW,2BACXC,cAAe,0BACfC,aAAc,8BACdC,WAAY,4BACZC,WAAY,6BAEhBC,OAAQ,CACJC,WAAY,cAGpBC,MAAO,CACHhC,QAAS,CACLC,OAAQ,wBACRgC,aAAc,wBAElB5B,SAAU,CACNC,KAAM,0BACN4B,OAAQ,wBACRC,MAAO,uBACPC,YAAa,8BACbC,WAAY,6BACZC,SAAU,2BACVC,aAAc,+BACdC,WAAY,6BACZC,eAAgB,iCAChBC,KAAM,4BACNC,MAAO,6BACP/B,IAAK,2BACLD,MAAO,6BACPD,OAAQ,8BACRkC,YAAa,8BACbC,UAAW,kCACXC,WAAY,mCACZC,UAAW,kCACXC,aAAc,+BACdC,aAAc,0BACdC,cAAe,0BACfC,UAAW,sBACXC,UAAW,sBACXC,iBAAkB,kCAClBC,QAAS,8BACTC,SAAU,+BACVC,SAAU,+BACVC,SAAU,qCACVC,UAAW,sCACXC,UAAW,sCACXC,kBAAmB,kCACnBC,iBAAkB,iCAClBC,qBAAsB,qCACtBC,iBAAkB,iCAClBC,iBAAkB,iCAClBC,mBAAoB,yCACpBC,kBAAmB,wCACnBC,sBAAuB,4CACvBC,kBAAmB,wCACnBC,kBAAmB,yCAEvBC,WAAY,CACRC,KAAM,OACNC,MAAO,QACPC,MAAO,SAEXC,WAAY,CACRC,UAAW,YACXC,SAAU,WACVC,aAAc,eACdC,SAAU,WACVC,SAAU,aAGlBC,OAAQ,CACJhF,QAAS,CACLiF,OAAQ,0BAEZ5E,SAAU,CACNC,KAAM,2BACNM,IAAK,sBACLC,WAAY,8BACZqE,UAAW,4BACXC,UAAW,4BACXC,YAAa,8BACbC,eAAgB,iCAChBtD,WAAY,6BACZuD,QAAS,0BACTC,eAAgB,kCAChBC,YAAa,8BACb7E,MAAO,wBACPD,OAAQ,yBACRQ,QAAS,0BACTuE,iBAAkB,oCAElBC,KAAM,uBACNC,UAAW,8BACXC,oBAAqB,yCACrBC,QAAS,2BACTC,kBAAmB,sCAEnBC,uBAAwB,2CACxBC,yBACI,6CACJC,qBAAsB,yCACtBC,mBAAoB,wCAExBC,aAAc,QACF,CAAExF,MAAO,IAAKD,OAAQ,WACvB,CAAEC,MAAO,IAAKD,OAAQ,WACtB,CAAEC,MAAO,IAAKD,OAAQ,KAC7B0F,OAAQ"} \ No newline at end of file +{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Media plugin helper function to build queryable data selectors.\n *\n * @module tiny_mediacms/selectors\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n IMAGE: {\n actions: {\n submit: '.tiny_imagecms_urlentrysubmit',\n imageBrowser: '.openimagecmsbrowser',\n addUrl: '.tiny_imagecms_addurl',\n deleteImage: '.tiny_imagecms_deleteicon',\n },\n elements: {\n form: 'form.tiny_imagecms_form',\n alignSettings: '.tiny_imagecms_button',\n alt: '.tiny_imagecms_altentry',\n altWarning: '.tiny_imagecms_altwarning',\n height: '.tiny_imagecms_heightentry',\n width: '.tiny_imagecms_widthentry',\n url: '.tiny_imagecms_urlentry',\n urlWarning: '.tiny_imagecms_urlwarning',\n size: '.tiny_imagecms_size',\n presentation: '.tiny_imagecms_presentation',\n constrain: '.tiny_imagecms_constrain',\n customStyle: '.tiny_imagecms_customstyle',\n preview: '.tiny_imagecms_preview',\n previewBox: '.tiny_imagecms_preview_box',\n loaderIcon: '.tiny_imagecms_loader',\n loaderIconContainer: '.tiny_imagecms_loader_container',\n insertImage: '.tiny_imagecms_insert_image',\n modalFooter: '.modal-footer',\n dropzoneContainer: '.tiny_imagecms_dropzone_container',\n fileInput: '#tiny_imagecms_fileinput',\n fileNameLabel: '.tiny_imagecms_filename',\n sizeOriginal: '.tiny_imagecms_sizeoriginal',\n sizeCustom: '.tiny_imagecms_sizecustom',\n properties: '.tiny_imagecms_properties',\n },\n styles: {\n responsive: 'img-fluid',\n },\n },\n EMBED: {\n actions: {\n submit: '.tiny_mediacms_submit',\n mediaBrowser: '.openmediacmsbrowser',\n },\n elements: {\n form: 'form.tiny_mediacms_form',\n source: '.tiny_mediacms_source',\n track: '.tiny_mediacms_track',\n mediaSource: '.tiny_mediacms_media_source',\n linkSource: '.tiny_mediacms_link_source',\n linkSize: '.tiny_mediacms_link_size',\n posterSource: '.tiny_mediacms_poster_source',\n posterSize: '.tiny_mediacms_poster_size',\n displayOptions: '.tiny_mediacms_display_options',\n name: '.tiny_mediacms_name_entry',\n title: '.tiny_mediacms_title_entry',\n url: '.tiny_mediacms_url_entry',\n width: '.tiny_mediacms_width_entry',\n height: '.tiny_mediacms_height_entry',\n trackSource: '.tiny_mediacms_track_source',\n trackKind: '.tiny_mediacms_track_kind_entry',\n trackLabel: '.tiny_mediacms_track_label_entry',\n trackLang: '.tiny_mediacms_track_lang_entry',\n trackDefault: '.tiny_mediacms_track_default',\n mediaControl: '.tiny_mediacms_controls',\n mediaAutoplay: '.tiny_mediacms_autoplay',\n mediaMute: '.tiny_mediacms_mute',\n mediaLoop: '.tiny_mediacms_loop',\n advancedSettings: '.tiny_mediacms_advancedsettings',\n linkTab: 'li[data-medium-type=\"link\"]',\n videoTab: 'li[data-medium-type=\"video\"]',\n audioTab: 'li[data-medium-type=\"audio\"]',\n linkPane: '.tab-pane[data-medium-type=\"link\"]',\n videoPane: '.tab-pane[data-medium-type=\"video\"]',\n audioPane: '.tab-pane[data-medium-type=\"audio\"]',\n trackSubtitlesTab: 'li[data-track-kind=\"subtitles\"]',\n trackCaptionsTab: 'li[data-track-kind=\"captions\"]',\n trackDescriptionsTab: 'li[data-track-kind=\"descriptions\"]',\n trackChaptersTab: 'li[data-track-kind=\"chapters\"]',\n trackMetadataTab: 'li[data-track-kind=\"metadata\"]',\n trackSubtitlesPane: '.tab-pane[data-track-kind=\"subtitles\"]',\n trackCaptionsPane: '.tab-pane[data-track-kind=\"captions\"]',\n trackDescriptionsPane: '.tab-pane[data-track-kind=\"descriptions\"]',\n trackChaptersPane: '.tab-pane[data-track-kind=\"chapters\"]',\n trackMetadataPane: '.tab-pane[data-track-kind=\"metadata\"]',\n },\n mediaTypes: {\n link: 'LINK',\n video: 'VIDEO',\n audio: 'AUDIO',\n },\n trackKinds: {\n subtitles: 'SUBTITLES',\n captions: 'CAPTIONS',\n descriptions: 'DESCRIPTIONS',\n chapters: 'CHAPTERS',\n metadata: 'METADATA',\n },\n },\n IFRAME: {\n actions: {\n remove: '[data-action=\"remove\"]',\n },\n elements: {\n form: 'form.tiny_iframecms_form',\n url: '.tiny_iframecms_url',\n urlWarning: '.tiny_iframecms_url_warning',\n showTitle: '.tiny_iframecms_showtitle',\n linkTitle: '.tiny_iframecms_linktitle',\n showRelated: '.tiny_iframecms_showrelated',\n showUserAvatar: '.tiny_iframecms_showuseravatar',\n responsive: '.tiny_iframecms_responsive',\n textLinkOnly: '.tiny_iframecms_textlinkonly',\n startAt: '.tiny_iframecms_startat',\n startAtEnabled: '.tiny_iframecms_startat_enabled',\n aspectRatio: '.tiny_iframecms_aspectratio',\n width: '.tiny_iframecms_width',\n height: '.tiny_iframecms_height',\n preview: '.tiny_iframecms_preview',\n previewContainer: '.tiny_iframecms_preview_container',\n // Tab elements\n tabs: '.tiny_iframecms_tabs',\n tabUrlBtn: '.tiny_iframecms_tab_url_btn',\n tabIframeLibraryBtn: '.tiny_iframecms_tab_iframe_library_btn',\n paneUrl: '.tiny_iframecms_pane_url',\n paneIframeLibrary: '.tiny_iframecms_pane_iframe_library',\n // Iframe library elements\n iframeLibraryContainer: '.tiny_iframecms_iframe_library_container',\n iframeLibraryPlaceholder:\n '.tiny_iframecms_iframe_library_placeholder',\n iframeLibraryLoading: '.tiny_iframecms_iframe_library_loading',\n iframeLibraryFrame: '.tiny_iframecms_iframe_library_frame',\n },\n aspectRatios: {\n '16:9': { width: 560, height: 315 },\n '4:3': { width: 560, height: 420 },\n '1:1': { width: 400, height: 400 },\n custom: null,\n },\n },\n};\n"],"names":["IMAGE","actions","submit","imageBrowser","addUrl","deleteImage","elements","form","alignSettings","alt","altWarning","height","width","url","urlWarning","size","presentation","constrain","customStyle","preview","previewBox","loaderIcon","loaderIconContainer","insertImage","modalFooter","dropzoneContainer","fileInput","fileNameLabel","sizeOriginal","sizeCustom","properties","styles","responsive","EMBED","mediaBrowser","source","track","mediaSource","linkSource","linkSize","posterSource","posterSize","displayOptions","name","title","trackSource","trackKind","trackLabel","trackLang","trackDefault","mediaControl","mediaAutoplay","mediaMute","mediaLoop","advancedSettings","linkTab","videoTab","audioTab","linkPane","videoPane","audioPane","trackSubtitlesTab","trackCaptionsTab","trackDescriptionsTab","trackChaptersTab","trackMetadataTab","trackSubtitlesPane","trackCaptionsPane","trackDescriptionsPane","trackChaptersPane","trackMetadataPane","mediaTypes","link","video","audio","trackKinds","subtitles","captions","descriptions","chapters","metadata","IFRAME","remove","showTitle","linkTitle","showRelated","showUserAvatar","textLinkOnly","startAt","startAtEnabled","aspectRatio","previewContainer","tabs","tabUrlBtn","tabIframeLibraryBtn","paneUrl","paneIframeLibrary","iframeLibraryContainer","iframeLibraryPlaceholder","iframeLibraryLoading","iframeLibraryFrame","aspectRatios","custom"],"mappings":"yKAuBe,CACXA,MAAO,CACHC,QAAS,CACLC,OAAQ,gCACRC,aAAc,uBACdC,OAAQ,wBACRC,YAAa,6BAEjBC,SAAU,CACNC,KAAM,0BACNC,cAAe,wBACfC,IAAK,0BACLC,WAAY,4BACZC,OAAQ,6BACRC,MAAO,4BACPC,IAAK,0BACLC,WAAY,4BACZC,KAAM,sBACNC,aAAc,8BACdC,UAAW,2BACXC,YAAa,6BACbC,QAAS,yBACTC,WAAY,6BACZC,WAAY,wBACZC,oBAAqB,kCACrBC,YAAa,8BACbC,YAAa,gBACbC,kBAAmB,oCACnBC,UAAW,2BACXC,cAAe,0BACfC,aAAc,8BACdC,WAAY,4BACZC,WAAY,6BAEhBC,OAAQ,CACJC,WAAY,cAGpBC,MAAO,CACHhC,QAAS,CACLC,OAAQ,wBACRgC,aAAc,wBAElB5B,SAAU,CACNC,KAAM,0BACN4B,OAAQ,wBACRC,MAAO,uBACPC,YAAa,8BACbC,WAAY,6BACZC,SAAU,2BACVC,aAAc,+BACdC,WAAY,6BACZC,eAAgB,iCAChBC,KAAM,4BACNC,MAAO,6BACP/B,IAAK,2BACLD,MAAO,6BACPD,OAAQ,8BACRkC,YAAa,8BACbC,UAAW,kCACXC,WAAY,mCACZC,UAAW,kCACXC,aAAc,+BACdC,aAAc,0BACdC,cAAe,0BACfC,UAAW,sBACXC,UAAW,sBACXC,iBAAkB,kCAClBC,QAAS,8BACTC,SAAU,+BACVC,SAAU,+BACVC,SAAU,qCACVC,UAAW,sCACXC,UAAW,sCACXC,kBAAmB,kCACnBC,iBAAkB,iCAClBC,qBAAsB,qCACtBC,iBAAkB,iCAClBC,iBAAkB,iCAClBC,mBAAoB,yCACpBC,kBAAmB,wCACnBC,sBAAuB,4CACvBC,kBAAmB,wCACnBC,kBAAmB,yCAEvBC,WAAY,CACRC,KAAM,OACNC,MAAO,QACPC,MAAO,SAEXC,WAAY,CACRC,UAAW,YACXC,SAAU,WACVC,aAAc,eACdC,SAAU,WACVC,SAAU,aAGlBC,OAAQ,CACJhF,QAAS,CACLiF,OAAQ,0BAEZ5E,SAAU,CACNC,KAAM,2BACNM,IAAK,sBACLC,WAAY,8BACZqE,UAAW,4BACXC,UAAW,4BACXC,YAAa,8BACbC,eAAgB,iCAChBtD,WAAY,6BACZuD,aAAc,+BACdC,QAAS,0BACTC,eAAgB,kCAChBC,YAAa,8BACb9E,MAAO,wBACPD,OAAQ,yBACRQ,QAAS,0BACTwE,iBAAkB,oCAElBC,KAAM,uBACNC,UAAW,8BACXC,oBAAqB,yCACrBC,QAAS,2BACTC,kBAAmB,sCAEnBC,uBAAwB,2CACxBC,yBACI,6CACJC,qBAAsB,yCACtBC,mBAAoB,wCAExBC,aAAc,QACF,CAAEzF,MAAO,IAAKD,OAAQ,WACvB,CAAEC,MAAO,IAAKD,OAAQ,WACtB,CAAEC,MAAO,IAAKD,OAAQ,KAC7B2F,OAAQ"} \ 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 5e4b95f0..b18e2b3d 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js @@ -237,6 +237,7 @@ export default class IframeEmbed { showRelated: getDefault('showRelated'), showUserAvatar: getDefault('showUserAvatar'), responsive: data.responsive !== false, + textLinkOnly: data.textLinkOnly || false, startAtEnabled: data.startAtEnabled || false, startAt: data.startAt || '0:00', width: data.width || 560, @@ -350,6 +351,8 @@ export default class IframeEmbed { ).checked, responsive: form.querySelector(Selectors.IFRAME.elements.responsive) .checked, + textLinkOnly: form.querySelector(Selectors.IFRAME.elements.textLinkOnly) + .checked, startAtEnabled: form.querySelector( Selectors.IFRAME.elements.startAtEnabled, ).checked, @@ -371,7 +374,7 @@ export default class IframeEmbed { } /** - * Generate the iframe HTML. + * Generate the iframe HTML or text link. * * @param {Object} values - Form values * @returns {Promise} Generated HTML @@ -382,6 +385,32 @@ export default class IframeEmbed { return ''; } + // If user selected "text link only", generate a link instead of iframe + if (values.textLinkOnly) { + // Build the view URL (not embed URL) for the link + let viewUrl; + if (parsed.isGeneric) { + viewUrl = parsed.rawUrl; + } else { + viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`; + } + + // Use the full URL as the link text + // Escape HTML entities in the URL for safe display + const escapeHtml = (str) => { + const div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + }; + + const linkText = escapeHtml(viewUrl); + const hrefUrl = viewUrl.replace(/"/g, '"'); + + // Add data attribute to mark this as a text-only link + // This prevents the filter from converting it to an LTI launch iframe + return `

${linkText}

`; + } + const embedUrl = this.buildEmbedUrl(parsed, values); // Calculate aspect ratio values for CSS @@ -448,21 +477,49 @@ export default class IframeEmbed { urlInput.value = embedUrl; } - // Show a scaled preview - const previewWidth = Math.min(values.width, 400); - const scale = previewWidth / values.width; - const previewHeight = Math.round(values.height * scale); + // If text link only is selected, show link preview + if (values.textLinkOnly) { + let viewUrl; + if (parsed.isGeneric) { + viewUrl = parsed.rawUrl; + } else { + viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`; + } - previewContainer.innerHTML = ` - - `; + // Escape HTML entities for safe display + const escapeHtml = (str) => { + const div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + }; + + const linkText = escapeHtml(viewUrl); + const hrefUrl = viewUrl.replace(/"/g, '"'); + + previewContainer.innerHTML = ` +
+ Text link preview:
+ ${linkText} +
This link will not be auto-converted by the MediaCMS filter. +
+ `; + } else { + // Show a scaled preview + const previewWidth = Math.min(values.width, 400); + const scale = previewWidth / values.width; + const previewHeight = Math.round(values.height * scale); + + previewContainer.innerHTML = ` + + `; + } } /** @@ -609,6 +666,11 @@ export default class IframeEmbed { this.handleInputChange(root, false), ); + // Text link only checkbox - doesn't affect URL, only output format + form.querySelector(Selectors.IFRAME.elements.textLinkOnly).addEventListener('change', () => + this.handleInputChange(root, false), + ); + // Start at time input - should update URL field form.querySelector(Selectors.IFRAME.elements.startAt).addEventListener( 'input', @@ -695,8 +757,15 @@ export default class IframeEmbed { // Iframe library event listeners this.registerIframeLibraryEventListeners(root); - // Since My Media tab is now default/active, load it immediately on modal open - setTimeout(() => this.handleIframeLibraryTabClick(root), 100); + // If editing, Configure tab is active - update preview immediately + // If inserting, My Media tab is active - load the library + if (this.isUpdating) { + // When editing, Configure tab is already active, just update preview + setTimeout(() => this.updatePreview(root), 100); + } else { + // When inserting, load My Media library tab + setTimeout(() => this.handleIframeLibraryTabClick(root), 100); + } } /** diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/selectors.js b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/selectors.js index e5149c16..99a0de56 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/selectors.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/selectors.js @@ -132,6 +132,7 @@ export default { showRelated: '.tiny_iframecms_showrelated', showUserAvatar: '.tiny_iframecms_showuseravatar', responsive: '.tiny_iframecms_responsive', + textLinkOnly: '.tiny_iframecms_textlinkonly', startAt: '.tiny_iframecms_startat', startAtEnabled: '.tiny_iframecms_startat_enabled', aspectRatio: '.tiny_iframecms_aspectratio', diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php b/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php index f88ffaa4..d3529616 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/lang/en/tiny_mediacms.php @@ -124,6 +124,7 @@ $string['linktitle'] = 'Link title'; $string['showrelated'] = 'Show related'; $string['showuseravatar'] = 'Show user avatar'; $string['responsive'] = 'Responsive'; +$string['textlinkonly'] = 'Insert text link only'; $string['startat'] = 'Start at'; $string['aspectratio'] = 'Aspect Ratio'; $string['aspectratio_16_9'] = '16:9'; diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache b/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache index 6096f12e..784a6562 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_modal.mustache @@ -31,19 +31,19 @@