diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/README.md b/lms-plugins/mediacms-moodle/tiny/mediacms/README.md index c4352f7b..80c73a10 100644 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/README.md +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/README.md @@ -4,13 +4,15 @@ A TinyMCE editor plugin for Moodle that provides media embedding capabilities wi ## Build Information - +### Preparation 1. Get and extract Moodle 5.1 2. cp -r lms-plugins/mediacms-moodle/tiny/mediacms/ moodle/public/lib/editor/tiny/plugins/ 3. nvm use 22 && cd moodle/public && npm install -4. npx grunt amd --root=lib/editor/tiny/plugins/mediacms -# i've noticed that this fails, so this should work: npx grunt amd +### Actual build +4. cd lib/editor/tiny/plugins/mediacms && npx grunt amd + +### Test the output 5. To test the output: cp * ../../../../../../../lms-plugins/mediacms-moodle/tiny/mediacms/ -r diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js index 3c4d25cf..b4f9dcb7 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js @@ -5,6 +5,6 @@ define("tiny_mediacms/commands",["exports","core/str","./common","./iframeembed" * @module tiny_mediacms/commands * @copyright 2022 Huong Nguyen * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0,_iframeembed=(obj=_iframeembed)&&obj.__esModule?obj:{default:obj};const isIframe=node=>"iframe"===node.nodeName.toLowerCase()||node.classList&&node.classList.contains("tiny-iframe-responsive")||node.classList&&node.classList.contains("tiny-mediacms-iframe-wrapper"),setupIframeOverlays=(editor,handleIframeAction)=>{const processIframes=()=>{const editorBody=editor.getBody();if(!editorBody)return;editorBody.querySelectorAll("iframe").forEach((iframe=>{var _iframe$parentElement;if(null!==(_iframe$parentElement=iframe.parentElement)&&void 0!==_iframe$parentElement&&_iframe$parentElement.classList.contains("tiny-mediacms-iframe-wrapper"))return;if(iframe.hasAttribute("data-mce-object")||iframe.hasAttribute("data-mce-placeholder"))return;const wrapper=editor.getDoc().createElement("div");wrapper.className="tiny-mediacms-iframe-wrapper",wrapper.setAttribute("contenteditable","false");const editBtn=editor.getDoc().createElement("button");editBtn.className="tiny-mediacms-edit-btn",editBtn.setAttribute("type","button"),editBtn.setAttribute("title","Edit video embed options"),editBtn.textContent="EDIT",iframe.parentNode.insertBefore(wrapper,iframe),wrapper.appendChild(iframe),wrapper.appendChild(editBtn)}))},handleOverlayClick=e=>{const editBtn=e.target.closest(".tiny-mediacms-edit-btn");if(!editBtn)return;e.preventDefault(),e.stopPropagation();const wrapper=editBtn.closest(".tiny-mediacms-iframe-wrapper");if(!wrapper)return;wrapper.querySelector("iframe")&&(editor.selection.select(wrapper),handleIframeAction())};editor.on("init",(()=>{(()=>{const editorDoc=editor.getDoc();if(!editorDoc)return;if(editorDoc.getElementById("tiny-mediacms-overlay-styles"))return;const style=editorDoc.createElement("style");style.id="tiny-mediacms-overlay-styles",style.textContent="\n .tiny-mediacms-iframe-wrapper {\n display: inline-block;\n position: relative;\n line-height: 0;\n vertical-align: top;\n }\n .tiny-mediacms-iframe-wrapper iframe {\n display: block;\n }\n .tiny-mediacms-edit-btn {\n position: absolute;\n top: 5px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.7);\n color: #ffffff;\n border: none;\n border-radius: 3px;\n cursor: pointer;\n z-index: 10;\n padding: 4px 12px;\n margin: 0;\n font-size: 12px;\n font-weight: bold;\n text-decoration: none;\n box-shadow: 0 2px 4px rgba(0,0,0,0.3);\n transition: background 0.15s, box-shadow 0.15s;\n display: inline-block;\n box-sizing: border-box;\n }\n .tiny-mediacms-edit-btn:hover {\n background: rgba(0, 0, 0, 0.85);\n box-shadow: 0 3px 6px rgba(0,0,0,0.4);\n }\n ",editorDoc.head.appendChild(style)})(),processIframes(),editor.getBody().addEventListener("click",handleOverlayClick)})),editor.on("SetContent",(()=>{processIframes()})),editor.on("PastePostProcess",(()=>{setTimeout(processIframes,100)})),editor.on("Undo Redo",(()=>{processIframes()})),editor.on("Change",(()=>{setTimeout(processIframes,50)})),editor.on("NodeChange",(()=>{processIframes()}))};_exports.getSetup=async()=>{const[iframeButtonText]=await(0,_str.getStrings)(["iframebuttontitle"].map((key=>({key:key,component:_common.component})))),[iframeButtonImage]=await Promise.all([(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{((editor,iframeButtonText,iframeButtonImage)=>{const handleIframeAction=()=>{new _iframeembed.default(editor).displayDialogue()};editor.ui.registry.addIcon(_common.iframeIcon,iframeButtonImage.html),editor.ui.registry.addToggleButton(_common.iframeButtonName,{icon:_common.iframeIcon,tooltip:iframeButtonText,onAction:handleIframeAction,onSetup:api=>editor.selection.selectorChangedWithUnbind("iframe:not([data-mce-object]):not([data-mce-placeholder]),.tiny-iframe-responsive,.tiny-mediacms-iframe-wrapper",api.setActive).unbind}),editor.ui.registry.addMenuItem(_common.iframeMenuItemName,{icon:_common.iframeIcon,text:iframeButtonText,onAction:handleIframeAction}),editor.ui.registry.addContextToolbar(_common.iframeButtonName,{predicate:isIframe,items:_common.iframeButtonName,position:"node",scope:"node"}),editor.ui.registry.addContextMenu(_common.iframeButtonName,{update:isIframe}),setupIframeOverlays(editor,handleIframeAction)})(editor,iframeButtonText,iframeButtonImage)}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0,_iframeembed=(obj=_iframeembed)&&obj.__esModule?obj:{default:obj};const isIframe=node=>"iframe"===node.nodeName.toLowerCase()||node.classList&&node.classList.contains("tiny-iframe-responsive")||node.classList&&node.classList.contains("tiny-mediacms-iframe-wrapper"),setupIframeOverlays=(editor,handleIframeAction)=>{const processIframes=()=>{const editorBody=editor.getBody();if(!editorBody)return;editorBody.querySelectorAll("iframe").forEach((iframe=>{var _iframe$parentElement;if(null!==(_iframe$parentElement=iframe.parentElement)&&void 0!==_iframe$parentElement&&_iframe$parentElement.classList.contains("tiny-mediacms-iframe-wrapper"))return;if(iframe.hasAttribute("data-mce-object")||iframe.hasAttribute("data-mce-placeholder"))return;const wrapper=editor.getDoc().createElement("div");wrapper.className="tiny-mediacms-iframe-wrapper",wrapper.setAttribute("contenteditable","false");const editBtn=editor.getDoc().createElement("button");editBtn.className="tiny-mediacms-edit-btn",editBtn.setAttribute("type","button"),editBtn.setAttribute("title","Edit media embed options"),editBtn.textContent="EDIT",iframe.parentNode.insertBefore(wrapper,iframe),wrapper.appendChild(iframe),wrapper.appendChild(editBtn)}))},handleOverlayClick=e=>{const editBtn=e.target.closest(".tiny-mediacms-edit-btn");if(!editBtn)return;e.preventDefault(),e.stopPropagation();const wrapper=editBtn.closest(".tiny-mediacms-iframe-wrapper");if(!wrapper)return;wrapper.querySelector("iframe")&&(editor.selection.select(wrapper),handleIframeAction())};editor.on("init",(()=>{(()=>{const editorDoc=editor.getDoc();if(!editorDoc)return;if(editorDoc.getElementById("tiny-mediacms-overlay-styles"))return;const style=editorDoc.createElement("style");style.id="tiny-mediacms-overlay-styles",style.textContent="\n .tiny-mediacms-iframe-wrapper {\n display: inline-block;\n position: relative;\n line-height: 0;\n vertical-align: top;\n margin-top: 40px;\n }\n .tiny-mediacms-iframe-wrapper iframe {\n display: block;\n }\n .tiny-mediacms-edit-btn {\n position: absolute;\n top: -35px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.7);\n color: #ffffff;\n border: none;\n border-radius: 3px;\n cursor: pointer;\n z-index: 10;\n padding: 4px 12px;\n margin: 0;\n font-size: 12px;\n font-weight: bold;\n text-decoration: none;\n box-shadow: 0 2px 4px rgba(0,0,0,0.3);\n transition: background 0.15s, box-shadow 0.15s;\n display: inline-block;\n box-sizing: border-box;\n }\n .tiny-mediacms-edit-btn:hover {\n background: rgba(0, 0, 0, 0.85);\n box-shadow: 0 3px 6px rgba(0,0,0,0.4);\n }\n ",editorDoc.head.appendChild(style)})(),processIframes(),editor.getBody().addEventListener("click",handleOverlayClick)})),editor.on("SetContent",(()=>{processIframes()})),editor.on("PastePostProcess",(()=>{setTimeout(processIframes,100)})),editor.on("Undo Redo",(()=>{processIframes()})),editor.on("Change",(()=>{setTimeout(processIframes,50)})),editor.on("NodeChange",(()=>{processIframes()}))};_exports.getSetup=async()=>{const[iframeButtonText]=await(0,_str.getStrings)(["iframebuttontitle"].map((key=>({key:key,component:_common.component})))),[iframeButtonImage]=await Promise.all([(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{((editor,iframeButtonText,iframeButtonImage)=>{const handleIframeAction=()=>{new _iframeembed.default(editor).displayDialogue()};editor.ui.registry.addIcon(_common.iframeIcon,iframeButtonImage.html),editor.ui.registry.addToggleButton(_common.iframeButtonName,{icon:_common.iframeIcon,tooltip:iframeButtonText,onAction:handleIframeAction,onSetup:api=>editor.selection.selectorChangedWithUnbind("iframe:not([data-mce-object]):not([data-mce-placeholder]),.tiny-iframe-responsive,.tiny-mediacms-iframe-wrapper",api.setActive).unbind}),editor.ui.registry.addMenuItem(_common.iframeMenuItemName,{icon:_common.iframeIcon,text:iframeButtonText,onAction:handleIframeAction}),editor.ui.registry.addContextToolbar(_common.iframeButtonName,{predicate:isIframe,items:_common.iframeButtonName,position:"node",scope:"node"}),editor.ui.registry.addContextMenu(_common.iframeButtonName,{update:isIframe}),setupIframeOverlays(editor,handleIframeAction)})(editor,iframeButtonText,iframeButtonImage)}}})); //# sourceMappingURL=commands.min.js.map \ No newline at end of file diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js.map b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js.map index f676482b..1d2d06d8 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js.map +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js.map @@ -1 +1 @@ -{"version":3,"file":"commands.min.js","sources":["../src/commands.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 commands.\n *\n * @module tiny_mediacms/commands\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getStrings} from 'core/str';\nimport {\n component,\n iframeButtonName,\n iframeMenuItemName,\n iframeIcon,\n} from './common';\nimport IframeEmbed from './iframeembed';\nimport {getButtonImage} from 'editor_tiny/utils';\n\nconst isIframe = (node) => node.nodeName.toLowerCase() === 'iframe' ||\n (node.classList && node.classList.contains('tiny-iframe-responsive')) ||\n (node.classList && node.classList.contains('tiny-mediacms-iframe-wrapper'));\n\n/**\n * Wrap iframes with overlay containers that allow hover detection.\n * Since iframes capture mouse events, we add an invisible overlay on top\n * that shows the edit button on hover.\n *\n * @param {TinyMCE} editor - The editor instance\n * @param {Function} handleIframeAction - The action to perform when clicking the button\n */\nconst setupIframeOverlays = (editor, handleIframeAction) => {\n /**\n * Process all iframes in the editor and add overlay wrappers.\n */\n const processIframes = () => {\n const editorBody = editor.getBody();\n if (!editorBody) {\n return;\n }\n\n const iframes = editorBody.querySelectorAll('iframe');\n iframes.forEach((iframe) => {\n // Skip if already wrapped\n if (iframe.parentElement?.classList.contains('tiny-mediacms-iframe-wrapper')) {\n return;\n }\n\n // Skip TinyMCE internal iframes\n if (iframe.hasAttribute('data-mce-object') || iframe.hasAttribute('data-mce-placeholder')) {\n return;\n }\n\n // Create wrapper div\n const wrapper = editor.getDoc().createElement('div');\n wrapper.className = 'tiny-mediacms-iframe-wrapper';\n wrapper.setAttribute('contenteditable', 'false');\n\n // Create edit button (positioned inside wrapper, over the iframe)\n const editBtn = editor.getDoc().createElement('button');\n editBtn.className = 'tiny-mediacms-edit-btn';\n editBtn.setAttribute('type', 'button');\n editBtn.setAttribute('title', 'Edit video embed options');\n // Use text \"EDIT\" instead of icon\n editBtn.textContent = 'EDIT';\n\n // Wrap the iframe: insert wrapper, move iframe into it, add button\n iframe.parentNode.insertBefore(wrapper, iframe);\n wrapper.appendChild(iframe);\n wrapper.appendChild(editBtn);\n });\n };\n\n /**\n * Add CSS styles for hover effects to the editor's document.\n */\n const addStyles = () => {\n const editorDoc = editor.getDoc();\n if (!editorDoc) {\n return;\n }\n\n // Check if styles already added\n if (editorDoc.getElementById('tiny-mediacms-overlay-styles')) {\n return;\n }\n\n const style = editorDoc.createElement('style');\n style.id = 'tiny-mediacms-overlay-styles';\n style.textContent = `\n .tiny-mediacms-iframe-wrapper {\n display: inline-block;\n position: relative;\n line-height: 0;\n vertical-align: top;\n }\n .tiny-mediacms-iframe-wrapper iframe {\n display: block;\n }\n .tiny-mediacms-edit-btn {\n position: absolute;\n top: 5px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.7);\n color: #ffffff;\n border: none;\n border-radius: 3px;\n cursor: pointer;\n z-index: 10;\n padding: 4px 12px;\n margin: 0;\n font-size: 12px;\n font-weight: bold;\n text-decoration: none;\n box-shadow: 0 2px 4px rgba(0,0,0,0.3);\n transition: background 0.15s, box-shadow 0.15s;\n display: inline-block;\n box-sizing: border-box;\n }\n .tiny-mediacms-edit-btn:hover {\n background: rgba(0, 0, 0, 0.85);\n box-shadow: 0 3px 6px rgba(0,0,0,0.4);\n }\n `;\n editorDoc.head.appendChild(style);\n };\n\n /**\n * Handle click on the edit button.\n *\n * @param {Event} e - The click event\n */\n const handleOverlayClick = (e) => {\n const target = e.target;\n\n // Check if clicked on edit button or its child (svg/path)\n const editBtn = target.closest('.tiny-mediacms-edit-btn');\n if (!editBtn) {\n return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n\n // Find the associated wrapper and iframe\n const wrapper = editBtn.closest('.tiny-mediacms-iframe-wrapper');\n if (!wrapper) {\n return;\n }\n\n const iframe = wrapper.querySelector('iframe');\n if (!iframe) {\n return;\n }\n\n // Select the wrapper so TinyMCE knows which element is selected\n editor.selection.select(wrapper);\n\n // Open the edit dialog\n handleIframeAction();\n };\n\n // Setup on editor init\n editor.on('init', () => {\n addStyles();\n processIframes();\n\n // Handle clicks on the overlay\n editor.getBody().addEventListener('click', handleOverlayClick);\n });\n\n // Re-process when content changes\n editor.on('SetContent', () => {\n processIframes();\n });\n\n // Re-process when content is pasted\n editor.on('PastePostProcess', () => {\n setTimeout(processIframes, 100);\n });\n\n // Re-process after undo/redo\n editor.on('Undo Redo', () => {\n processIframes();\n });\n\n // Re-process on any content change (covers modal updates)\n editor.on('Change', () => {\n setTimeout(processIframes, 50);\n });\n\n // Re-process when node changes (selection changes)\n editor.on('NodeChange', () => {\n processIframes();\n });\n};\n\nconst registerIframeCommand = (editor, iframeButtonText, iframeButtonImage) => {\n const handleIframeAction = () => {\n const iframeEmbed = new IframeEmbed(editor);\n iframeEmbed.displayDialogue();\n };\n\n // Register the iframe icon\n editor.ui.registry.addIcon(iframeIcon, iframeButtonImage.html);\n\n // Register the Menu Button as a toggle.\n // This means that when highlighted over an existing iframe element it will show as toggled on.\n editor.ui.registry.addToggleButton(iframeButtonName, {\n icon: iframeIcon,\n tooltip: iframeButtonText,\n onAction: handleIframeAction,\n onSetup: api => {\n return editor.selection.selectorChangedWithUnbind(\n 'iframe:not([data-mce-object]):not([data-mce-placeholder]),.tiny-iframe-responsive,.tiny-mediacms-iframe-wrapper',\n api.setActive\n ).unbind;\n }\n });\n\n editor.ui.registry.addMenuItem(iframeMenuItemName, {\n icon: iframeIcon,\n text: iframeButtonText,\n onAction: handleIframeAction,\n });\n\n editor.ui.registry.addContextToolbar(iframeButtonName, {\n predicate: isIframe,\n items: iframeButtonName,\n position: 'node',\n scope: 'node'\n });\n\n editor.ui.registry.addContextMenu(iframeButtonName, {\n update: isIframe,\n });\n\n // Setup iframe overlays with edit button on hover\n setupIframeOverlays(editor, handleIframeAction);\n};\n\nexport const getSetup = async() => {\n const [\n iframeButtonText,\n ] = await getStrings([\n 'iframebuttontitle',\n ].map((key) => ({key, component})));\n\n const [\n iframeButtonImage,\n ] = await Promise.all([\n getButtonImage('icon', component),\n ]);\n\n // Note: The function returned here must be synchronous and cannot use promises.\n // All promises must be resolved prior to returning the function.\n return (editor) => {\n registerIframeCommand(editor, iframeButtonText, iframeButtonImage);\n };\n};\n"],"names":["isIframe","node","nodeName","toLowerCase","classList","contains","setupIframeOverlays","editor","handleIframeAction","processIframes","editorBody","getBody","querySelectorAll","forEach","iframe","parentElement","_iframe$parentElement","hasAttribute","wrapper","getDoc","createElement","className","setAttribute","editBtn","textContent","parentNode","insertBefore","appendChild","handleOverlayClick","e","target","closest","preventDefault","stopPropagation","querySelector","selection","select","on","editorDoc","getElementById","style","id","head","addStyles","addEventListener","setTimeout","async","iframeButtonText","map","key","component","iframeButtonImage","Promise","all","IframeEmbed","displayDialogue","ui","registry","addIcon","iframeIcon","html","addToggleButton","iframeButtonName","icon","tooltip","onAction","onSetup","api","selectorChangedWithUnbind","setActive","unbind","addMenuItem","iframeMenuItemName","text","addContextToolbar","predicate","items","position","scope","addContextMenu","update","registerIframeCommand"],"mappings":";;;;;;;8JAiCMA,SAAYC,MAAyC,WAAhCA,KAAKC,SAASC,eACpCF,KAAKG,WAAaH,KAAKG,UAAUC,SAAS,2BAC1CJ,KAAKG,WAAaH,KAAKG,UAAUC,SAAS,gCAUzCC,oBAAsB,CAACC,OAAQC,4BAI3BC,eAAiB,WACbC,WAAaH,OAAOI,cACrBD,kBAIWA,WAAWE,iBAAiB,UACpCC,SAASC,oEAETA,OAAOC,gDAAPC,sBAAsBZ,UAAUC,SAAS,0CAKzCS,OAAOG,aAAa,oBAAsBH,OAAOG,aAAa,qCAK5DC,QAAUX,OAAOY,SAASC,cAAc,OAC9CF,QAAQG,UAAY,+BACpBH,QAAQI,aAAa,kBAAmB,eAGlCC,QAAUhB,OAAOY,SAASC,cAAc,UAC9CG,QAAQF,UAAY,yBACpBE,QAAQD,aAAa,OAAQ,UAC7BC,QAAQD,aAAa,QAAS,4BAE9BC,QAAQC,YAAc,OAGtBV,OAAOW,WAAWC,aAAaR,QAASJ,QACxCI,QAAQS,YAAYb,QACpBI,QAAQS,YAAYJ,aAgEtBK,mBAAsBC,UAIlBN,QAHSM,EAAEC,OAGMC,QAAQ,+BAC1BR,eAILM,EAAEG,iBACFH,EAAEI,wBAGIf,QAAUK,QAAQQ,QAAQ,qCAC3Bb,eAIUA,QAAQgB,cAAc,YAMrC3B,OAAO4B,UAAUC,OAAOlB,SAGxBV,uBAIJD,OAAO8B,GAAG,QAAQ,KAxFA,YACRC,UAAY/B,OAAOY,aACpBmB,oBAKDA,UAAUC,eAAe,6CAIvBC,MAAQF,UAAUlB,cAAc,SACtCoB,MAAMC,GAAK,+BACXD,MAAMhB,iwCAoCNc,UAAUI,KAAKf,YAAYa,QAwC3BG,GACAlC,iBAGAF,OAAOI,UAAUiC,iBAAiB,QAAShB,uBAI/CrB,OAAO8B,GAAG,cAAc,KACpB5B,oBAIJF,OAAO8B,GAAG,oBAAoB,KAC1BQ,WAAWpC,eAAgB,QAI/BF,OAAO8B,GAAG,aAAa,KACnB5B,oBAIJF,OAAO8B,GAAG,UAAU,KAChBQ,WAAWpC,eAAgB,OAI/BF,OAAO8B,GAAG,cAAc,KACpB5B,uCAgDgBqC,gBAEhBC,wBACM,mBAAW,CACjB,qBACFC,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,wBAGlBC,yBACMC,QAAQC,IAAI,EAClB,yBAAe,OAAQH,4BAKnB3C,SA3DkB,EAACA,OAAQwC,iBAAkBI,2BAC/C3C,mBAAqB,KACH,IAAI8C,qBAAY/C,QACxBgD,mBAIhBhD,OAAOiD,GAAGC,SAASC,QAAQC,mBAAYR,kBAAkBS,MAIzDrD,OAAOiD,GAAGC,SAASI,gBAAgBC,yBAAkB,CACjDC,KAAMJ,mBACNK,QAASjB,iBACTkB,SAAUzD,mBACV0D,QAASC,KACE5D,OAAO4B,UAAUiC,0BACpB,kHACAD,IAAIE,WACNC,SAIV/D,OAAOiD,GAAGC,SAASc,YAAYC,2BAAoB,CAC/CT,KAAMJ,mBACNc,KAAM1B,iBACNkB,SAAUzD,qBAGdD,OAAOiD,GAAGC,SAASiB,kBAAkBZ,yBAAkB,CACnDa,UAAW3E,SACX4E,MAAOd,yBACPe,SAAU,OACVC,MAAO,SAGXvE,OAAOiD,GAAGC,SAASsB,eAAejB,yBAAkB,CAChDkB,OAAQhF,WAIZM,oBAAoBC,OAAQC,qBAmBxByE,CAAsB1E,OAAQwC,iBAAkBI"} \ No newline at end of file +{"version":3,"file":"commands.min.js","sources":["../src/commands.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 commands.\n *\n * @module tiny_mediacms/commands\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getStrings} from 'core/str';\nimport {\n component,\n iframeButtonName,\n iframeMenuItemName,\n iframeIcon,\n} from './common';\nimport IframeEmbed from './iframeembed';\nimport {getButtonImage} from 'editor_tiny/utils';\n\nconst isIframe = (node) => node.nodeName.toLowerCase() === 'iframe' ||\n (node.classList && node.classList.contains('tiny-iframe-responsive')) ||\n (node.classList && node.classList.contains('tiny-mediacms-iframe-wrapper'));\n\n/**\n * Wrap iframes with overlay containers that allow hover detection.\n * Since iframes capture mouse events, we add an invisible overlay on top\n * that shows the edit button on hover.\n *\n * @param {TinyMCE} editor - The editor instance\n * @param {Function} handleIframeAction - The action to perform when clicking the button\n */\nconst setupIframeOverlays = (editor, handleIframeAction) => {\n /**\n * Process all iframes in the editor and add overlay wrappers.\n */\n const processIframes = () => {\n const editorBody = editor.getBody();\n if (!editorBody) {\n return;\n }\n\n const iframes = editorBody.querySelectorAll('iframe');\n iframes.forEach((iframe) => {\n // Skip if already wrapped\n if (iframe.parentElement?.classList.contains('tiny-mediacms-iframe-wrapper')) {\n return;\n }\n\n // Skip TinyMCE internal iframes\n if (iframe.hasAttribute('data-mce-object') || iframe.hasAttribute('data-mce-placeholder')) {\n return;\n }\n\n // Create wrapper div\n const wrapper = editor.getDoc().createElement('div');\n wrapper.className = 'tiny-mediacms-iframe-wrapper';\n wrapper.setAttribute('contenteditable', 'false');\n\n // Create edit button (positioned inside wrapper, over the iframe)\n const editBtn = editor.getDoc().createElement('button');\n editBtn.className = 'tiny-mediacms-edit-btn';\n editBtn.setAttribute('type', 'button');\n editBtn.setAttribute('title', 'Edit media embed options');\n // Use text \"EDIT\" instead of icon\n editBtn.textContent = 'EDIT';\n\n // Wrap the iframe: insert wrapper, move iframe into it, add button\n iframe.parentNode.insertBefore(wrapper, iframe);\n wrapper.appendChild(iframe);\n wrapper.appendChild(editBtn);\n });\n };\n\n /**\n * Add CSS styles for hover effects to the editor's document.\n */\n const addStyles = () => {\n const editorDoc = editor.getDoc();\n if (!editorDoc) {\n return;\n }\n\n // Check if styles already added\n if (editorDoc.getElementById('tiny-mediacms-overlay-styles')) {\n return;\n }\n\n const style = editorDoc.createElement('style');\n style.id = 'tiny-mediacms-overlay-styles';\n style.textContent = `\n .tiny-mediacms-iframe-wrapper {\n display: inline-block;\n position: relative;\n line-height: 0;\n vertical-align: top;\n margin-top: 40px;\n }\n .tiny-mediacms-iframe-wrapper iframe {\n display: block;\n }\n .tiny-mediacms-edit-btn {\n position: absolute;\n top: -35px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.7);\n color: #ffffff;\n border: none;\n border-radius: 3px;\n cursor: pointer;\n z-index: 10;\n padding: 4px 12px;\n margin: 0;\n font-size: 12px;\n font-weight: bold;\n text-decoration: none;\n box-shadow: 0 2px 4px rgba(0,0,0,0.3);\n transition: background 0.15s, box-shadow 0.15s;\n display: inline-block;\n box-sizing: border-box;\n }\n .tiny-mediacms-edit-btn:hover {\n background: rgba(0, 0, 0, 0.85);\n box-shadow: 0 3px 6px rgba(0,0,0,0.4);\n }\n `;\n editorDoc.head.appendChild(style);\n };\n\n /**\n * Handle click on the edit button.\n *\n * @param {Event} e - The click event\n */\n const handleOverlayClick = (e) => {\n const target = e.target;\n\n // Check if clicked on edit button or its child (svg/path)\n const editBtn = target.closest('.tiny-mediacms-edit-btn');\n if (!editBtn) {\n return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n\n // Find the associated wrapper and iframe\n const wrapper = editBtn.closest('.tiny-mediacms-iframe-wrapper');\n if (!wrapper) {\n return;\n }\n\n const iframe = wrapper.querySelector('iframe');\n if (!iframe) {\n return;\n }\n\n // Select the wrapper so TinyMCE knows which element is selected\n editor.selection.select(wrapper);\n\n // Open the edit dialog\n handleIframeAction();\n };\n\n // Setup on editor init\n editor.on('init', () => {\n addStyles();\n processIframes();\n\n // Handle clicks on the overlay\n editor.getBody().addEventListener('click', handleOverlayClick);\n });\n\n // Re-process when content changes\n editor.on('SetContent', () => {\n processIframes();\n });\n\n // Re-process when content is pasted\n editor.on('PastePostProcess', () => {\n setTimeout(processIframes, 100);\n });\n\n // Re-process after undo/redo\n editor.on('Undo Redo', () => {\n processIframes();\n });\n\n // Re-process on any content change (covers modal updates)\n editor.on('Change', () => {\n setTimeout(processIframes, 50);\n });\n\n // Re-process when node changes (selection changes)\n editor.on('NodeChange', () => {\n processIframes();\n });\n};\n\nconst registerIframeCommand = (editor, iframeButtonText, iframeButtonImage) => {\n const handleIframeAction = () => {\n const iframeEmbed = new IframeEmbed(editor);\n iframeEmbed.displayDialogue();\n };\n\n // Register the iframe icon\n editor.ui.registry.addIcon(iframeIcon, iframeButtonImage.html);\n\n // Register the Menu Button as a toggle.\n // This means that when highlighted over an existing iframe element it will show as toggled on.\n editor.ui.registry.addToggleButton(iframeButtonName, {\n icon: iframeIcon,\n tooltip: iframeButtonText,\n onAction: handleIframeAction,\n onSetup: api => {\n return editor.selection.selectorChangedWithUnbind(\n 'iframe:not([data-mce-object]):not([data-mce-placeholder]),.tiny-iframe-responsive,.tiny-mediacms-iframe-wrapper',\n api.setActive\n ).unbind;\n }\n });\n\n editor.ui.registry.addMenuItem(iframeMenuItemName, {\n icon: iframeIcon,\n text: iframeButtonText,\n onAction: handleIframeAction,\n });\n\n editor.ui.registry.addContextToolbar(iframeButtonName, {\n predicate: isIframe,\n items: iframeButtonName,\n position: 'node',\n scope: 'node'\n });\n\n editor.ui.registry.addContextMenu(iframeButtonName, {\n update: isIframe,\n });\n\n // Setup iframe overlays with edit button on hover\n setupIframeOverlays(editor, handleIframeAction);\n};\n\nexport const getSetup = async() => {\n const [\n iframeButtonText,\n ] = await getStrings([\n 'iframebuttontitle',\n ].map((key) => ({key, component})));\n\n const [\n iframeButtonImage,\n ] = await Promise.all([\n getButtonImage('icon', component),\n ]);\n\n // Note: The function returned here must be synchronous and cannot use promises.\n // All promises must be resolved prior to returning the function.\n return (editor) => {\n registerIframeCommand(editor, iframeButtonText, iframeButtonImage);\n };\n};\n"],"names":["isIframe","node","nodeName","toLowerCase","classList","contains","setupIframeOverlays","editor","handleIframeAction","processIframes","editorBody","getBody","querySelectorAll","forEach","iframe","parentElement","_iframe$parentElement","hasAttribute","wrapper","getDoc","createElement","className","setAttribute","editBtn","textContent","parentNode","insertBefore","appendChild","handleOverlayClick","e","target","closest","preventDefault","stopPropagation","querySelector","selection","select","on","editorDoc","getElementById","style","id","head","addStyles","addEventListener","setTimeout","async","iframeButtonText","map","key","component","iframeButtonImage","Promise","all","IframeEmbed","displayDialogue","ui","registry","addIcon","iframeIcon","html","addToggleButton","iframeButtonName","icon","tooltip","onAction","onSetup","api","selectorChangedWithUnbind","setActive","unbind","addMenuItem","iframeMenuItemName","text","addContextToolbar","predicate","items","position","scope","addContextMenu","update","registerIframeCommand"],"mappings":";;;;;;;8JAiCMA,SAAYC,MAAyC,WAAhCA,KAAKC,SAASC,eACpCF,KAAKG,WAAaH,KAAKG,UAAUC,SAAS,2BAC1CJ,KAAKG,WAAaH,KAAKG,UAAUC,SAAS,gCAUzCC,oBAAsB,CAACC,OAAQC,4BAI3BC,eAAiB,WACbC,WAAaH,OAAOI,cACrBD,kBAIWA,WAAWE,iBAAiB,UACpCC,SAASC,oEAETA,OAAOC,gDAAPC,sBAAsBZ,UAAUC,SAAS,0CAKzCS,OAAOG,aAAa,oBAAsBH,OAAOG,aAAa,qCAK5DC,QAAUX,OAAOY,SAASC,cAAc,OAC9CF,QAAQG,UAAY,+BACpBH,QAAQI,aAAa,kBAAmB,eAGlCC,QAAUhB,OAAOY,SAASC,cAAc,UAC9CG,QAAQF,UAAY,yBACpBE,QAAQD,aAAa,OAAQ,UAC7BC,QAAQD,aAAa,QAAS,4BAE9BC,QAAQC,YAAc,OAGtBV,OAAOW,WAAWC,aAAaR,QAASJ,QACxCI,QAAQS,YAAYb,QACpBI,QAAQS,YAAYJ,aAiEtBK,mBAAsBC,UAIlBN,QAHSM,EAAEC,OAGMC,QAAQ,+BAC1BR,eAILM,EAAEG,iBACFH,EAAEI,wBAGIf,QAAUK,QAAQQ,QAAQ,qCAC3Bb,eAIUA,QAAQgB,cAAc,YAMrC3B,OAAO4B,UAAUC,OAAOlB,SAGxBV,uBAIJD,OAAO8B,GAAG,QAAQ,KAzFA,YACRC,UAAY/B,OAAOY,aACpBmB,oBAKDA,UAAUC,eAAe,6CAIvBC,MAAQF,UAAUlB,cAAc,SACtCoB,MAAMC,GAAK,+BACXD,MAAMhB,syCAqCNc,UAAUI,KAAKf,YAAYa,QAwC3BG,GACAlC,iBAGAF,OAAOI,UAAUiC,iBAAiB,QAAShB,uBAI/CrB,OAAO8B,GAAG,cAAc,KACpB5B,oBAIJF,OAAO8B,GAAG,oBAAoB,KAC1BQ,WAAWpC,eAAgB,QAI/BF,OAAO8B,GAAG,aAAa,KACnB5B,oBAIJF,OAAO8B,GAAG,UAAU,KAChBQ,WAAWpC,eAAgB,OAI/BF,OAAO8B,GAAG,cAAc,KACpB5B,uCAgDgBqC,gBAEhBC,wBACM,mBAAW,CACjB,qBACFC,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,wBAGlBC,yBACMC,QAAQC,IAAI,EAClB,yBAAe,OAAQH,4BAKnB3C,SA3DkB,EAACA,OAAQwC,iBAAkBI,2BAC/C3C,mBAAqB,KACH,IAAI8C,qBAAY/C,QACxBgD,mBAIhBhD,OAAOiD,GAAGC,SAASC,QAAQC,mBAAYR,kBAAkBS,MAIzDrD,OAAOiD,GAAGC,SAASI,gBAAgBC,yBAAkB,CACjDC,KAAMJ,mBACNK,QAASjB,iBACTkB,SAAUzD,mBACV0D,QAASC,KACE5D,OAAO4B,UAAUiC,0BACpB,kHACAD,IAAIE,WACNC,SAIV/D,OAAOiD,GAAGC,SAASc,YAAYC,2BAAoB,CAC/CT,KAAMJ,mBACNc,KAAM1B,iBACNkB,SAAUzD,qBAGdD,OAAOiD,GAAGC,SAASiB,kBAAkBZ,yBAAkB,CACnDa,UAAW3E,SACX4E,MAAOd,yBACPe,SAAU,OACVC,MAAO,SAGXvE,OAAOiD,GAAGC,SAASsB,eAAejB,yBAAkB,CAChDkB,OAAQhF,WAIZM,oBAAoBC,OAAQC,qBAmBxByE,CAAsB1E,OAAQwC,iBAAkBI"} \ No newline at end of file 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 cb6e25ef..ae4d0fdf 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,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})); +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 urlObj.pathname.includes("/filter/mediacms/launch.php")&&urlObj.searchParams.has("token")?{baseUrl:baseUrl,videoId:urlObj.searchParams.get("token"),rawUrl:url,isLtiLaunch:!0}:{baseUrl:baseUrl,rawUrl:url,isGeneric:!0}}catch(e){return null}}parseEmbedUrl(url){return this.parseVideoUrl(url)}secondsToTimeString(seconds){const mins=Math.floor(seconds/60),secs=seconds%60;return"".concat(mins,":").concat(secs.toString().padStart(2,"0"))}timeStringToSeconds(timeStr){if(!timeStr||!timeStr.trim())return null;if((timeStr=timeStr.trim()).includes(":")){const parts=timeStr.split(":");return 60*(parseInt(parts[0])||0)+(parseInt(parts[1])||0)}const secs=parseInt(timeStr);return isNaN(secs)?null:secs}buildEmbedUrl(parsed,options){if(parsed.isGeneric||parsed.isLtiLaunch)return parsed.rawUrl;const url=new URL("".concat(parsed.baseUrl,"/embed"));if(url.searchParams.set("m",parsed.videoId),url.searchParams.set("showTitle",options.showTitle?"1":"0"),url.searchParams.set("showRelated",options.showRelated?"1":"0"),url.searchParams.set("showUserAvatar",options.showUserAvatar?"1":"0"),url.searchParams.set("linkTitle",options.linkTitle?"1":"0"),options.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.isLtiLaunch?parsed.rawUrl:"".concat(parsed.baseUrl,"/view?m=").concat(parsed.videoId);const linkText=(str=>{const div=document.createElement("div");return div.textContent=str,div.innerHTML})(viewUrl),hrefUrl=viewUrl.replace(/"/g,""");return'

