diff --git a/amd/build/clientrender.min.js b/amd/build/clientrender.min.js
new file mode 100644
index 0000000..d26913e
--- /dev/null
+++ b/amd/build/clientrender.min.js
@@ -0,0 +1,10 @@
+/**
+ * Client-side rendering for oembed content.
+ *
+ * @module filter_oembed/clientrender
+ * @copyright 2026
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define("filter_oembed/clientrender",["jquery","core/log"],(function($,Log){return{init:function(){$(".oembed-client-render").each((function(){var $container=$(this);if(!$container.data("processed")){$container.data("processed",!0);var oembedUrl=$container.data("oembed-url"),originalUrl=$container.data("original-url"),params=$container.data("params");if(!oembedUrl)return Log.debug("filter_oembed/clientrender: No oembed URL provided"),void $container.html(''+originalUrl+"");$.ajax({url:oembedUrl,dataType:"json",success:function(data){if(data&&data.html){var embed=data.html;if(params){var paramStr="";for(var key in params)params.hasOwnProperty(key)&&(paramStr+="&"+key+"="+encodeURIComponent(params[key]));embed=embed.replace("?feature=oembed","?feature=oembed"+paramStr)}var aspectRatio=0;if(data.width&&data.height&&(aspectRatio=data.height/data.width),aspectRatio>0){var paddiv='
';$container.html('
'+embed+paddiv+"
")}else $container.html('
'+embed+"
")}else Log.debug("filter_oembed/clientrender: No HTML in oembed response for "+originalUrl),$container.html(''+originalUrl+"")},error:function(jqXHR,textStatus,errorThrown){Log.debug("filter_oembed/clientrender: Error loading oembed content for "+originalUrl+" - "+textStatus+": "+errorThrown),$container.html(''+originalUrl+"")}})}}))}}}));
+
+//# sourceMappingURL=clientrender.min.js.map
\ No newline at end of file
diff --git a/amd/build/manageproviders.min.js b/amd/build/manageproviders.min.js
index 21dd840..64f8b48 100644
--- a/amd/build/manageproviders.min.js
+++ b/amd/build/manageproviders.min.js
@@ -1 +1,23 @@
-define(["jquery","core/notification","core/ajax","core/templates","core/fragment","core/str","filter_oembed/list"],function(a,b,c,d,e,f,g){return{prevEditId:null,reloadRow:function(e,f,g,h){g=g?g:"reload",c.call([{methodname:"filter_oembed_provider_manage",args:{pid:e,action:g},done:function(b){d.render("filter_oembed/managementpagerow",b.providermodel).done(function(b){a(f).replaceWith(b),f=a("#oembed-display-providers_"+e),"function"==typeof h&&h(f)})},fail:function(a){b.exception(a)}}],!0,!0)},reloadProviders:function(e){c.call([{methodname:"filter_oembed_providers",args:{scope:"all"},done:function(b){d.render("filter_oembed/managementpage",b).done(function(b){var c=a(a.parseHTML(b)).html();a("#providermanagement").html(c),"function"==typeof e&&e()})},fail:function(a){b.exception(a)}}],!0,!0)},listenEnableDisable:function(){var b=this;a("#providermanagement").on("click",".oembed-provider-actions .filter-oembed-visibility",function(c){c.preventDefault();var d=a(this).parents("tr")[0],e=a(d).data("pid"),f=!a(d).hasClass("dimmed_text"),g=f?"disable":"enable";b.reloadRow(e,d,g)})},listenDelete:function(){var d=function(d){var e=a(d).data("pid");c.call([{methodname:"filter_oembed_provider_manage",args:{pid:e,action:"delete"},done:function(){a(d).remove()},fail:function(a){b.exception(a)}}],!0,!0)};a("#providermanagement").on("click",".oembed-provider-actions .filter-oembed-delete",function(c){c.preventDefault();var e=a(this).parents("tr")[0],g=a(a(this).parents("td").find(".list-providername")[0]).text();f.get_strings([{key:"deleteprovidertitle",component:"filter_oembed"},{key:"deleteproviderconfirm",component:"filter_oembed",param:g},{key:"ok",component:"core"},{key:"cancel",component:"core"}]).done(function(a){var c=a[0],f=a[1],g=a[2],h=a[3];b.confirm(c,f,g,h,function(){d(e)})})})},listenEdit:function(){var b=this,c=function(b){var c="#oembed-display-providers_"+b;a(c).removeClass("oembed-provider-editing"),a(c+" form").remove(),a(c+" td div.alert").remove()},f=function(b,c,f){var g,h=new RegExp("(?:course-)(\\S)"),i=h.exec(a("body").attr("class")),j=parseInt(i[1]);g=c?{formdata:c,pid:b}:{pid:b},e.loadFragment("filter_oembed","provider",j,g).done(function(c,e){a("#oembed-display-providers_"+b).addClass("oembed-provider-editing"),d.replaceNodeContents(a("#oembed-display-providers_"+b+" .oembed-provider-details"),c,e),"function"==typeof f&&f()})};a("#providermanagement").on("click",".oembed-provider-actions .filter-oembed-edit",function(d){d.preventDefault();var e=a(this).parents("tr")[0],g=a(e).data("pid");null!==b.prevEditId&&(c(b.prevEditId),c(g)),b.prevEditId=g,f(g)}),a("#providermanagement").on("click",".oembed-provider-details form #id_submitbutton",function(d){d.preventDefault();var e=a(this).parents("tr")[0],g=a(e).data("pid"),h=a(this).parents("form")[0],i=a(h).find('input[name="source"]').val();a(h).trigger("save-form-state");var j=a(h).serialize();f(g,j,function(){var d="#oembed-display-providers_"+g+" .oembed-provider-details",f=d+" div.alert-success",h=a(f);if(h.length){var j=h[0].outerHTML;if(c(g),i.indexOf("download::")>-1){var k=d+" .js-oembed-newprovider",l=a(k);l.length&&(g=l.data("newproviderid"))}var m=function(){var b=a("#oembed-display-providers_"+g+" td");a(b).append(j),a(b).find(" div.alert-success").attr("tabindex",-1),a(b).find(" div.alert-success").focus()};i.indexOf("download::")>-1?b.reloadProviders(m):b.reloadRow(g,e,"reload",m)}})}),a("#providermanagement").on("click",".oembed-provider-details form #id_cancel",function(b){b.preventDefault();var d=a(this).parents("tr")[0];c(a(d).data("pid"))})},init:function(){var a={valueNames:["list-providername"]};new g("providermanagement",a),this.listenEnableDisable(),this.listenDelete(),this.listenEdit()}}});
\ No newline at end of file
+/**
+ * This file is part of Moodle - http://moodle.org/
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ *
+ * @package filter_oembed
+ * @copyright Guy Thomas / moodlerooms.com 2016
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define("filter_oembed/manageproviders",["jquery","core/notification","core/ajax","core/templates","core/fragment","core/str","filter_oembed/list"],(function($,notification,ajax,templates,fragment,str,List){return{prevEditId:null,reloadRow:function(pid,row,action,callback){action=action||"reload",ajax.call([{methodname:"filter_oembed_provider_manage",args:{pid:pid,action:action},done:function(response){templates.render("filter_oembed/managementpagerow",response.providermodel).done((function(result){$(row).replaceWith(result),row=$("#oembed-display-providers_"+pid),"function"==typeof callback&&callback(row)}))},fail:function(response){notification.exception(response)}}],!0,!0)},reloadProviders:function(callback){ajax.call([{methodname:"filter_oembed_providers",args:{scope:"all"},done:function(response){templates.render("filter_oembed/managementpage",response).done((function(result){var resultHtml=$($.parseHTML(result)).html();$("#providermanagement").html(resultHtml),"function"==typeof callback&&callback()}))},fail:function(response){notification.exception(response)}}],!0,!0)},listenEnableDisable:function(){var self=this;$("#providermanagement").on("click",".oembed-provider-actions .filter-oembed-visibility",(function(e){e.preventDefault();var row=$(this).parents("tr")[0],pid=$(row).data("pid"),action=!$(row).hasClass("dimmed_text")?"disable":"enable";self.reloadRow(pid,row,action)}))},listenDelete:function(){$("#providermanagement").on("click",".oembed-provider-actions .filter-oembed-delete",(function(e){e.preventDefault();var row=$(this).parents("tr")[0],providerName=$($(this).parents("td").find(".list-providername")[0]).text();str.get_strings([{key:"deleteprovidertitle",component:"filter_oembed"},{key:"deleteproviderconfirm",component:"filter_oembed",param:providerName},{key:"ok",component:"core"},{key:"cancel",component:"core"}]).done((function(strings){var delTitle=strings[0],delConf=strings[1],ok=strings[2],cancel=strings[3];notification.confirm(delTitle,delConf,ok,cancel,(function(){!function(row){var pid=$(row).data("pid");ajax.call([{methodname:"filter_oembed_provider_manage",args:{pid:pid,action:"delete"},done:function(){$(row).remove()},fail:function(response){notification.exception(response)}}],!0,!0)}(row)}))}))}))},listenEdit:function(){var self=this,turnEditingOff=function(provderId){var sel="#oembed-display-providers_"+provderId;$(sel).removeClass("oembed-provider-editing"),$(sel+" form").remove(),$(sel+" td div.alert").remove()},updateProviderForm=function(pid,data,callback){var params,result=new RegExp("(?:course-)(\\S)").exec($("body").attr("class")),contextid=parseInt(result[1]);params=data?{formdata:data,pid:pid}:{pid:pid},fragment.loadFragment("filter_oembed","provider",contextid,params).done((function(html,js){$("#oembed-display-providers_"+pid).addClass("oembed-provider-editing"),templates.replaceNodeContents($("#oembed-display-providers_"+pid+" .oembed-provider-details"),html,js),"function"==typeof callback&&callback()}))};$("#providermanagement").on("click",".oembed-provider-actions .filter-oembed-edit",(function(e){e.preventDefault();var row=$(this).parents("tr")[0],pid=$(row).data("pid");null!==self.prevEditId&&(turnEditingOff(self.prevEditId),turnEditingOff(pid)),self.prevEditId=pid,updateProviderForm(pid)})),$("#providermanagement").on("click",".list-providername",(function(e){e.preventDefault();var row=$(this).parents("tr")[0],pid=$(row).data("pid");null!==self.prevEditId&&(turnEditingOff(self.prevEditId),turnEditingOff(pid)),self.prevEditId=pid,updateProviderForm(pid)})),$("#providermanagement").on("click",".oembed-provider-details form #id_submitbutton",(function(e){e.preventDefault();var row=$(this).parents("tr")[0],pid=$(row).data("pid"),form=$(this).parents("form")[0],source=$(form).find('input[name="source"]').val();$(form).trigger("save-form-state");var data=$(form).serialize();updateProviderForm(pid,data,(function(){var detailsSel="#oembed-display-providers_"+pid+" .oembed-provider-details",successEl=$(detailsSel+" div.alert-success");if(successEl.length){var successHTML=successEl[0].outerHTML;turnEditingOff(pid);var isNewProvider=!1;if(source.indexOf("download::")>-1||source.indexOf("local::new")>-1){var newProviderEl=$(detailsSel+" .js-oembed-newprovider");newProviderEl.length&&(pid=newProviderEl.data("newproviderid"),isNewProvider=!0)}var onReload=function(){$("#providermanagement .alert-success").remove(),$("#providermanagement").prepend(successHTML),$("#providermanagement div.alert-success").attr("tabindex",-1),$("#providermanagement div.alert-success").focus()};source.indexOf("download::")>-1||isNewProvider?self.reloadProviders(onReload):self.reloadRow(pid,row,"reload",onReload)}}))})),$("#providermanagement").on("click",".oembed-provider-details form #id_cancel",(function(e){e.preventDefault();var row=$(this).parents("tr")[0];turnEditingOff($(row).data("pid"))}))},listenAddProvider:function(){var self=this;$("#oembed-add-provider").on("click",(function(e){if(e.preventDefault(),null!==self.prevEditId){var sel="#oembed-display-providers_"+self.prevEditId;$(sel).removeClass("oembed-provider-editing"),$(sel+" form").remove(),$(sel+" td div.alert").remove()}var newRowId="oembed-display-providers_0",existingNewRow=$("#"+newRowId);existingNewRow.length&&existingNewRow.remove();$("#oembed-local-providers-head-row").after('
'),self.prevEditId=0;var result=new RegExp("(?:course-)(\\S)").exec($("body").attr("class")),contextid=parseInt(result[1]);fragment.loadFragment("filter_oembed","provider",contextid,{pid:0}).done((function(html,js){templates.replaceNodeContents($("#"+newRowId+" .oembed-provider-details"),html,js)}))}))},init:function(){new List("providermanagement",{valueNames:["list-providername"]}),this.listenEnableDisable(),this.listenDelete(),this.listenEdit(),this.listenAddProvider()}}}));
+
+//# sourceMappingURL=manageproviders.min.js.map
\ No newline at end of file
diff --git a/amd/src/clientrender.js b/amd/src/clientrender.js
new file mode 100644
index 0000000..cdaf701
--- /dev/null
+++ b/amd/src/clientrender.js
@@ -0,0 +1,94 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * Client-side rendering for oembed content.
+ *
+ * @module filter_oembed/clientrender
+ * @copyright 2026
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/log'], function($, Log) {
+
+ /**
+ * Initialize client-side rendering for oembed content.
+ */
+ var init = function() {
+ $('.oembed-client-render').each(function() {
+ var $container = $(this);
+
+ if ($container.data('processed')) {
+ return;
+ }
+ $container.data('processed', true);
+
+ var oembedUrl = $container.data('oembed-url');
+ var originalUrl = $container.data('original-url');
+ var params = $container.data('params');
+
+ if (!oembedUrl) {
+ Log.debug('filter_oembed/clientrender: No oembed URL provided');
+ $container.html('' + originalUrl + '');
+ return;
+ }
+
+ $.ajax({
+ url: oembedUrl,
+ dataType: 'json',
+ success: function(data) {
+ if (data && data.html) {
+ var embed = data.html;
+
+ if (params) {
+ var paramStr = '';
+ for (var key in params) {
+ if (params.hasOwnProperty(key)) {
+ paramStr += '&' + key + '=' + encodeURIComponent(params[key]);
+ }
+ }
+ embed = embed.replace('?feature=oembed', '?feature=oembed' + paramStr);
+ }
+
+ var aspectRatio = 0;
+ if (data.width && data.height) {
+ aspectRatio = data.height / data.width;
+ }
+
+ if (aspectRatio > 0) {
+ var padding = aspectRatio * 100;
+ var paddiv = '';
+ $container.html('
' + embed + paddiv + '
');
+ } else {
+ $container.html('
' + embed + '
');
+ }
+ } else {
+ Log.debug('filter_oembed/clientrender: No HTML in oembed response for ' + originalUrl);
+ $container.html('' + originalUrl + '');
+ }
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ Log.debug('filter_oembed/clientrender: Error loading oembed content for ' + originalUrl +
+ ' - ' + textStatus + ': ' + errorThrown);
+ $container.html('' + originalUrl + '');
+ }
+ });
+ });
+ };
+
+ return {
+ init: init
+ };
+});
diff --git a/amd/src/manageproviders.js b/amd/src/manageproviders.js
index 700cd00..f979975 100644
--- a/amd/src/manageproviders.js
+++ b/amd/src/manageproviders.js
@@ -206,7 +206,7 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates', 'core/frag
);
};
- // Listen for click cancel.
+ // Listen for click on edit button.
$('#providermanagement').on('click', '.oembed-provider-actions .filter-oembed-edit', function(e) {
e.preventDefault();
@@ -224,6 +224,24 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates', 'core/frag
updateProviderForm(pid);
});
+ // Listen for click on provider name.
+ $('#providermanagement').on('click', '.list-providername', function(e) {
+ e.preventDefault();
+
+ var row = $(this).parents('tr')[0];
+ var pid = $(row).data('pid');
+
+ // Remove editing class from current row / previous row and delete form.
+ if (self.prevEditId !== null) {
+ turnEditingOff(self.prevEditId);
+ turnEditingOff(pid);
+ }
+
+ self.prevEditId = pid;
+
+ updateProviderForm(pid);
+ });
+
// Listen for form click submit.
$('#providermanagement').on('click', '.oembed-provider-details form #id_submitbutton', function(e) {
e.preventDefault();
@@ -244,11 +262,13 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates', 'core/frag
turnEditingOff(pid);
// Get new provider id and set pid to it so correct row is targeted on reload.
- if (source.indexOf('download::') > -1) {
+ var isNewProvider = false;
+ if (source.indexOf('download::') > -1 || source.indexOf('local::new') > -1) {
var newProviderSel = detailsSel + ' .js-oembed-newprovider';
var newProviderEl = $(newProviderSel);
if (newProviderEl.length) {
pid = newProviderEl.data('newproviderid');
+ isNewProvider = true;
}
}
@@ -256,15 +276,17 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates', 'core/frag
* On reloading providers or single row append success HTML.
*/
var onReload = function() {
- var rowcell = $('#oembed-display-providers_' + pid + ' td');
- $(rowcell).append(successHTML);
- $(rowcell).find(' div.alert-success').attr('tabindex', -1);
- $(rowcell).find(' div.alert-success').focus();
+ // Remove any existing success messages.
+ $('#providermanagement .alert-success').remove();
+ // Add success message at the top of the provider management div.
+ $('#providermanagement').prepend(successHTML);
+ $('#providermanagement div.alert-success').attr('tabindex', -1);
+ $('#providermanagement div.alert-success').focus();
};
- if (source.indexOf('download::') > -1) {
- // When a downloaded provider is saved, a new one is created as a local provider, so we
- // need to reload the full list.
+ if (source.indexOf('download::') > -1 || isNewProvider) {
+ // When a downloaded provider is saved or a new provider is created,
+ // a new one is created as a local provider, so we need to reload the full list.
self.reloadProviders(onReload);
} else {
self.reloadRow(pid, row, 'reload', onReload);
@@ -281,6 +303,56 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates', 'core/frag
});
},
+ /**
+ * Listen for add new provider button.
+ */
+ listenAddProvider: function() {
+ var self = this;
+
+ $('#oembed-add-provider').on('click', function(e) {
+ e.preventDefault();
+
+ // Turn off any existing editing.
+ if (self.prevEditId !== null) {
+ var sel = '#oembed-display-providers_' + self.prevEditId;
+ $(sel).removeClass('oembed-provider-editing');
+ $(sel + ' form').remove();
+ $(sel + ' td div.alert').remove();
+ }
+
+ // Create a temporary row for the new provider form.
+ var newRowId = 'oembed-display-providers_0';
+ var existingNewRow = $('#' + newRowId);
+ if (existingNewRow.length) {
+ existingNewRow.remove();
+ }
+
+ var newRow = '