This commit is contained in:
Markos Gogoulos
2026-02-12 11:28:40 +02:00
parent 86f4484c36
commit abd22426d7
8 changed files with 37 additions and 20 deletions

View File

@@ -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

View File

@@ -5,6 +5,6 @@ define("tiny_mediacms/commands",["exports","core/str","./common","./iframeembed"
* @module tiny_mediacms/commands
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
* @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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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);

View File

@@ -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}`;

View File

@@ -53,12 +53,19 @@
</label>
</div>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input tiny_iframecms_showrelated"
<input type="checkbox" class="form-check-input tiny_iframecms_showrelated"
id="{{elementid}}_showrelated" {{#showRelated}}checked{{/showRelated}}>
<label class="form-check-label" for="{{elementid}}_showrelated">
{{#str}} showrelated, tiny_mediacms {{/str}}
</label>
</div>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input tiny_iframecms_textlinkonly"
id="{{elementid}}_textlinkonly" {{#textLinkOnly}}checked{{/textLinkOnly}}>
<label class="form-check-label" for="{{elementid}}_textlinkonly">
{{#str}} textlinkonly, tiny_mediacms {{/str}}
</label>
</div>
</div>
<div class="col-6">
<div class="form-check mb-2">
@@ -75,13 +82,6 @@
{{#str}} responsive, tiny_mediacms {{/str}}
</label>
</div>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input tiny_iframecms_textlinkonly"
id="{{elementid}}_textlinkonly" {{#textLinkOnly}}checked{{/textLinkOnly}}>
<label class="form-check-label" for="{{elementid}}_textlinkonly">
{{#str}} textlinkonly, tiny_mediacms {{/str}}
</label>
</div>
<div class="form-check mb-2 d-flex align-items-center">
<input type="checkbox" class="form-check-input tiny_iframecms_startat_enabled"
id="{{elementid}}_startat_enabled" {{#startAtEnabled}}checked{{/startAtEnabled}}>