This commit is contained in:
Markos Gogoulos
2026-04-21 11:02:30 +03:00
parent 656176b002
commit 6145249f5c
18 changed files with 227 additions and 203 deletions
@@ -156,22 +156,23 @@ class text_filter extends \core_filters\text_filter {
$launchurl = new moodle_url('/filter/mediacms/launch.php', $launch_params);
// Build iframe attributes
$iframe_attrs = [
'src' => $launchurl->out(false),
'frameborder' => 0,
'allowfullscreen' => 'allowfullscreen',
'class' => 'mediacms-embed',
'title' => 'MediaCMS Video'
];
// Build responsive CSS
$max_width = ($width !== null) ? (int)$width : 640;
if ($width !== null && $height !== null && (int)$height > 0) {
$aspect_ratio_css = (int)$width . ' / ' . (int)$height;
} else {
$aspect_ratio_css = '16 / 9';
}
$style = 'width:100%;max-width:' . $max_width . 'px;aspect-ratio:' . $aspect_ratio_css
. ';display:block;margin:0 auto;border:0;';
// Add width/height attributes only if provided
if ($width !== null) {
$iframe_attrs['width'] = $width;
}
if ($height !== null) {
$iframe_attrs['height'] = $height;
}
$iframe_attrs = [
'src' => $launchurl->out(false),
'style' => $style,
'frameborder' => '0',
'allowfullscreen' => 'allowfullscreen',
'title' => 'MediaCMS Video',
];
$iframe = html_writer::tag('iframe', '', $iframe_attrs);
@@ -47,7 +47,8 @@ echo html_writer::tag('iframe', '', [
'src' => $src,
'allowfullscreen' => 'true',
'allow' => 'autoplay *; fullscreen *; encrypted-media *; camera *; microphone *; display-capture *;',
'style' => 'border:none;display:block;width:100%;height:calc(100vh - 120px);',
'style' => 'border:none;display:block;width:100%;height:100vh;',
// 'style' => 'border:none;display:block;width:100%;height:calc(100vh - 120px);',
]);
echo $OUTPUT->footer();
@@ -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")||"a"===node.nodeName.toLowerCase()&&"true"===node.getAttribute("data-mediacms-textlink"),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: -15px;\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=>{const selector=["iframe:not([data-mce-object]):not([data-mce-placeholder])",".tiny-iframe-responsive",".tiny-mediacms-iframe-wrapper",'a[data-mediacms-textlink="true"]'].join(",");return editor.selection.selectorChangedWithUnbind(selector,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")||"a"===node.nodeName.toLowerCase()&&"true"===node.getAttribute("data-mediacms-textlink"),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: -30px;\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=>{const selector=["iframe:not([data-mce-object]):not([data-mce-placeholder])",".tiny-iframe-responsive",".tiny-mediacms-iframe-wrapper",'a[data-mediacms-textlink="true"]'].join(",");return editor.selection.selectorChangedWithUnbind(selector,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
@@ -5,6 +5,6 @@ define("tiny_mediacms/plugin",["exports","editor_tiny/loader","editor_tiny/utils
* @module tiny_mediacms/plugin
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);const isMediaCMSUrl=url=>{if(!url)return!1;try{const urlObj=new URL(url);return("/embed"===urlObj.pathname||"/view"===urlObj.pathname)&&urlObj.searchParams.has("m")}catch(e){return!1}},MEDIACMS_URL_PATTERN=/(^|>|\s)(https?:\/\/[^\s<>"]+\/(?:embed|view)\?m=[^\s<>"]+)(<|\s|$)/g;var _default=new Promise((async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add("".concat(_common.component,"/plugin"),(editor=>(Options.register(editor),setupCommands(editor),(0,_autoconvert.setupAutoConvert)(editor),editor.on("BeforeSetContent",(e=>{e.content&&"string"==typeof e.content&&(e.content=e.content.replace(MEDIACMS_URL_PATTERN,((match,before,url,after)=>isMediaCMSUrl(url)?before+(url=>{let embedUrl=url;try{const urlObj=new URL(url);"/view"===urlObj.pathname&&(urlObj.pathname="/embed",embedUrl=urlObj.toString())}catch(e){}return'<iframe src="'.concat(embedUrl,'" ')+'style="width: 100%; aspect-ratio: 16 / 9; display: block; border: 0;" allowfullscreen="allowfullscreen"></iframe>'})(url)+after:match)))})),editor.on("GetContent",(e=>{if("html"===e.format){const tempDiv=document.createElement("div");tempDiv.innerHTML=e.content,tempDiv.querySelectorAll(".tiny-mediacms-edit-btn").forEach((btn=>btn.remove())),tempDiv.querySelectorAll("iframe").forEach((iframe=>{const src=iframe.getAttribute("src");if(isMediaCMSUrl(src)){const wrapper=iframe.closest(".tiny-mediacms-iframe-wrapper")||iframe.closest(".tiny-iframe-responsive"),urlText=document.createTextNode(src),p=document.createElement("p");p.appendChild(urlText),wrapper?(wrapper.parentNode.insertBefore(p,wrapper),wrapper.remove()):(iframe.parentNode.insertBefore(p,iframe),iframe.remove())}})),tempDiv.querySelectorAll(".tiny-mediacms-iframe-wrapper").forEach((wrapper=>{const iframe=wrapper.querySelector("iframe");iframe&&wrapper.parentNode.insertBefore(iframe,wrapper),wrapper.remove()})),tempDiv.querySelectorAll(".tiny-iframe-responsive").forEach((wrapper=>{const iframe=wrapper.querySelector("iframe");iframe&&wrapper.parentNode.insertBefore(iframe,wrapper),wrapper.remove()})),e.content=tempDiv.innerHTML}})),pluginMetadata))),resolve(["".concat(_common.component,"/plugin"),Configuration])}));return _exports.default=_default,_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);const isMediaCMSUrl=url=>{if(!url)return!1;try{const urlObj=new URL(url);return("/embed"===urlObj.pathname||"/view"===urlObj.pathname)&&urlObj.searchParams.has("m")}catch(e){return!1}},convertUrlsToIframes=html=>{const tempDiv=document.createElement("div");tempDiv.innerHTML=html;const nodesToReplace=[],walk=el=>{for(const child of Array.from(el.childNodes))if(child.nodeType===Node.TEXT_NODE){const url=child.textContent.trim();isMediaCMSUrl(url)&&nodesToReplace.push({node:child,url:url})}else child.nodeType===Node.ELEMENT_NODE&&"a"!==child.tagName.toLowerCase()&&walk(child)};return walk(tempDiv),nodesToReplace.forEach((_ref=>{let{node:node,url:url}=_ref;const wrapper=document.createElement("div");wrapper.innerHTML=(url=>{let embedUrl=url;try{const urlObj=new URL(url);"/view"===urlObj.pathname&&(urlObj.pathname="/embed",embedUrl=urlObj.toString())}catch(e){}return'<iframe src="'.concat(embedUrl,'" width="560" height="315" ')+'style="width:100%;max-width:560px;aspect-ratio:560 / 315;display:block;margin:0 auto;border:0;" frameborder="0" allowfullscreen></iframe>'})(url);const iframe=wrapper.firstChild;iframe&&node.parentNode.replaceChild(iframe,node)})),tempDiv.innerHTML};var _default=new Promise((async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add("".concat(_common.component,"/plugin"),(editor=>(Options.register(editor),setupCommands(editor),(0,_autoconvert.setupAutoConvert)(editor),editor.on("BeforeSetContent",(e=>{e.content&&"string"==typeof e.content&&(e.content=convertUrlsToIframes(e.content))})),editor.on("GetContent",(e=>{if("html"===e.format){const tempDiv=document.createElement("div");tempDiv.innerHTML=e.content,tempDiv.querySelectorAll(".tiny-mediacms-edit-btn").forEach((btn=>btn.remove())),tempDiv.querySelectorAll(".tiny-mediacms-iframe-wrapper, .tiny-iframe-responsive").forEach((wrapper=>{const iframe=wrapper.querySelector("iframe");iframe&&wrapper.parentNode.insertBefore(iframe,wrapper),wrapper.remove()})),e.content=tempDiv.innerHTML}})),pluginMetadata))),resolve(["".concat(_common.component,"/plugin"),Configuration])}));return _exports.default=_default,_exports.default}));
//# sourceMappingURL=plugin.min.js.map
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
define("tiny_mediacms/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_imagecms_urlentrysubmit",imageBrowser:".openimagecmsbrowser",addUrl:".tiny_imagecms_addurl",deleteImage:".tiny_imagecms_deleteicon"},elements:{form:"form.tiny_imagecms_form",alignSettings:".tiny_imagecms_button",alt:".tiny_imagecms_altentry",altWarning:".tiny_imagecms_altwarning",height:".tiny_imagecms_heightentry",width:".tiny_imagecms_widthentry",url:".tiny_imagecms_urlentry",urlWarning:".tiny_imagecms_urlwarning",size:".tiny_imagecms_size",presentation:".tiny_imagecms_presentation",constrain:".tiny_imagecms_constrain",customStyle:".tiny_imagecms_customstyle",preview:".tiny_imagecms_preview",previewBox:".tiny_imagecms_preview_box",loaderIcon:".tiny_imagecms_loader",loaderIconContainer:".tiny_imagecms_loader_container",insertImage:".tiny_imagecms_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_imagecms_dropzone_container",fileInput:"#tiny_imagecms_fileinput",fileNameLabel:".tiny_imagecms_filename",sizeOriginal:".tiny_imagecms_sizeoriginal",sizeCustom:".tiny_imagecms_sizecustom",properties:".tiny_imagecms_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_mediacms_submit",mediaBrowser:".openmediacmsbrowser"},elements:{form:"form.tiny_mediacms_form",source:".tiny_mediacms_source",track:".tiny_mediacms_track",mediaSource:".tiny_mediacms_media_source",linkSource:".tiny_mediacms_link_source",linkSize:".tiny_mediacms_link_size",posterSource:".tiny_mediacms_poster_source",posterSize:".tiny_mediacms_poster_size",displayOptions:".tiny_mediacms_display_options",name:".tiny_mediacms_name_entry",title:".tiny_mediacms_title_entry",url:".tiny_mediacms_url_entry",width:".tiny_mediacms_width_entry",height:".tiny_mediacms_height_entry",trackSource:".tiny_mediacms_track_source",trackKind:".tiny_mediacms_track_kind_entry",trackLabel:".tiny_mediacms_track_label_entry",trackLang:".tiny_mediacms_track_lang_entry",trackDefault:".tiny_mediacms_track_default",mediaControl:".tiny_mediacms_controls",mediaAutoplay:".tiny_mediacms_autoplay",mediaMute:".tiny_mediacms_mute",mediaLoop:".tiny_mediacms_loop",advancedSettings:".tiny_mediacms_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}},IFRAME:{actions:{remove:'[data-action="remove"]'},elements:{form:"form.tiny_iframecms_form",url:".tiny_iframecms_url",urlWarning:".tiny_iframecms_url_warning",showTitle:".tiny_iframecms_showtitle",linkTitle:".tiny_iframecms_linktitle",showRelated:".tiny_iframecms_showrelated",showUserAvatar:".tiny_iframecms_showuseravatar",textLinkOnly:".tiny_iframecms_textlinkonly",startAt:".tiny_iframecms_startat",startAtEnabled:".tiny_iframecms_startat_enabled",aspectRatio:".tiny_iframecms_aspectratio",width:".tiny_iframecms_width",height:".tiny_iframecms_height",preview:".tiny_iframecms_preview",previewContainer:".tiny_iframecms_preview_container",tabs:".tiny_iframecms_tabs",tabUrlBtn:".tiny_iframecms_tab_url_btn",tabIframeLibraryBtn:".tiny_iframecms_tab_iframe_library_btn",tabUploadMediaBtn:".tiny_iframecms_upload_media_btn",paneUrl:".tiny_iframecms_pane_url",paneIframeLibrary:".tiny_iframecms_pane_iframe_library",iframeLibraryContainer:".tiny_iframecms_iframe_library_container",iframeLibraryPlaceholder:".tiny_iframecms_iframe_library_placeholder",iframeLibraryLoading:".tiny_iframecms_iframe_library_loading",iframeLibraryFrame:".tiny_iframecms_iframe_library_frame"},aspectRatios:{"16:9":{width:560,height:315},"4:3":{width:560,height:420},"1:1":{width:400,height:400},custom:null}}},_exports.default}));
define("tiny_mediacms/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_imagecms_urlentrysubmit",imageBrowser:".openimagecmsbrowser",addUrl:".tiny_imagecms_addurl",deleteImage:".tiny_imagecms_deleteicon"},elements:{form:"form.tiny_imagecms_form",alignSettings:".tiny_imagecms_button",alt:".tiny_imagecms_altentry",altWarning:".tiny_imagecms_altwarning",height:".tiny_imagecms_heightentry",width:".tiny_imagecms_widthentry",url:".tiny_imagecms_urlentry",urlWarning:".tiny_imagecms_urlwarning",size:".tiny_imagecms_size",presentation:".tiny_imagecms_presentation",constrain:".tiny_imagecms_constrain",customStyle:".tiny_imagecms_customstyle",preview:".tiny_imagecms_preview",previewBox:".tiny_imagecms_preview_box",loaderIcon:".tiny_imagecms_loader",loaderIconContainer:".tiny_imagecms_loader_container",insertImage:".tiny_imagecms_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_imagecms_dropzone_container",fileInput:"#tiny_imagecms_fileinput",fileNameLabel:".tiny_imagecms_filename",sizeOriginal:".tiny_imagecms_sizeoriginal",sizeCustom:".tiny_imagecms_sizecustom",properties:".tiny_imagecms_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_mediacms_submit",mediaBrowser:".openmediacmsbrowser"},elements:{form:"form.tiny_mediacms_form",source:".tiny_mediacms_source",track:".tiny_mediacms_track",mediaSource:".tiny_mediacms_media_source",linkSource:".tiny_mediacms_link_source",linkSize:".tiny_mediacms_link_size",posterSource:".tiny_mediacms_poster_source",posterSize:".tiny_mediacms_poster_size",displayOptions:".tiny_mediacms_display_options",name:".tiny_mediacms_name_entry",title:".tiny_mediacms_title_entry",url:".tiny_mediacms_url_entry",width:".tiny_mediacms_width_entry",height:".tiny_mediacms_height_entry",trackSource:".tiny_mediacms_track_source",trackKind:".tiny_mediacms_track_kind_entry",trackLabel:".tiny_mediacms_track_label_entry",trackLang:".tiny_mediacms_track_lang_entry",trackDefault:".tiny_mediacms_track_default",mediaControl:".tiny_mediacms_controls",mediaAutoplay:".tiny_mediacms_autoplay",mediaMute:".tiny_mediacms_mute",mediaLoop:".tiny_mediacms_loop",advancedSettings:".tiny_mediacms_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}},IFRAME:{actions:{remove:'[data-action="remove"]'},elements:{form:"form.tiny_iframecms_form",url:".tiny_iframecms_url",urlWarning:".tiny_iframecms_url_warning",showTitle:".tiny_iframecms_showtitle",linkTitle:".tiny_iframecms_linktitle",showRelated:".tiny_iframecms_showrelated",showUserAvatar:".tiny_iframecms_showuseravatar",textLinkOnly:".tiny_iframecms_textlinkonly",startAt:".tiny_iframecms_startat",startAtEnabled:".tiny_iframecms_startat_enabled",width:".tiny_iframecms_width",height:".tiny_iframecms_height",preview:".tiny_iframecms_preview",previewContainer:".tiny_iframecms_preview_container",tabs:".tiny_iframecms_tabs",tabUrlBtn:".tiny_iframecms_tab_url_btn",tabIframeLibraryBtn:".tiny_iframecms_tab_iframe_library_btn",tabUploadMediaBtn:".tiny_iframecms_upload_media_btn",paneUrl:".tiny_iframecms_pane_url",paneIframeLibrary:".tiny_iframecms_pane_iframe_library",iframeLibraryContainer:".tiny_iframecms_iframe_library_container",iframeLibraryPlaceholder:".tiny_iframecms_iframe_library_placeholder",iframeLibraryLoading:".tiny_iframecms_iframe_library_loading",iframeLibraryFrame:".tiny_iframecms_iframe_library_frame"}}},_exports.default}));
//# sourceMappingURL=selectors.min.js.map
File diff suppressed because one or more lines are too long
@@ -115,7 +115,7 @@ const setupIframeOverlays = (editor, handleIframeAction) => {
}
.tiny-mediacms-edit-btn {
position: absolute;
top: -15px;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
@@ -139,6 +139,12 @@ export default class IframeEmbed {
return isNaN(parsed) ? null : parsed;
}
computeAspectRatioCSS(values) {
const w = values.width || 560;
const h = values.height || 315;
return `${w} / ${h}`;
}
buildEmbedUrl(parsed, options) {
if (parsed.isGeneric) {
return parsed.rawUrl;
@@ -167,13 +173,6 @@ export default class IframeEmbed {
);
url.searchParams.set('linkTitle', options.linkTitle ? '1' : '0');
if (options.width) {
url.searchParams.set('width', options.width.toString());
}
if (options.height) {
url.searchParams.set('height', options.height.toString());
}
if (options.startAtEnabled && options.startAt) {
const seconds = this.timeStringToSeconds(options.startAt);
if (seconds !== null && seconds > 0) {
@@ -197,14 +196,8 @@ export default class IframeEmbed {
: fallback;
};
let width, height;
if (this.isUpdating && (data.width || data.height)) {
width = data.width || 640;
height = data.height || 360;
} else {
width = 640;
height = 360;
}
const width = (this.isUpdating && data.width) ? data.width : 560;
const height = (this.isUpdating && data.height) ? data.height : 315;
return {
elementid: this.editor.getElement().id,
@@ -217,12 +210,8 @@ export default class IframeEmbed {
textLinkOnly: data.textLinkOnly || false,
startAtEnabled: data.startAtEnabled || false,
startAt: data.startAt || '0:00',
width: width,
height: height,
is16_9: !data.aspectRatio || data.aspectRatio === '16:9',
is4_3: data.aspectRatio === '4:3',
is1_1: data.aspectRatio === '1:1',
isCustom: data.aspectRatio === 'custom',
width,
height,
};
}
@@ -298,21 +287,31 @@ export default class IframeEmbed {
const src = this.selectedIframe.getAttribute('src');
const parsed = this.parseInput(src);
let width = parsed?.width || this.selectedIframe.getAttribute('width') || null;
let height = parsed?.height || this.selectedIframe.getAttribute('height') || null;
// Parse responsive dimensions from inline style
const style = this.selectedIframe.getAttribute('style') || '';
const maxWidthMatch = style.match(/max-width:\s*(\d+(?:\.\d+)?)px/);
const aspectRatioMatch = style.match(/aspect-ratio:\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)/);
width = width ? parseInt(width) : null;
height = height ? parseInt(height) : null;
const maxWidth = maxWidthMatch ? parseInt(maxWidthMatch[1]) : 560;
let height = 315;
if (aspectRatioMatch) {
const rw = parseFloat(aspectRatioMatch[1]);
const rh = parseFloat(aspectRatioMatch[2]);
if (rw > 0) {
height = Math.round(maxWidth * rh / rw);
}
}
return {
url: src,
width: width,
height: height,
width: maxWidth,
height,
showTitle: parsed?.showTitle ?? true,
linkTitle: parsed?.linkTitle ?? true,
showRelated: parsed?.showRelated ?? true,
showUserAvatar: parsed?.showUserAvatar ?? true,
startAtEnabled: parsed?.startAt !== null,
startAtEnabled: !!(parsed?.startAt),
startAt: parsed?.startAt || '0:00',
};
}
@@ -340,9 +339,6 @@ export default class IframeEmbed {
startAt: form
.querySelector(Selectors.IFRAME.elements.startAt)
.value.trim(),
aspectRatio: form.querySelector(
Selectors.IFRAME.elements.aspectRatio,
).value,
width: this.parseWidthHeight(
form.querySelector(Selectors.IFRAME.elements.width).value,
),
@@ -382,8 +378,9 @@ export default class IframeEmbed {
const context = {
src: embedUrl,
width: values.width,
height: values.height,
maxWidth: values.width || 560,
height: values.height || 315,
aspectRatioCSS: this.computeAspectRatioCSS(values),
};
const { html } = await Templates.renderForPromise(
@@ -447,22 +444,20 @@ export default class IframeEmbed {
<div class="alert alert-info">
<strong>Text link preview:</strong><br>
<a href="${hrefUrl}" target="_blank" data-mediacms-textlink="true">${linkText}</a>
<br><small class="text-muted mt-2 d-block">This link will not be auto-converted by the MediaCMS filter.</small>
</div>
`;
} else {
const previewWidth = Math.min(values.width, 400);
const scale = previewWidth / values.width;
const previewHeight = Math.round(values.height * scale);
const previewWidth = Math.min(values.width || 560, 400);
const previewHeight = Math.round(previewWidth * (values.height || 315) / (values.width || 560));
previewContainer.innerHTML = `
<iframe
src="${embedUrl}"
width="${previewWidth}"
height="${previewHeight}"
style="display:block;border:0;"
frameborder="0"
allowfullscreen
style="max-width: 100%;">
allowfullscreen>
</iframe>
`;
}
@@ -475,56 +470,18 @@ export default class IframeEmbed {
}, 500);
}
handleAspectRatioChange(root) {
handleWidthChange(root) {
const form = root.querySelector(Selectors.IFRAME.elements.form);
const aspectRatio = form.querySelector(
Selectors.IFRAME.elements.aspectRatio,
).value;
const dimensions = Selectors.IFRAME.aspectRatios[aspectRatio];
if (dimensions && aspectRatio !== 'custom') {
form.querySelector(Selectors.IFRAME.elements.width).value =
dimensions.width;
form.querySelector(Selectors.IFRAME.elements.height).value =
dimensions.height;
}
this.updatePreview(root);
}
handleWidthChange(root, newWidth) {
const form = root.querySelector(Selectors.IFRAME.elements.form);
const aspectRatio = form.querySelector(Selectors.IFRAME.elements.aspectRatio).value;
const widthInput = form.querySelector(Selectors.IFRAME.elements.width);
const heightInput = form.querySelector(Selectors.IFRAME.elements.height);
if (aspectRatio !== 'custom' && newWidth) {
const width = parseInt(newWidth) || 0;
const arr = aspectRatio.split(':');
const x = parseInt(arr[0]);
const y = parseInt(arr[1]);
const calculatedHeight = Math.round((width * y) / x);
heightInput.value = calculatedHeight;
const newWidth = parseInt(widthInput.value);
if (!isNaN(newWidth) && newWidth > 0) {
heightInput.value = Math.round(newWidth * 9 / 16);
}
this.handleInputChange(root);
}
handleHeightChange(root, newHeight) {
const form = root.querySelector(Selectors.IFRAME.elements.form);
const aspectRatio = form.querySelector(Selectors.IFRAME.elements.aspectRatio).value;
const widthInput = form.querySelector(Selectors.IFRAME.elements.width);
if (aspectRatio !== 'custom' && newHeight) {
const height = parseInt(newHeight) || 0;
const arr = aspectRatio.split(':');
const x = parseInt(arr[0]);
const y = parseInt(arr[1]);
const calculatedWidth = Math.round((height * x) / y);
widthInput.value = calculatedWidth;
}
handleHeightChange(root) {
this.handleInputChange(root);
}
@@ -573,6 +530,14 @@ export default class IframeEmbed {
} else {
this.editor.insertContent(html);
}
setTimeout(() => {
const body = this.editor.getBody();
body.querySelectorAll('p').forEach(p => {
if (p.innerHTML.trim() === '' || p.innerHTML === '<br>') {
p.remove();
}
});
}, 50);
}
}
}
@@ -636,17 +601,13 @@ export default class IframeEmbed {
() => this.handleInputChange(root, true),
);
form.querySelector(
Selectors.IFRAME.elements.aspectRatio,
).addEventListener('change', () => this.handleAspectRatioChange(root));
form.querySelector(Selectors.IFRAME.elements.width).addEventListener(
'input',
(e) => this.handleWidthChange(root, e.target.value),
() => this.handleWidthChange(root),
);
form.querySelector(Selectors.IFRAME.elements.height).addEventListener(
'input',
(e) => this.handleHeightChange(root, e.target.value),
() => this.handleHeightChange(root),
);
$root.on(ModalEvents.save, () => this.handleDialogueSubmission(modal));
@@ -1024,15 +985,14 @@ export default class IframeEmbed {
if (data.action === 'selectMedia' || data.action === 'mediaSelected') {
const embedUrl = data.embedUrl || data.url || '';
const videoId = data.mediaId || data.videoId || data.id || '';
if (embedUrl) {
this.selectIframeLibraryVideo(root, embedUrl, videoId);
this.selectIframeLibraryVideo(root, embedUrl);
}
return;
}
}
selectIframeLibraryVideo(root, embedUrl, videoId) {
selectIframeLibraryVideo(root, embedUrl) {
const form = root.querySelector(Selectors.IFRAME.elements.form);
const urlInput = form.querySelector(Selectors.IFRAME.elements.url);
@@ -68,17 +68,49 @@ const mediaCMSUrlToIframe = (url) => {
// Keep original URL if parsing fails
}
return `<iframe src="${embedUrl}" ` +
`style="width: 100%; aspect-ratio: 16 / 9; display: block; border: 0;" ` +
`allowfullscreen="allowfullscreen"></iframe>`;
return `<iframe src="${embedUrl}" width="560" height="315" ` +
`style="width:100%;max-width:560px;aspect-ratio:560 / 315;display:block;margin:0 auto;border:0;" ` +
`frameborder="0" allowfullscreen></iframe>`;
};
/**
* Regular expression to match standalone MediaCMS URLs in content.
* Matches URLs that are on their own line or surrounded by whitespace/tags.
* The URL must contain /embed?m= or /view?m= pattern.
* Convert standalone MediaCMS URL text nodes to iframes.
* Uses DOM traversal so URLs inside <a> tags (text links) are never touched.
*
* @param {string} html - Raw HTML string from the editor
* @returns {string} HTML with standalone URLs replaced by iframe HTML
*/
const MEDIACMS_URL_PATTERN = /(^|>|\s)(https?:\/\/[^\s<>"]+\/(?:embed|view)\?m=[^\s<>"]+)(<|\s|$)/g;
const convertUrlsToIframes = (html) => {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
const nodesToReplace = [];
const walk = (el) => {
for (const child of Array.from(el.childNodes)) {
if (child.nodeType === Node.TEXT_NODE) {
const url = child.textContent.trim();
if (isMediaCMSUrl(url)) {
nodesToReplace.push({node: child, url});
}
} else if (child.nodeType === Node.ELEMENT_NODE && child.tagName.toLowerCase() !== 'a') {
walk(child);
}
// Do not recurse into <a> tags — text links must be preserved as-is
}
};
walk(tempDiv);
nodesToReplace.forEach(({node, url}) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = mediaCMSUrlToIframe(url);
const iframe = wrapper.firstChild;
if (iframe) {
node.parentNode.replaceChild(iframe, node);
}
});
return tempDiv.innerHTML;
};
// eslint-disable-next-line no-async-promise-executor
export default new Promise(async(resolve) => {
@@ -102,69 +134,26 @@ export default new Promise(async(resolve) => {
// Setup auto-conversion of pasted MediaCMS URLs.
setupAutoConvert(editor);
// Convert MediaCMS URLs to iframes when content is loaded into the editor.
// This handles content from the database that was saved as just URLs.
// Convert standalone MediaCMS URL text nodes to iframes when loading content.
// Text links (<a data-mediacms-textlink>) are preserved because DOM traversal skips <a> tags.
editor.on('BeforeSetContent', (e) => {
if (e.content && typeof e.content === 'string') {
// Replace standalone MediaCMS URLs with iframes
e.content = e.content.replace(MEDIACMS_URL_PATTERN, (match, before, url, after) => {
// Verify it's a valid MediaCMS URL
if (isMediaCMSUrl(url)) {
return before + mediaCMSUrlToIframe(url) + after;
}
return match;
});
e.content = convertUrlsToIframes(e.content);
}
});
// Convert MediaCMS iframes back to just embed URLs when saving.
// This stores only the URL in the database, not the full iframe HTML.
// Clean up editor-only overlay elements when saving, preserving iframe HTML with its
// responsive styles (max-width, aspect-ratio) so dimensions survive the round-trip.
editor.on('GetContent', (e) => {
if (e.format === 'html') {
// Create a temporary container to manipulate the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = e.content;
// Remove edit buttons
// Remove edit buttons added by the overlay system (editor-only UI)
tempDiv.querySelectorAll('.tiny-mediacms-edit-btn').forEach(btn => btn.remove());
// Process all iframes - convert MediaCMS iframes to just URLs
tempDiv.querySelectorAll('iframe').forEach(iframe => {
const src = iframe.getAttribute('src');
if (isMediaCMSUrl(src)) {
// Check if iframe is inside a wrapper
const wrapper = iframe.closest('.tiny-mediacms-iframe-wrapper') ||
iframe.closest('.tiny-iframe-responsive');
// Create a text node with just the URL
const urlText = document.createTextNode(src);
// Wrap in a paragraph for proper formatting
const p = document.createElement('p');
p.appendChild(urlText);
if (wrapper) {
// Replace the entire wrapper with the URL
wrapper.parentNode.insertBefore(p, wrapper);
wrapper.remove();
} else {
// Replace just the iframe with the URL
iframe.parentNode.insertBefore(p, iframe);
iframe.remove();
}
}
});
// Clean up any remaining wrappers that might not have had MediaCMS iframes
tempDiv.querySelectorAll('.tiny-mediacms-iframe-wrapper').forEach(wrapper => {
const iframe = wrapper.querySelector('iframe');
if (iframe) {
wrapper.parentNode.insertBefore(iframe, wrapper);
}
wrapper.remove();
});
tempDiv.querySelectorAll('.tiny-iframe-responsive').forEach(wrapper => {
// Unwrap overlay divs, keeping the iframe HTML intact with its responsive styles
tempDiv.querySelectorAll('.tiny-mediacms-iframe-wrapper, .tiny-iframe-responsive').forEach(wrapper => {
const iframe = wrapper.querySelector('iframe');
if (iframe) {
wrapper.parentNode.insertBefore(iframe, wrapper);
@@ -134,7 +134,6 @@ export default {
textLinkOnly: '.tiny_iframecms_textlinkonly',
startAt: '.tiny_iframecms_startat',
startAtEnabled: '.tiny_iframecms_startat_enabled',
aspectRatio: '.tiny_iframecms_aspectratio',
width: '.tiny_iframecms_width',
height: '.tiny_iframecms_height',
preview: '.tiny_iframecms_preview',
@@ -153,11 +152,5 @@ export default {
iframeLibraryLoading: '.tiny_iframecms_iframe_library_loading',
iframeLibraryFrame: '.tiny_iframecms_iframe_library_frame',
},
aspectRatios: {
'16:9': { width: 560, height: 315 },
'4:3': { width: 560, height: 420 },
'1:1': { width: 400, height: 400 },
custom: null,
},
},
};
@@ -126,11 +126,6 @@ $string['showuseravatar'] = 'Show user avatar';
$string['responsive'] = 'Responsive';
$string['textlinkonly'] = 'Insert text link only';
$string['startat'] = 'Start at';
$string['aspectratio'] = 'Aspect Ratio';
$string['aspectratio_16_9'] = '16:9';
$string['aspectratio_4_3'] = '4:3';
$string['aspectratio_1_1'] = '1:1';
$string['aspectratio_custom'] = 'Custom';
$string['dimensions'] = 'Dimensions';
$string['preview'] = 'Preview';
$string['insertiframe'] = 'Insert';
@@ -88,19 +88,6 @@
</div>
</div>
<!-- Aspect Ratio -->
<div class="mb-3">
<label for="{{elementid}}_aspectratio" class="form-label font-weight-bold">
{{#str}} aspectratio, tiny_mediacms {{/str}}
</label>
<select class="form-control tiny_iframecms_aspectratio" id="{{elementid}}_aspectratio">
<option value="16:9" {{#is16_9}}selected{{/is16_9}}>{{#str}} aspectratio_16_9, tiny_mediacms {{/str}}</option>
<option value="4:3" {{#is4_3}}selected{{/is4_3}}>{{#str}} aspectratio_4_3, tiny_mediacms {{/str}}</option>
<option value="1:1" {{#is1_1}}selected{{/is1_1}}>{{#str}} aspectratio_1_1, tiny_mediacms {{/str}}</option>
<option value="custom" {{#isCustom}}selected{{/isCustom}}>{{#str}} aspectratio_custom, tiny_mediacms {{/str}}</option>
</select>
</div>
<!-- Dimensions -->
<div class="mb-3">
<label class="form-label font-weight-bold">
@@ -110,7 +97,7 @@
<div class="col-6">
<div class="input-group">
<input type="number" class="form-control tiny_iframecms_width"
id="{{elementid}}_width" value="{{width}}" placeholder="640" min="1" step="1">
id="{{elementid}}_width" value="{{width}}" placeholder="560" min="1" step="1">
<span class="input-group-text">px</span>
</div>
<small class="text-muted">{{#str}} width, tiny_mediacms {{/str}}</small>
@@ -118,7 +105,7 @@
<div class="col-6">
<div class="input-group">
<input type="number" class="form-control tiny_iframecms_height"
id="{{elementid}}_height" value="{{height}}" placeholder="360" min="1" step="1">
id="{{elementid}}_height" value="{{height}}" placeholder="315" min="1" step="1">
<span class="input-group-text">px</span>
</div>
<small class="text-muted">{{#str}} height, tiny_mediacms {{/str}}</small>
@@ -28,4 +28,4 @@
"aspectRatioClass": "ratio-16-9"
}
}}
<iframe width="{{width}}" height="{{height}}" src="{{src}}" frameBorder="0" allowFullScreen></iframe>
<div class="tiny-mediacms-iframe-wrapper" style="margin:0;padding:0;"><iframe src="{{src}}" width="{{maxWidth}}" height="{{height}}" style="width:100%;max-width:{{maxWidth}}px;height:auto;aspect-ratio:{{aspectRatioCSS}};display:block;margin:0 auto;border:0;" frameborder="0" allowfullscreen></iframe></div>
@@ -0,0 +1,98 @@
<div class="form-group{% if form.state.errors or form.confirm_state.errors %} has-error{% endif %}">
<div class="control-label-container">
<label class="control-label">State</label>
</div>
<div class="controls">
<div class="state-options">
{% for val, lbl in form.fields.state.choices %}{% if val == 'private' %}
<label class="state-option">
<input type="radio" name="state" value="private"
{% if form.state.value == 'private' %}checked{% endif %}>
Private
</label>
{% endif %}{% endfor %}
{% for val, lbl in form.fields.state.choices %}{% if val == 'unlisted' %}
<label class="state-option">
<input type="radio" name="state" value="unlisted"
{% if form.state.value == 'unlisted' %}checked{% endif %}>
Unlisted
</label>
{% endif %}{% endfor %}
{% if form.fields.shared %}
<label class="state-option shared-option">
<input type="checkbox" name="shared" id="id_shared"
{% if form.shared.value %}checked{% endif %}>
Shared
</label>
{% endif %}
{% for val, lbl in form.fields.state.choices %}{% if val == 'public' %}
<label class="state-option">
<input type="radio" name="state" value="public"
{% if form.state.value == 'public' %}checked{% endif %}>
Public
</label>
{% endif %}{% endfor %}
</div>
{% if form.state.errors %}
<div class="error-container" style="margin-top:0.5rem;">
{% for error in form.state.errors %}<p class="invalid-feedback">{{ error }}</p>{% endfor %}
</div>
{% endif %}
{% if form.fields.shared %}
<div id="shared-info" style="display:none; margin-top:0.5rem; font-size:0.875rem; color:#555;">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle; margin-right:4px; flex-shrink:0;"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>To share media with someone, go to My Media &gt; select media &gt; Bulk Actions &gt; Share with&hellip;
</div>
{% endif %}
</div>
{% if form.fields.shared %}{% if form.was_shared %}
<div id="shared-deselect-warning" style="display:none; margin-top:0.75rem; padding:0.75rem; background:#fff3cd; border:1px solid #ffc107; border-radius:4px;">
<label style="display:flex; gap:0.5rem; align-items:flex-start; cursor:pointer; margin:0;">
<input type="checkbox" name="confirm_state" id="id_confirm_state"
{% if form.confirm_state.value %}checked{% endif %}
style="margin-top:3px; flex-shrink:0;">
<span id="shared-deselect-msg-private">I understand that changing to Private will remove all sharing. Currently this media is shared by me with other users (visible in 'My Media &gt; Shared by Me' page).</span>
<span id="shared-deselect-msg-other" style="display:none;">I understand that unchecking Shared will affect existing sharing settings.</span>
</label>
{% if form.confirm_state.errors %}
<div style="margin-top:0.5rem;">
{% for error in form.confirm_state.errors %}<p class="invalid-feedback" style="color:#dc3545;">{{ error }}</p>{% endfor %}
</div>
{% endif %}
</div>
{% endif %}{% endif %}
{% if form.fields.shared %}
<script>
(function() {
var sharedCb = document.getElementById('id_shared');
var warning = document.getElementById('shared-deselect-warning');
var sharedInfo = document.getElementById('shared-info');
var msgPrivate = document.getElementById('shared-deselect-msg-private');
var msgOther = document.getElementById('shared-deselect-msg-other');
function getSelectedState() {
var radios = document.querySelectorAll('input[name="state"]');
for (var i = 0; i < radios.length; i++) {
if (radios[i].checked) return radios[i].value;
}
return '';
}
function updateWarning() {
var isShared = sharedCb.checked;
if (warning) warning.style.display = isShared ? 'none' : 'block';
if (sharedInfo) sharedInfo.style.display = isShared ? 'block' : 'none';
if (!isShared) {
var state = getSelectedState();
if (msgPrivate) msgPrivate.style.display = state === 'private' ? 'inline' : 'none';
if (msgOther) msgOther.style.display = state !== 'private' ? 'inline' : 'none';
}
}
if (sharedCb) {
sharedCb.addEventListener('change', updateWarning);
document.querySelectorAll('input[name="state"]').forEach(function(r) {
r.addEventListener('change', updateWarning);
});
updateWarning();
}
})();
</script>
{% endif %}
</div>