Commit AMD build artifacts required for Moodle Marketplace review.
Stop ignoring amd/build so generated modules ship with the plugin ZIP. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+418
@@ -0,0 +1,418 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @module mod_meetletacbfree/settings_access_reset
|
||||
* @copyright 2026 MeetLet <info@meetlet.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define(['core/ajax', 'mod_meetletacbfree/meetlet_modal_scroll'], function(Ajax, MeetletModalScroll) {
|
||||
'use strict';
|
||||
|
||||
var modalState = {
|
||||
open: false,
|
||||
resolve: null,
|
||||
lastFocus: null,
|
||||
onKey: null,
|
||||
mode: 'confirm',
|
||||
pendingSection: null,
|
||||
};
|
||||
|
||||
function modalScroll() {
|
||||
return MeetletModalScroll;
|
||||
}
|
||||
|
||||
function byId(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function parseStrings(root) {
|
||||
if (!root) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
return JSON.parse(root.getAttribute('data-strings') || '{}') || {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function sectionContainer(section) {
|
||||
if (section === 'rolemapping') {
|
||||
return document.querySelector('[data-meetlet-rolemapping]');
|
||||
}
|
||||
if (section === 'featureaccess') {
|
||||
return document.querySelector('[data-meetlet-access-matrix]');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyRoleMatrix(container, matrix) {
|
||||
if (!container || !matrix) {
|
||||
return;
|
||||
}
|
||||
Object.keys(matrix).forEach(function (rowkey) {
|
||||
var row = container.querySelector('[data-role-row="' + rowkey + '"]');
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
var val = matrix[rowkey];
|
||||
var radio = row.querySelector('input[type="radio"][value="' + val + '"]');
|
||||
if (radio) {
|
||||
radio.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyFeatureMatrix(container, matrix) {
|
||||
if (!container || !matrix) {
|
||||
return;
|
||||
}
|
||||
container.querySelectorAll('.meetlet-access-check').forEach(function (cb) {
|
||||
var role = cb.getAttribute('data-role');
|
||||
var feature = cb.getAttribute('data-feature');
|
||||
var on = matrix[role] && matrix[role][feature];
|
||||
cb.checked = !!on;
|
||||
});
|
||||
}
|
||||
|
||||
function renderModalMessage(bodyEl, mode, message) {
|
||||
if (!bodyEl) {
|
||||
return;
|
||||
}
|
||||
bodyEl.textContent = '';
|
||||
if (mode === 'busy') {
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'meetlet-modal__busy';
|
||||
var spinner = document.createElement('div');
|
||||
spinner.className = 'meetlet-modal__spinner';
|
||||
spinner.setAttribute('aria-hidden', 'true');
|
||||
var text = document.createElement('p');
|
||||
text.className = 'meetlet-modal__lead';
|
||||
text.textContent = message || '';
|
||||
wrap.appendChild(spinner);
|
||||
wrap.appendChild(text);
|
||||
bodyEl.appendChild(wrap);
|
||||
return;
|
||||
}
|
||||
var lead = document.createElement('p');
|
||||
lead.className = 'meetlet-modal__lead';
|
||||
lead.textContent = message || '';
|
||||
bodyEl.appendChild(lead);
|
||||
}
|
||||
|
||||
function closeModal(confirmed) {
|
||||
var modal = byId('meetlet-access-reset-modal');
|
||||
if (!modal || !modalState.open) {
|
||||
return;
|
||||
}
|
||||
modal.setAttribute('hidden', 'hidden');
|
||||
modal.style.display = 'none';
|
||||
modal.classList.remove('is-busy');
|
||||
modal.setAttribute('aria-busy', 'false');
|
||||
modalState.open = false;
|
||||
modalScroll().unlock();
|
||||
if (modalState.onKey) {
|
||||
document.removeEventListener('keydown', modalState.onKey, true);
|
||||
modalState.onKey = null;
|
||||
}
|
||||
var resolve = modalState.resolve;
|
||||
modalState.resolve = null;
|
||||
modalState.pendingSection = null;
|
||||
if (resolve) {
|
||||
resolve(!!confirmed);
|
||||
}
|
||||
if (modalState.lastFocus && typeof modalState.lastFocus.focus === 'function') {
|
||||
modalScroll().focusNoScroll(modalState.lastFocus);
|
||||
}
|
||||
modalState.lastFocus = null;
|
||||
}
|
||||
|
||||
function openModal(opts) {
|
||||
opts = opts || {};
|
||||
var mode = opts.mode === 'alert' ? 'alert' : (opts.mode === 'busy' ? 'busy' : 'confirm');
|
||||
var modal = byId('meetlet-access-reset-modal');
|
||||
if (!modal) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (modalState.open) {
|
||||
if (modalState.mode === 'busy' && mode === 'alert') {
|
||||
modalState.resolve = null;
|
||||
} else {
|
||||
closeModal(false);
|
||||
}
|
||||
}
|
||||
|
||||
var str = opts.strings || {};
|
||||
var titleEl = byId('meetlet-access-reset-modal-title');
|
||||
var bodyEl = byId('meetlet-access-reset-modal-body');
|
||||
var confirmBtn = byId('meetlet-access-reset-modal-confirm');
|
||||
var cancelBtn = modal.querySelector('[data-meetlet-modal-dismiss].btn');
|
||||
var closeBtn = modal.querySelector('.meetlet-modal__close');
|
||||
|
||||
if (titleEl) {
|
||||
titleEl.textContent = opts.title || '';
|
||||
}
|
||||
renderModalMessage(bodyEl, mode, opts.message || '');
|
||||
modal.classList.toggle('is-busy', mode === 'busy');
|
||||
modal.setAttribute('aria-busy', mode === 'busy' ? 'true' : 'false');
|
||||
|
||||
if (confirmBtn) {
|
||||
confirmBtn.textContent = opts.confirmLabel || (mode === 'alert' ? (str.modal_ok || 'OK') : (str.confirm_reset || 'Reset'));
|
||||
confirmBtn.className = opts.confirmClass || (mode === 'alert' ? 'btn btn-primary' : 'btn btn-outline-danger');
|
||||
confirmBtn.hidden = mode === 'busy';
|
||||
confirmBtn.disabled = mode === 'busy';
|
||||
confirmBtn.style.display = mode === 'busy' ? 'none' : '';
|
||||
}
|
||||
if (cancelBtn) {
|
||||
cancelBtn.textContent = str.modal_cancel || 'Cancel';
|
||||
cancelBtn.hidden = mode !== 'confirm';
|
||||
cancelBtn.style.display = mode !== 'confirm' ? 'none' : '';
|
||||
}
|
||||
if (closeBtn) {
|
||||
if (str.modal_close) {
|
||||
closeBtn.setAttribute('aria-label', str.modal_close);
|
||||
}
|
||||
closeBtn.hidden = mode === 'busy';
|
||||
closeBtn.style.display = mode === 'busy' ? 'none' : '';
|
||||
}
|
||||
|
||||
var dir = document.documentElement.getAttribute('dir') || 'ltr';
|
||||
modal.setAttribute('dir', dir);
|
||||
var dialog = modal.querySelector('.meetlet-modal__dialog');
|
||||
if (dialog) {
|
||||
dialog.setAttribute('dir', dir);
|
||||
}
|
||||
|
||||
modalState.mode = mode;
|
||||
if (!modalState.lastFocus) {
|
||||
modalState.lastFocus = document.activeElement;
|
||||
}
|
||||
modalScroll().lock();
|
||||
modal.removeAttribute('hidden');
|
||||
modal.style.display = 'flex';
|
||||
modalState.open = true;
|
||||
|
||||
if (modalState.onKey) {
|
||||
document.removeEventListener('keydown', modalState.onKey, true);
|
||||
}
|
||||
modalState.onKey = function (e) {
|
||||
if (!modalState.open) {
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
if (modalState.mode === 'busy') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeModal(false);
|
||||
return;
|
||||
}
|
||||
if (e.key !== 'Tab' || modalState.mode === 'busy') {
|
||||
return;
|
||||
}
|
||||
var focusable = modal.querySelectorAll(
|
||||
'button:not([disabled]):not([hidden]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
var visible = [];
|
||||
for (var i = 0; i < focusable.length; i++) {
|
||||
if (focusable[i].style.display === 'none') {
|
||||
continue;
|
||||
}
|
||||
visible.push(focusable[i]);
|
||||
}
|
||||
if (!visible.length) {
|
||||
return;
|
||||
}
|
||||
var first = visible[0];
|
||||
var last = visible[visible.length - 1];
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
modalScroll().focusNoScroll(last);
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
modalScroll().focusNoScroll(first);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', modalState.onKey, true);
|
||||
|
||||
if (mode !== 'busy' && confirmBtn && !confirmBtn.hidden) {
|
||||
window.setTimeout(function () {
|
||||
modalScroll().focusNoScroll(confirmBtn);
|
||||
}, 20);
|
||||
}
|
||||
|
||||
if (mode === 'busy') {
|
||||
modalState.resolve = null;
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
modalState.resolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
function bindModal() {
|
||||
var modal = byId('meetlet-access-reset-modal');
|
||||
if (!modal || modal.dataset.bound === '1') {
|
||||
return;
|
||||
}
|
||||
modal.dataset.bound = '1';
|
||||
modal.addEventListener('click', function (e) {
|
||||
if (modalState.mode === 'busy') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.target.closest('[data-meetlet-modal-dismiss]')) {
|
||||
e.preventDefault();
|
||||
closeModal(false);
|
||||
}
|
||||
});
|
||||
var confirmBtn = byId('meetlet-access-reset-modal-confirm');
|
||||
if (confirmBtn) {
|
||||
confirmBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
if (modalState.mode === 'busy') {
|
||||
return;
|
||||
}
|
||||
closeModal(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function postReset(root, section, str) {
|
||||
return Ajax.call([{
|
||||
methodname: 'mod_meetletacbfree_reset_access_settings',
|
||||
args: {section: section}
|
||||
}])[0].then(function(data) {
|
||||
if (data && data.matrixjson) {
|
||||
try {
|
||||
data.matrix = JSON.parse(data.matrixjson);
|
||||
} catch (e) {
|
||||
data.matrix = null;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmCopy(section, str) {
|
||||
if (section === 'rolemapping') {
|
||||
return {
|
||||
title: str.confirm_rolemapping_title || '',
|
||||
message: str.confirm_rolemapping || '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: str.confirm_featureaccess_title || '',
|
||||
message: str.confirm_featureaccess || '',
|
||||
};
|
||||
}
|
||||
|
||||
function successCopy(section, str, serverMessage) {
|
||||
if (serverMessage) {
|
||||
return serverMessage;
|
||||
}
|
||||
if (section === 'rolemapping') {
|
||||
return str.success_rolemapping || '';
|
||||
}
|
||||
return str.success_featureaccess || '';
|
||||
}
|
||||
|
||||
function handleResetClick(btn, root, str) {
|
||||
var section = btn.getAttribute('data-meetlet-reset-section') || '';
|
||||
if (!section) {
|
||||
return;
|
||||
}
|
||||
var copy = confirmCopy(section, str);
|
||||
openModal({
|
||||
mode: 'confirm',
|
||||
strings: str,
|
||||
title: copy.title,
|
||||
message: copy.message,
|
||||
confirmClass: 'btn btn-outline-danger',
|
||||
}).then(function (confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
openModal({
|
||||
mode: 'busy',
|
||||
strings: str,
|
||||
title: str.busy || '',
|
||||
message: str.busy || '',
|
||||
});
|
||||
btn.disabled = true;
|
||||
postReset(root, section, str).then(function (data) {
|
||||
btn.disabled = false;
|
||||
if (!data || !data.success) {
|
||||
openModal({
|
||||
mode: 'alert',
|
||||
strings: str,
|
||||
title: str.error || 'Error',
|
||||
message: (data && data.error) || str.error || 'Error',
|
||||
confirmClass: 'btn btn-primary',
|
||||
});
|
||||
return;
|
||||
}
|
||||
var container = sectionContainer(section);
|
||||
if (section === 'rolemapping') {
|
||||
applyRoleMatrix(container, data.matrix);
|
||||
} else {
|
||||
applyFeatureMatrix(container, data.matrix);
|
||||
}
|
||||
openModal({
|
||||
mode: 'alert',
|
||||
strings: str,
|
||||
title: copy.title,
|
||||
message: successCopy(section, str, data.message),
|
||||
confirmClass: 'btn btn-primary',
|
||||
});
|
||||
}).catch(function () {
|
||||
btn.disabled = false;
|
||||
openModal({
|
||||
mode: 'alert',
|
||||
strings: str,
|
||||
title: str.error || 'Error',
|
||||
message: str.error || 'Error',
|
||||
confirmClass: 'btn btn-primary',
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
var root = byId('meetlet-access-reset-root');
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
var str = parseStrings(root);
|
||||
bindModal();
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-meetlet-reset-section]');
|
||||
if (!btn || btn.disabled) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
handleResetClick(btn, root, str);
|
||||
});
|
||||
}
|
||||
|
||||
return {init: init};
|
||||
});
|
||||
Reference in New Issue
Block a user