').concat(linkText,"

")}const 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.isLtiLaunch?parsed.rawUrl:"".concat(parsed.baseUrl,"/view?m=").concat(parsed.videoId);const linkText=(str=>{const div=document.createElement("div");return div.textContent=str,div.innerHTML})(viewUrl),hrefUrl=viewUrl.replace(/"/g,""");previewContainer.innerHTML='\n
\n Text link preview:
\n ').concat(linkText,'\n
This link will not be auto-converted by the MediaCMS filter.\n
\n ')}else{const previewWidth=Math.min(values.width,400),scale=previewWidth/values.width,previewHeight=Math.round(values.height*scale);previewContainer.innerHTML='\n \n \n ')}}handleInputChange(root){let updateUrlField=arguments.length>1&&void 0!==arguments[1]&&arguments[1];clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout((()=>{this.updatePreview(root,updateUrlField)}),500)}handleAspectRatioChange(root){const form=root.querySelector(_selectors.default.IFRAME.elements.form),aspectRatio=form.querySelector(_selectors.default.IFRAME.elements.aspectRatio).value,dimensions=_selectors.default.IFRAME.aspectRatios[aspectRatio];dimensions&&"custom"!==aspectRatio&&(form.querySelector(_selectors.default.IFRAME.elements.width).value=dimensions.width,form.querySelector(_selectors.default.IFRAME.elements.height).value=dimensions.height),this.updatePreview(root)}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 2f44518d..d5ac19e5 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 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 +{"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 // Moodle LTI launch.php URL: /filter/mediacms/launch.php?token=TOKEN\n // This is used when selecting from \"My Media\" via LTI\n // We treat it as a generic iframe URL (keep as-is)\n if (urlObj.pathname.includes('/filter/mediacms/launch.php') && urlObj.searchParams.has('token')) {\n return {\n baseUrl: baseUrl,\n videoId: urlObj.searchParams.get('token'),\n rawUrl: url,\n isLtiLaunch: true,\n };\n }\n\n // Generic URL - just use as-is\n return {\n baseUrl: baseUrl,\n rawUrl: url,\n isGeneric: true,\n };\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Parse an embed URL.\n *\n * @param {string} url - The embed URL\n * @returns {Object|null} Parsed info\n */\n parseEmbedUrl(url) {\n return this.parseVideoUrl(url);\n }\n\n /**\n * Convert seconds to time string (M:SS format).\n *\n * @param {number} seconds - Time in seconds\n * @returns {string} Time string\n */\n secondsToTimeString(seconds) {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n /**\n * Convert time string to seconds.\n *\n * @param {string} timeStr - Time string (M:SS or just seconds)\n * @returns {number|null} Seconds or null if invalid\n */\n timeStringToSeconds(timeStr) {\n if (!timeStr || !timeStr.trim()) {\n return null;\n }\n timeStr = timeStr.trim();\n\n // Handle M:SS format\n if (timeStr.includes(':')) {\n const parts = timeStr.split(':');\n const mins = parseInt(parts[0]) || 0;\n const secs = parseInt(parts[1]) || 0;\n return mins * 60 + secs;\n }\n\n // Handle plain seconds\n const secs = parseInt(timeStr);\n return isNaN(secs) ? null : secs;\n }\n\n /**\n * Build the embed URL with options.\n *\n * @param {Object} parsed - Parsed URL info\n * @param {Object} options - Embed options\n * @returns {string} The complete embed URL\n */\n buildEmbedUrl(parsed, options) {\n if (parsed.isGeneric || parsed.isLtiLaunch) {\n return parsed.rawUrl;\n }\n\n const url = new URL(`${parsed.baseUrl}/embed`);\n url.searchParams.set('m', parsed.videoId);\n\n // Always include all options with 1 or 0\n url.searchParams.set('showTitle', options.showTitle ? '1' : '0');\n url.searchParams.set('showRelated', options.showRelated ? '1' : '0');\n url.searchParams.set(\n 'showUserAvatar',\n options.showUserAvatar ? '1' : '0',\n );\n url.searchParams.set('linkTitle', options.linkTitle ? '1' : '0');\n\n // Add 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 || parsed.isLtiLaunch) {\n // For generic URLs and LTI launch URLs, use as-is\n viewUrl = parsed.rawUrl;\n } else {\n // For MediaCMS URLs, convert to view URL\n viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`;\n }\n\n // Use the full URL as the link text\n // Escape HTML entities in the URL for safe display\n const escapeHtml = (str) => {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n };\n\n const linkText = escapeHtml(viewUrl);\n const hrefUrl = viewUrl.replace(/\"/g, '"');\n\n // Add data attribute to mark this as a text-only link\n // This prevents the filter from converting it to an LTI launch iframe\n return `

${linkText}

`;\n }\n\n const embedUrl = this.buildEmbedUrl(parsed, values);\n\n // 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 || parsed.isLtiLaunch) {\n viewUrl = parsed.rawUrl;\n } else {\n viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`;\n }\n\n // Escape HTML entities for safe display\n const escapeHtml = (str) => {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n };\n\n const linkText = escapeHtml(viewUrl);\n const hrefUrl = viewUrl.replace(/\"/g, '"');\n\n previewContainer.innerHTML = `\n
\n Text link preview:
\n ${linkText}\n
This link will not be auto-converted by the MediaCMS filter.\n
\n `;\n } else {\n // Show a scaled preview\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","includes","rawUrl","isLtiLaunch","isGeneric","e","seconds","mins","Math","floor","secs","toString","padStart","timeStringToSeconds","timeStr","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/src/commands.js b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/commands.js index fb57f64f..bfb4208d 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/commands.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/commands.js @@ -74,7 +74,7 @@ const setupIframeOverlays = (editor, handleIframeAction) => { const editBtn = editor.getDoc().createElement('button'); editBtn.className = 'tiny-mediacms-edit-btn'; editBtn.setAttribute('type', 'button'); - editBtn.setAttribute('title', 'Edit video embed options'); + editBtn.setAttribute('title', 'Edit media embed options'); // Use text "EDIT" instead of icon editBtn.textContent = 'EDIT'; @@ -107,13 +107,14 @@ const setupIframeOverlays = (editor, handleIframeAction) => { position: relative; line-height: 0; vertical-align: top; + margin-top: 40px; } .tiny-mediacms-iframe-wrapper iframe { display: block; } .tiny-mediacms-edit-btn { position: absolute; - top: 5px; + top: -35px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.7); 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 b18e2b3d..835588c3 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/iframeembed.js @@ -113,6 +113,18 @@ export default class IframeEmbed { }; } + // Moodle LTI launch.php URL: /filter/mediacms/launch.php?token=TOKEN + // This is used when selecting from "My Media" via LTI + // We treat it as a generic iframe URL (keep as-is) + if (urlObj.pathname.includes('/filter/mediacms/launch.php') && urlObj.searchParams.has('token')) { + return { + baseUrl: baseUrl, + videoId: urlObj.searchParams.get('token'), + rawUrl: url, + isLtiLaunch: true, + }; + } + // Generic URL - just use as-is return { baseUrl: baseUrl, @@ -179,7 +191,7 @@ export default class IframeEmbed { * @returns {string} The complete embed URL */ buildEmbedUrl(parsed, options) { - if (parsed.isGeneric) { + if (parsed.isGeneric || parsed.isLtiLaunch) { return parsed.rawUrl; } @@ -389,9 +401,11 @@ export default class IframeEmbed { if (values.textLinkOnly) { // Build the view URL (not embed URL) for the link let viewUrl; - if (parsed.isGeneric) { + if (parsed.isGeneric || parsed.isLtiLaunch) { + // For generic URLs and LTI launch URLs, use as-is viewUrl = parsed.rawUrl; } else { + // For MediaCMS URLs, convert to view URL viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`; } @@ -480,7 +494,7 @@ export default class IframeEmbed { // If text link only is selected, show link preview if (values.textLinkOnly) { let viewUrl; - if (parsed.isGeneric) { + if (parsed.isGeneric || parsed.isLtiLaunch) { viewUrl = parsed.rawUrl; } else { viewUrl = `${parsed.baseUrl}/view?m=${parsed.videoId}`; diff --git a/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_options.mustache b/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_options.mustache index 738faf88..c999f703 100755 --- a/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_options.mustache +++ b/lms-plugins/mediacms-moodle/tiny/mediacms/templates/iframe_embed_options.mustache @@ -53,12 +53,19 @@
-
+
+ + +
@@ -75,13 +82,6 @@ {{#str}} responsive, tiny_mediacms {{/str}}
-
- - -