MDL-78428 tiny_media: Handled save-update media

This commit is contained in:
Stevani Andolo
2025-03-18 13:23:04 +08:00
parent 62104e46e1
commit 73e81651ba
7 changed files with 147 additions and 4 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,6 +7,6 @@ define("tiny_media/embed/embedhelpers",["exports","../selectors","../helpers","c
* @module tiny_media/embed/embedhelpers
* @copyright 2024 Stevani Andolo <stevani@hotmail.com.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.prepareMoodleLang=_exports.mediaDetailsTemplateContext=_exports.isUrlFromKnownMediaSites=_exports.insertMediaTemplateContext=_exports.getSelectedMediaElement=_exports.getMoodleLangObj=_exports.getHelpStrings=_exports.getEmbeddedMediaDetails=_exports.formatMediaUrl=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};_exports.insertMediaTemplateContext=props=>({mediaType:props.mediaType,showDropzone:props.canShowDropZone,showFilePicker:props.canShowFilePicker});_exports.getSelectedMediaElement=editor=>{let mediaType=null,selectedMedia=null;const mediaElm=editor.selection.getNode();return mediaElm?"video"===mediaElm.nodeName.toLowerCase()||"audio"===mediaElm.nodeName.toLowerCase()?(mediaType=mediaElm.nodeName.toLowerCase(),selectedMedia=mediaElm):"a"===mediaElm.nodeName.toLowerCase()?(mediaType="link",selectedMedia=mediaElm):mediaElm.querySelector("video")?(mediaType="video",selectedMedia=mediaElm.querySelector("video")):mediaElm.querySelector("audio")&&(mediaType="audio",selectedMedia=mediaElm.querySelector("audio")):(mediaType=null,selectedMedia=null),[mediaType,selectedMedia]};_exports.formatMediaUrl=url=>{const params=(0,_helpers.convertStringUrlToObject)(url);if(url.includes(_selectors.default.EMBED.mediaSites.youtube)){let fetchedUrl=null,fetchedUrlValue=null;for(const k in params)if(url.includes(k)){fetchedUrl=k,fetchedUrlValue=params[k],delete params[k];break}url=(url=fetchedUrl.replace("watch?v","embed/"))+fetchedUrlValue+"?"+(0,_helpers.createUrlParams)(params)}return url};_exports.isUrlFromKnownMediaSites=url=>{let state=!1;const sites=_selectors.default.EMBED.mediaSites;for(const site in sites)if(url.includes(sites[site])){state=!0;break}return state};_exports.mediaDetailsTemplateContext=async props=>{const context={bodyTemplate:_selectors.default.EMBED.template.body.mediaDetailsBody,footerTemplate:_selectors.default.EMBED.template.footer.mediaDetailsFooter,isVideo:"video"===props.mediaType,isAudio:"audio"===props.mediaType,isLink:"link"===props.mediaType,isUpdating:props.isUpdating};return props.mediaData?{...context,...props.mediaData}:{...context,...await props.mediaTemplateContext}};_exports.getHelpStrings=async()=>{const[customsize]=await(0,_str.getStrings)(["customsize_help"].map((key=>({key:key,component:_common.component}))));return{customsize:customsize}};_exports.prepareMoodleLang=editor=>{const moodleLangs=(0,_options.getMoodleLang)(editor),currentLanguage=(0,_options.getCurrentLanguage)(editor);return{installed:Object.entries(moodleLangs.installed).map((_ref=>{let[lang,code]=_ref;return{lang:lang,code:code,default:lang===currentLanguage}})),available:Object.entries(moodleLangs.available).map((_ref2=>{let[lang,code]=_ref2;return{lang:lang,code:code,default:lang===currentLanguage}}))}};_exports.getMoodleLangObj=(subtitleLang,editor)=>{const{available:available}=(0,_options.getMoodleLang)(editor);return available[subtitleLang]?{lang:subtitleLang,code:available[subtitleLang]}:null};_exports.getEmbeddedMediaDetails=props=>{const tracks={subtitles:[],captions:[],descriptions:[],chapters:[],metadata:[]};props.root.querySelectorAll(_selectors.default.EMBED.elements.mediaMetadataTabPane).forEach((metaData=>{metaData.querySelectorAll(_selectors.default.EMBED.elements.track).forEach((track=>{tracks[metaData.dataset.trackKind].push({src:track.querySelector(_selectors.default.EMBED.elements.url).value,srclang:track.querySelector(_selectors.default.EMBED.elements.trackLang).value,label:track.querySelector(_selectors.default.EMBED.elements.trackLabel).value,defaultTrack:track.querySelector(_selectors.default.EMBED.elements.trackDefault).checked})}))}));const querySelector=element=>props.root.querySelector(element),mediaDataProps={};return mediaDataProps.media={type:props.mediaType,sources:props.media,poster:querySelector(_selectors.default.EMBED.elements.videoTag).getAttribute("poster"),title:querySelector(_selectors.default.EMBED.elements.title).value,width:querySelector(_selectors.default.EMBED.elements.width).value,height:querySelector(_selectors.default.EMBED.elements.height).value,autoplay:querySelector(_selectors.default.EMBED.elements.mediaAutoplay).checked,loop:querySelector(_selectors.default.EMBED.elements.mediaLoop).checked,muted:querySelector(_selectors.default.EMBED.elements.mediaMute).checked,controls:querySelector(_selectors.default.EMBED.elements.mediaControl).checked,tracks:tracks},mediaDataProps.link=!1,mediaDataProps}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.prepareMoodleLang=_exports.mediaDetailsTemplateContext=_exports.isUrlFromKnownMediaSites=_exports.insertMediaTemplateContext=_exports.getSelectedMediaElement=_exports.getMoodleLangObj=_exports.getLinkBasedUrl=_exports.getHelpStrings=_exports.getEmbeddedMediaDetails=_exports.formatMediaUrl=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};_exports.insertMediaTemplateContext=props=>({mediaType:props.mediaType,showDropzone:props.canShowDropZone,showFilePicker:props.canShowFilePicker});_exports.getSelectedMediaElement=editor=>{let mediaType=null,selectedMedia=null;const mediaElm=editor.selection.getNode();return mediaElm?"video"===mediaElm.nodeName.toLowerCase()||"audio"===mediaElm.nodeName.toLowerCase()?(mediaType=mediaElm.nodeName.toLowerCase(),selectedMedia=mediaElm):"a"===mediaElm.nodeName.toLowerCase()?(mediaType="link",selectedMedia=mediaElm):mediaElm.querySelector("video")?(mediaType="video",selectedMedia=mediaElm.querySelector("video")):mediaElm.querySelector("audio")&&(mediaType="audio",selectedMedia=mediaElm.querySelector("audio")):(mediaType=null,selectedMedia=null),[mediaType,selectedMedia]};_exports.formatMediaUrl=url=>{const params=(0,_helpers.convertStringUrlToObject)(url);if(url.includes(_selectors.default.EMBED.mediaSites.youtube)){let fetchedUrl=null,fetchedUrlValue=null;for(const k in params)if(url.includes(k)){fetchedUrl=k,fetchedUrlValue=params[k],delete params[k];break}url=(url=fetchedUrl.replace("watch?v","embed/"))+fetchedUrlValue+"?"+(0,_helpers.createUrlParams)(params)}return url};_exports.isUrlFromKnownMediaSites=url=>{let state=!1;const sites=_selectors.default.EMBED.mediaSites;for(const site in sites)if(url.includes(sites[site])){state=!0;break}return state};_exports.mediaDetailsTemplateContext=async props=>{const context={bodyTemplate:_selectors.default.EMBED.template.body.mediaDetailsBody,footerTemplate:_selectors.default.EMBED.template.footer.mediaDetailsFooter,isVideo:"video"===props.mediaType,isAudio:"audio"===props.mediaType,isLink:"link"===props.mediaType,isUpdating:props.isUpdating};return props.mediaData?{...context,...props.mediaData}:{...context,...await props.mediaTemplateContext}};_exports.getHelpStrings=async()=>{const[customsize]=await(0,_str.getStrings)(["customsize_help"].map((key=>({key:key,component:_common.component}))));return{customsize:customsize}};_exports.prepareMoodleLang=editor=>{const moodleLangs=(0,_options.getMoodleLang)(editor),currentLanguage=(0,_options.getCurrentLanguage)(editor);return{installed:Object.entries(moodleLangs.installed).map((_ref=>{let[lang,code]=_ref;return{lang:lang,code:code,default:lang===currentLanguage}})),available:Object.entries(moodleLangs.available).map((_ref2=>{let[lang,code]=_ref2;return{lang:lang,code:code,default:lang===currentLanguage}}))}};_exports.getMoodleLangObj=(subtitleLang,editor)=>{const{available:available}=(0,_options.getMoodleLang)(editor);return available[subtitleLang]?{lang:subtitleLang,code:available[subtitleLang]}:null};_exports.getEmbeddedMediaDetails=props=>{const tracks={subtitles:[],captions:[],descriptions:[],chapters:[],metadata:[]};props.root.querySelectorAll(_selectors.default.EMBED.elements.mediaMetadataTabPane).forEach((metaData=>{metaData.querySelectorAll(_selectors.default.EMBED.elements.track).forEach((track=>{tracks[metaData.dataset.trackKind].push({src:track.querySelector(_selectors.default.EMBED.elements.url).value,srclang:track.querySelector(_selectors.default.EMBED.elements.trackLang).value,label:track.querySelector(_selectors.default.EMBED.elements.trackLabel).value,defaultTrack:track.querySelector(_selectors.default.EMBED.elements.trackDefault).checked})}))}));const querySelector=element=>props.root.querySelector(element),mediaDataProps={};return mediaDataProps.media={type:props.mediaType,sources:props.media,poster:querySelector(_selectors.default.EMBED.elements.videoTag).getAttribute("poster"),title:querySelector(_selectors.default.EMBED.elements.title).value,width:querySelector(_selectors.default.EMBED.elements.width).value,height:querySelector(_selectors.default.EMBED.elements.height).value,autoplay:querySelector(_selectors.default.EMBED.elements.mediaAutoplay).checked,loop:querySelector(_selectors.default.EMBED.elements.mediaLoop).checked,muted:querySelector(_selectors.default.EMBED.elements.mediaMute).checked,controls:querySelector(_selectors.default.EMBED.elements.mediaControl).checked,tracks:tracks},mediaDataProps.link=!1,mediaDataProps};_exports.getLinkBasedUrl=url=>{if(url.includes(_selectors.default.EMBED.mediaSites.youtube)&&url.includes("embed/")){const urlParams=url.split("embed/");let id=null;id=urlParams[1].includes("?")?urlParams[1].split("?")[0]:urlParams[1],url="".concat(urlParams[0],"watch?v=").concat(id,"&")}return url}}));
//# sourceMappingURL=embedhelpers.min.js.map
File diff suppressed because one or more lines are too long
@@ -39,6 +39,7 @@ import {
isValidUrl,
convertStringUrlToObject,
stopMediaLoading,
createUrlParams,
} from '../helpers';
import * as ModalEvents from 'core/modal_events';
import {displayFilepicker} from 'editor_tiny/utils';
@@ -47,7 +48,9 @@ import {
getHelpStrings,
prepareMoodleLang,
getMoodleLangObj,
getLinkBasedUrl,
} from "./embedhelpers";
import Templates from 'core/templates';
export class EmbedHandler {
@@ -413,6 +416,119 @@ export class EmbedHandler {
addUrl.disabled = !(url !== "" && isValidUrl(url));
}
/**
* Get media html to be inserted or updated into tiny.
*
* @param {html} form Selected element
* @returns {string} String of html
*/
getMediaHTML = (form) => {
let mediumType = this.root.querySelector(Selectors.EMBED.elements.previewClass).nodeName.toLowerCase();
mediumType = (mediumType === 'iframe') ? 'link' : mediumType;
const tabContent = form.querySelector('.tab-content');
const callback = 'getMediaHTML' + mediumType[0].toUpperCase() + mediumType.substr(1);
return this[callback](tabContent);
};
/**
* Get media as link.
*
* @returns {string} String of html
*/
getMediaHTMLLink = () => {
const details = document.querySelector(Selectors.EMBED.elements.mediaDetailsBody);
const url = details.querySelector(Selectors.EMBED.elements.preview).src ?? null;
const urlParamString = createUrlParams({
controls: details.querySelector(Selectors.EMBED.elements.mediaControl).checked ? 1 : 0,
autoplay: details.querySelector(Selectors.EMBED.elements.mediaAutoplay).checked ? 1 : 0,
mute: details.querySelector(Selectors.EMBED.elements.mediaMute).checked ? 1 : 0,
});
const name = details.querySelector(Selectors.EMBED.elements.title).value || false;
const context = {
url: getLinkBasedUrl(url) + urlParamString,
name: name ? name.trim() : false,
};
return context.url ? Templates.renderForPromise('tiny_media/embed/embed_media_link', context) : '';
};
/**
* Get media as video.
*
* @param {html} tab Selected element
* @returns {string} String of html.
*/
getMediaHTMLVideo = (tab) => {
const details = document.querySelector(Selectors.EMBED.elements.mediaDetailsBody);
const context = this.getContextForMediaHTML(tab, details);
context.width = details.querySelector(Selectors.EMBED.elements.width).value || false;
context.height = details.querySelector(Selectors.EMBED.elements.height).value || false;
context.poster = details.querySelector(Selectors.EMBED.elements.videoTag).poster || false;
return context.sources ? Templates.renderForPromise('tiny_media/embed/embed_media_video', context) : '';
};
/**
* Get media as audio.
*
* @param {html} tab Selected element
* @returns {string} String of html.
*/
getMediaHTMLAudio = (tab) => {
const details = document.querySelector(Selectors.EMBED.elements.mediaDetailsBody);
const context = this.getContextForMediaHTML(tab, details);
return context.sources.length ? Templates.renderForPromise('tiny_media/embed/embed_media_audio', context) : '';
};
/**
* Get previewed media data.
*
* @param {html} tab Selected element
* @param {html} details Selected element
* @returns {object}
*/
getContextForMediaHTML = (tab, details) => {
const tracks = Array.from(tab.querySelectorAll(Selectors.EMBED.elements.track)).map(track => ({
track: track.querySelector(Selectors.EMBED.elements.trackSource + ' ' + Selectors.EMBED.elements.url).value,
kind: this.getTrackTypeFromTabPane(track.closest('.tab-pane')),
label: track.querySelector(Selectors.EMBED.elements.trackLabel).value ||
track.querySelector(Selectors.EMBED.elements.trackLang).value,
srclang: track.querySelector(Selectors.EMBED.elements.trackLang).value,
defaultTrack: track.querySelector(Selectors.EMBED.elements.trackDefault).checked ? "true" : null
})).filter((track) => !!track.track);
const sources = details.querySelector(Selectors.EMBED.elements.preview).src ?? null;
const title = details.querySelector(Selectors.EMBED.elements.title).value;
return {
sources,
description: tab.querySelector(Selectors.EMBED.elements.url).value || false,
tracks,
showControls: details.querySelector(Selectors.EMBED.elements.mediaControl).checked,
autoplay: details.querySelector(Selectors.EMBED.elements.mediaAutoplay).checked,
muted: details.querySelector(Selectors.EMBED.elements.mediaMute).checked,
loop: details.querySelector(Selectors.EMBED.elements.mediaLoop).checked,
title: title !== '' ? title.trim() : false,
};
};
/**
* Handle the insert/update media in tiny editor.
*
* @param {event} event
* @param {object} modal Object of current modal
*/
handleDialogueSubmission = async(event, modal) => {
const {html} = await this.getMediaHTML(modal.getRoot()[0]);
if (html) {
if (this.isUpdating) {
this.selectedMedia.outerHTML = html;
this.isUpdating = false;
} else {
this.editor.insertContent(html);
}
}
};
registerEventListeners = async(modal) => {
await modal.getBody();
const $root = modal.getRoot();
@@ -440,5 +556,7 @@ export class EmbedHandler {
}
});
});
$root.on(ModalEvents.save, this.handleDialogueSubmission.bind(this));
};
}
@@ -269,3 +269,25 @@ export const getEmbeddedMediaDetails = (props) => {
mediaDataProps.link = false;
return mediaDataProps;
};
/**
* Get based url from the link.
*
* @param {string} url
* @returns {string}
*/
export const getLinkBasedUrl = (url) => {
if (url.includes(Selectors.EMBED.mediaSites.youtube)) {
if (url.includes('embed/')) {
const urlParams = url.split('embed/');
let id = null;
if (urlParams[1].includes('?')) {
id = urlParams[1].split('?')[0];
} else {
id = urlParams[1];
}
url = `${urlParams[0]}watch?v=${id}&`;
}
}
return url;
};
@@ -46,6 +46,9 @@
</div>
<div class="col-md-6 text-right mt-2 md-0 p-0">
<button type="button" class="btn btn-secondary" data-action="back-to-media-details">{{#str}} back, tiny_media {{/str}}</button>
<button class="tiny_media_submit btn btn-primary" data-action="save">
{{#str}} saveimage, tiny_media {{/str}}
</button>
</div>
</div>
{{/isLink}}