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:
2026-09-20 12:42:19 +03:30
parent be252e49e2
commit 988aafce39
9 changed files with 2985 additions and 1 deletions
-1
View File
@@ -10,7 +10,6 @@ Desktop.ini
.phpunit.result.cache
.phpunit.cache/
/coverage/
/amd/build/
grunt/
npm-debug.log*
+67
View File
@@ -0,0 +1,67 @@
// 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/featureaccess_matrix
* @copyright 2026 MeetLet <info@meetlet.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
const bindMatrix = (matrix) => {
if (!matrix || matrix.getAttribute('data-access-bound') === '1') {
return;
}
matrix.setAttribute('data-access-bound', '1');
const setGroup = (checks, forceOn) => {
const list = Array.from(checks).filter((el) => !el.disabled);
if (!list.length) {
return;
}
const turnOn = (typeof forceOn === 'boolean')
? forceOn
: list.some((el) => !el.checked);
list.forEach((el) => {
if (el.checked !== turnOn) {
el.checked = turnOn;
el.dispatchEvent(new Event('change', {bubbles: true}));
}
});
};
matrix.addEventListener('click', (e) => {
const btn = e.target.closest('[data-access-toggle]');
if (!btn || !matrix.contains(btn) || matrix.classList.contains('is-disabled')) {
return;
}
e.preventDefault();
const mode = btn.getAttribute('data-access-toggle');
if (mode === 'row') {
const role = btn.getAttribute('data-role');
setGroup(matrix.querySelectorAll('.meetlet-access-check[data-role="' + role + '"]'));
} else if (mode === 'col') {
const feature = btn.getAttribute('data-feature');
setGroup(matrix.querySelectorAll('.meetlet-access-check[data-feature="' + feature + '"]'));
}
});
};
const init = () => {
document.querySelectorAll('[data-meetlet-access-matrix]').forEach(bindMatrix);
};
return {init: init};
});
+124
View File
@@ -0,0 +1,124 @@
// 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/meetlet_modal_scroll
* @copyright 2026 MeetLet <info@meetlet.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
'use strict';
var scrollLockY = 0;
var guardsActive = false;
function scrollbarWidth() {
return Math.max(0, window.innerWidth - document.documentElement.clientWidth);
}
function focusNoScroll(el) {
if (!el || typeof el.focus !== 'function') {
return;
}
try {
el.focus({preventScroll: true});
} catch (e) {
el.focus();
}
}
function restoreScroll() {
if (scrollLockY > 0) {
window.scrollTo(0, scrollLockY);
}
}
function onScrollGuard() {
if (scrollLockY && window.scrollY !== scrollLockY) {
window.scrollTo(0, scrollLockY);
}
}
function onMoveGuard(e) {
if (e.target.closest && e.target.closest('.meetlet-modal__dialog')) {
return;
}
e.preventDefault();
}
function bindGuards() {
if (guardsActive) {
return;
}
guardsActive = true;
window.addEventListener('scroll', onScrollGuard, true);
window.addEventListener('wheel', onMoveGuard, {passive: false, capture: true});
window.addEventListener('touchmove', onMoveGuard, {passive: false, capture: true});
}
function unbindGuards() {
if (!guardsActive) {
return;
}
guardsActive = false;
window.removeEventListener('scroll', onScrollGuard, true);
window.removeEventListener('wheel', onMoveGuard, true);
window.removeEventListener('touchmove', onMoveGuard, true);
}
function lockPageScroll() {
if (document.documentElement.classList.contains('meetlet-modal-open')) {
return;
}
scrollLockY = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
var pad = scrollbarWidth();
if (pad > 0) {
document.body.style.paddingRight = pad + 'px';
}
document.documentElement.classList.add('meetlet-modal-open');
restoreScroll();
if (typeof window.requestAnimationFrame === 'function') {
window.requestAnimationFrame(restoreScroll);
}
bindGuards();
}
function unlockPageScroll() {
if (!document.documentElement.classList.contains('meetlet-modal-open')) {
return;
}
unbindGuards();
var y = scrollLockY;
document.documentElement.classList.remove('meetlet-modal-open');
document.body.style.paddingRight = '';
scrollLockY = 0;
if (y) {
window.scrollTo(0, y);
if (typeof window.requestAnimationFrame === 'function') {
window.requestAnimationFrame(function() {
window.scrollTo(0, y);
});
}
}
}
return {
init: function() {},
lock: lockPageScroll,
unlock: unlockPageScroll,
focusNoScroll: focusNoScroll
};
});
+504
View File
@@ -0,0 +1,504 @@
// 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/modform
* @copyright 2026 MeetLet <info@meetlet.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/str', 'core/notification', 'core/templates', 'core/ajax'], function(Str, Notification, Templates, Ajax) {
function init() {
const ELEMENT_SELECTOR = {
participantData: () => document.querySelector('[data-participant-data]'),
participantList: () => document.getElementsByName('participants')[0],
participantTable: () => document.getElementById('participant_list_table'),
participantSelectionType: () => document.getElementsByName('meetlet_participant_selection_type')[0],
participantSelection: () => document.getElementsByName('meetlet_participant_selection')[0],
participantAddButton: () => document.getElementsByName('meetlet_participant_selection_add')[0],
};
const getParticipantList = () => {
const value = ELEMENT_SELECTOR.participantList().value;
return value ? JSON.parse(value) : [];
};
const participantListUpdate = (list) => {
ELEMENT_SELECTOR.participantList().value = JSON.stringify(list);
};
const participantRemove = (selectionTypeValue, selectionValue) => {
const pList = getParticipantList();
const id = 'participant_list_tr_' + selectionTypeValue + '-' + selectionValue;
const table = ELEMENT_SELECTOR.participantTable();
for (let i = 0; i < pList.length; i++) {
if (pList[i].selectiontype === selectionTypeValue &&
String(pList[i].selectionid) === String(selectionValue)) {
pList.splice(i, 1);
break;
}
}
for (let i = 0; i < table.rows.length; i++) {
if (table.rows[i].id === id) {
table.deleteRow(i);
break;
}
}
participantListUpdate(pList);
};
const participantListRoleUpdate = (type, id) => {
const select = document.querySelector('#participant_list_tr_' + type + '-' + id + ' .participant-select');
const pList = getParticipantList();
for (let i = 0; i < pList.length; i++) {
if (pList[i].selectiontype === type && String(pList[i].selectionid) === String(id)) {
pList[i].role = select.value;
}
}
participantListUpdate(pList);
};
const participantAddToForm = async(selectionTypeValue, selectionValue, selectedRole, canRemove) => {
const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);
const [shost, spresenter, sparticipant] = await Promise.all([
Str.get_string('role_host', 'meetletacbfree'),
Str.get_string('role_presenter', 'meetletacbfree'),
Str.get_string('role_participant', 'meetletacbfree'),
]);
const roles = {
host: {id: 'host', label: shost},
presenter: {id: 'presenter', label: spresenter},
participant: {id: 'participant', label: sparticipant},
};
if (roles[selectedRole]) {
roles[selectedRole].isselected = true;
} else {
roles.participant.isselected = true;
}
try {
const listTable = document.querySelector('#participant_list_table tbody');
const templateContext = {
selectiontypevalue: selectionTypeValue,
selectionvalue: selectionValue,
participanttype: participantData[selectionTypeValue].name,
participantvalue: (selectionTypeValue !== 'all')
? participantData[selectionTypeValue].children[selectionValue].name : null,
roles: Object.values(roles),
canRemove: canRemove,
};
const {html, js} = await Templates.renderForPromise('mod_meetletacbfree/participant_form_add', templateContext);
const newNode = Templates.appendNodeContents(listTable, html, js)[0];
newNode.querySelector('.participant-select').addEventListener('change', () => {
participantListRoleUpdate(selectionTypeValue, selectionValue);
});
const removeNode = newNode.querySelector('.remove-button');
if (removeNode) {
removeNode.addEventListener('click', (e) => {
e.preventDefault();
participantRemove(selectionTypeValue, selectionValue);
});
}
} catch (e) {
Notification.exception(e);
}
};
const participantAddFromCurrentSelection = () => {
const selectionType = ELEMENT_SELECTOR.participantSelectionType();
const selection = ELEMENT_SELECTOR.participantSelection();
const pList = getParticipantList();
for (let i = 0; i < pList.length; i++) {
if (pList[i].selectiontype === selectionType.value &&
String(pList[i].selectionid) === String(selection.value)) {
return;
}
}
const role = 'participant';
pList.push({
selectiontype: selectionType.value,
selectionid: selection.value,
role: role,
});
participantAddToForm(selectionType.value, selection.value, role, true);
participantListUpdate(pList);
};
const updateSelectionFromType = (currentTypeSelect) => {
const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);
const participantSelect = ELEMENT_SELECTOR.participantSelection();
while (participantSelect.firstChild) {
participantSelect.removeChild(participantSelect.firstChild);
}
if (currentTypeSelect.value === 'all') {
const option = document.createElement('option');
option.text = '---------------';
option.value = 'all';
participantSelect.add(option);
return;
}
const options = Object.values(participantData[currentTypeSelect.value].children || {});
options.forEach((opt) => {
const option = document.createElement('option');
option.text = opt.name;
option.value = opt.id;
participantSelect.add(option);
});
};
const participantListInit = () => {
const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);
getParticipantList().forEach((participant) => {
if (participant.selectiontype === 'all' ||
(participantData[participant.selectiontype] &&
typeof participantData[participant.selectiontype].children[participant.selectionid] !== 'undefined')) {
participantAddToForm(participant.selectiontype, participant.selectionid, participant.role, true);
}
});
};
const syncAccessMatrix = () => {
// Moodle advcheckbox renders a hidden input with the same name first — use the real checkbox.
const override = document.querySelector('#id_featureaccess_override') ||
document.querySelector('input[type="checkbox"][name="featureaccess_override"]');
const matrix = document.querySelector('.meetlet-access-matrix');
if (!override || !matrix) {
return;
}
const enabled = !!override.checked;
matrix.classList.toggle('is-disabled', !enabled);
matrix.querySelectorAll('input.meetlet-access-check').forEach((el) => {
el.disabled = !enabled;
});
matrix.querySelectorAll('.meetlet-access-toggle').forEach((el) => {
el.disabled = !enabled;
});
};
const accessOverride = document.querySelector('#id_featureaccess_override') ||
document.querySelector('input[type="checkbox"][name="featureaccess_override"]');
if (accessOverride) {
accessOverride.addEventListener('change', syncAccessMatrix);
accessOverride.addEventListener('click', syncAccessMatrix);
syncAccessMatrix();
}
const setDatetimeFields = (basename, unix) => {
if (!unix) {
return;
}
const d = new Date(unix * 1000);
const set = (suffix, value) => {
const el = document.querySelector('[name="' + basename + '[' + suffix + ']"]');
if (el) {
el.value = String(value);
el.dispatchEvent(new Event('change', {bubbles: true}));
}
};
set('year', d.getFullYear());
set('month', d.getMonth() + 1);
set('day', d.getDate());
set('hour', d.getHours());
set('minute', d.getMinutes());
};
const applyExistingMeeting = () => {
const picker = document.querySelector('[data-meeting-picker]');
const metaEl = document.querySelector('[data-meetings-meta]');
const scoInput = document.querySelector('#id_existingscoid') || document.querySelector('input[name="existingscoid"]');
const linkmode = document.querySelector('#id_linkmode') || document.querySelector('[name="linkmode"]');
const nameInput = document.querySelector('#id_name') || document.querySelector('[name="name"]');
if (!picker || !scoInput || !linkmode) {
return;
}
const toggle = picker.querySelector('[data-meeting-toggle]');
const panel = picker.querySelector('[data-meeting-panel]');
const searchInput = picker.querySelector('[data-meeting-search]');
const listEl = picker.querySelector('[data-meeting-list]');
const hint = picker.querySelector('[data-meeting-hint]');
const labelEl = picker.querySelector('[data-meeting-label]');
const readonly = picker.getAttribute('data-readonly') === '1';
const strChoose = picker.getAttribute('data-str-choose') || '---';
const strLoading = picker.getAttribute('data-str-loading') || '...';
const strEmpty = picker.getAttribute('data-str-empty') || '';
const courseid = parseInt(picker.getAttribute('data-courseid') || '0', 10);
let meta = {};
try {
meta = JSON.parse((metaEl && metaEl.getAttribute('data-meetings-meta')) || '{}');
} catch (e) {
meta = {};
}
let loadedOnce = false;
let searchTimer = null;
let requestSeq = 0;
const setOpen = (open) => {
if (!panel || !toggle || readonly) {
return;
}
if (open) {
panel.removeAttribute('hidden');
toggle.setAttribute('aria-expanded', 'true');
picker.classList.add('is-open');
if (searchInput) {
searchInput.focus();
}
} else {
panel.setAttribute('hidden', 'hidden');
toggle.setAttribute('aria-expanded', 'false');
picker.classList.remove('is-open');
}
};
const applySelected = (scoid) => {
const info = meta[String(scoid)];
if (!info || !nameInput || String(linkmode.value) !== '1') {
return;
}
if (info.name) {
nameInput.value = info.name;
nameInput.dispatchEvent(new Event('input', {bubbles: true}));
nameInput.dispatchEvent(new Event('change', {bubbles: true}));
}
if (info.starttime) {
setDatetimeFields('starttime', info.starttime);
}
if (info.endtime) {
const endEnable = document.querySelector('[name="endtimeenable"]');
if (endEnable && !endEnable.checked) {
endEnable.checked = true;
endEnable.dispatchEvent(new Event('change', {bubbles: true}));
}
setDatetimeFields('endtime', info.endtime);
}
};
const selectMeeting = (m) => {
const scoid = String(m.scoid || '0');
scoInput.value = scoid;
meta[scoid] = {
name: m.name || '',
urlpath: m.urlpath || '',
starttime: m.starttime || 0,
endtime: m.endtime || 0,
};
if (metaEl) {
metaEl.setAttribute('data-meetings-meta', JSON.stringify(meta));
}
if (labelEl) {
labelEl.textContent = m.label || m.name || strChoose;
}
if (listEl) {
listEl.querySelectorAll('.meetlet-combobox__option').forEach((btn) => {
btn.classList.toggle('is-selected', btn.getAttribute('data-scoid') === scoid);
});
}
setOpen(false);
applySelected(scoid);
};
const renderList = (meetings) => {
if (!listEl) {
return;
}
const selected = String(scoInput.value || '0');
listEl.innerHTML = '';
const rows = meetings || [];
if (!rows.length) {
return;
}
rows.forEach((m) => {
const scoid = String(m.scoid);
meta[scoid] = {
name: m.name || '',
urlpath: m.urlpath || '',
starttime: m.starttime || 0,
endtime: m.endtime || 0,
};
const li = document.createElement('li');
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'meetlet-combobox__option' + (scoid === selected ? ' is-selected' : '');
btn.setAttribute('data-scoid', scoid);
btn.textContent = m.label || m.name || scoid;
btn.addEventListener('click', (e) => {
e.preventDefault();
selectMeeting(m);
});
li.appendChild(btn);
listEl.appendChild(li);
});
if (metaEl) {
metaEl.setAttribute('data-meetings-meta', JSON.stringify(meta));
}
};
const fetchMeetings = (query) => {
if (String(linkmode.value) !== '1' || readonly) {
return Promise.resolve();
}
const seq = ++requestSeq;
if (hint) {
hint.textContent = strLoading;
}
return Ajax.call([{
methodname: 'mod_meetletacbfree_search_server_meetings',
args: {
query: query || '',
limit: 10,
courseid: courseid,
},
}])[0].then((res) => {
if (seq !== requestSeq) {
return null;
}
const meetings = (res && res.meetings) ? res.meetings : [];
renderList(meetings);
if (hint) {
hint.textContent = meetings.length ? '' : strEmpty;
}
loadedOnce = true;
return res;
}).catch((err) => {
if (hint) {
hint.textContent = (err && err.message) ? err.message : strEmpty;
}
Notification.exception(err);
});
};
if (toggle) {
toggle.addEventListener('click', (e) => {
e.preventDefault();
if (readonly || String(linkmode.value) !== '1') {
return;
}
const willOpen = panel.hasAttribute('hidden');
setOpen(willOpen);
if (willOpen) {
fetchMeetings(searchInput ? String(searchInput.value || '').trim() : '');
}
});
}
if (searchInput) {
searchInput.addEventListener('input', () => {
window.clearTimeout(searchTimer);
searchTimer = window.setTimeout(() => {
fetchMeetings(String(searchInput.value || '').trim());
}, 350);
});
}
document.addEventListener('click', (e) => {
if (!picker.contains(e.target)) {
setOpen(false);
}
});
linkmode.addEventListener('change', () => {
if (String(linkmode.value) === '1') {
loadedOnce = false;
if (!readonly) {
setOpen(true);
fetchMeetings('');
}
} else {
setOpen(false);
scoInput.value = '0';
if (labelEl) {
labelEl.textContent = strChoose;
}
}
});
if (String(linkmode.value) === '1' && !readonly) {
// Prefetch quietly so first open is fast; do not force panel open on edit.
fetchMeetings('');
}
};
applyExistingMeeting();
const syncSettingOverrideInputs = () => {
document.querySelectorAll('[data-meetlet-setting-radio]').forEach((input) => {
if (input.getAttribute('data-meetlet-setting-bound') === '1') {
return;
}
input.setAttribute('data-meetlet-setting-bound', '1');
input.addEventListener('change', () => {
const baseName = input.name.replace(/_state$/, '');
const hidden = document.querySelector('input[type="hidden"][name="' + baseName + '"]');
if (hidden && input.checked) {
if (baseName === 'participantsmode') {
hidden.value = input.value;
} else {
hidden.value = input.value === '2' ? '1' : '0';
}
}
syncParticipantOverride();
});
});
};
const participantModeInput = () => document.querySelector('input[name="participantsmode_state"]:checked');
const syncParticipantOverride = () => {
const mode = participantModeInput();
const enabled = !mode || mode.value === '1';
const container = document.querySelector('#fitem_id_static_participant_list');
if (container) {
container.classList.toggle('is-disabled', !enabled);
}
const participantForm = document.querySelector('.meetlet-participant-form') || container;
if (participantForm) {
participantForm.querySelectorAll('select, button, input.participant-select').forEach((el) => {
el.disabled = !enabled;
});
}
const table = ELEMENT_SELECTOR.participantTable();
if (table) {
table.querySelectorAll('select, button').forEach((el) => {
el.disabled = !enabled;
});
}
};
syncSettingOverrideInputs();
syncParticipantOverride();
const typeSelect = ELEMENT_SELECTOR.participantSelectionType();
const addButton = ELEMENT_SELECTOR.participantAddButton();
if (!typeSelect || !addButton || !ELEMENT_SELECTOR.participantData()) {
return;
}
typeSelect.addEventListener('change', (e) => updateSelectionFromType(e.target));
addButton.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
participantAddFromCurrentSelection();
});
document.querySelectorAll('input[name="participantsmode_state"]').forEach((input) => {
input.addEventListener('change', syncParticipantOverride);
});
updateSelectionFromType(typeSelect);
participantListInit();
}
return {
init: init
};
});
+418
View File
@@ -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};
});
+324
View File
@@ -0,0 +1,324 @@
// 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_conntest
* @copyright 2026 MeetLet <info@meetlet.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax'], function(Ajax) {
'use strict';
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function el(tag, cls) {
var node = document.createElement(tag);
if (cls) {
node.className = cls;
}
return node;
}
function text(tag, cls, value) {
var node = el(tag, cls);
node.textContent = value || '';
return node;
}
function parseLabels(raw) {
try {
return JSON.parse(raw || '{}') || {};
} catch (e) {
return {};
}
}
function setProgress(root, current, total, strings) {
var fill = root.querySelector('[data-meetlet-progress-fill]');
var label = root.querySelector('[data-meetlet-progress-label]');
var pct = total ? Math.round((current / total) * 100) : 0;
if (fill) {
fill.style.width = pct + '%';
}
if (label) {
label.textContent = strings.progress
.replace('{$a->current}', String(current))
.replace('{$a->total}', String(total))
.replace('{$a->percent}', String(pct));
}
}
function buildCard(index, total, step, label, strings) {
var wrap = el('div', 'meetlet-conntest__card meetlet-conntest__card--pending');
wrap.setAttribute('data-step', step);
wrap.setAttribute('data-index', String(index));
var top = el('div', 'meetlet-conntest__rowtop');
top.appendChild(text('span', 'meetlet-conntest__num', String(index + 1)));
top.appendChild(el('span', 'meetlet-dot meetlet-dot--pending'));
top.appendChild(text('span', 'meetlet-conntest__label', label));
top.appendChild(text('span', 'meetlet-pill meetlet-pill--pending', strings.waiting));
wrap.appendChild(top);
wrap.appendChild(text('div', 'meetlet-conntest__detail meetlet-conntest__card-detail', ''));
return wrap;
}
function setCardState(card, state, strings, detail) {
card.className = 'meetlet-conntest__card meetlet-conntest__card--' + state;
var dot = card.querySelector('.meetlet-dot');
var pill = card.querySelector('.meetlet-pill');
var detailNode = card.querySelector('.meetlet-conntest__card-detail');
if (dot) {
dot.className = 'meetlet-dot meetlet-dot--' + state;
}
if (pill) {
pill.className = 'meetlet-pill meetlet-pill--' + state;
if (state === 'pending') {
pill.textContent = strings.waiting;
} else if (state === 'running') {
pill.textContent = strings.runningstep;
} else if (state === 'ok') {
pill.textContent = strings.ok;
} else if (state === 'fail') {
pill.textContent = strings.fail;
} else if (state === 'skipped') {
pill.textContent = strings.skipped;
}
}
if (detailNode) {
detailNode.textContent = detail || '';
detailNode.style.display = detail ? '' : 'none';
}
}
function setSubResults(card, subresults, strings) {
var wrap = card.querySelector('[data-meetlet-subresults]');
if (!wrap) {
wrap = el('div', 'meetlet-conntest__subs');
wrap.setAttribute('data-meetlet-subresults', '1');
card.appendChild(wrap);
}
wrap.innerHTML = '';
if (!subresults || !subresults.length) {
wrap.style.display = 'none';
return;
}
wrap.style.display = '';
subresults.forEach(function (sub) {
var substate = 'fail';
var pillText = strings.fail;
if (sub.skipped) {
substate = 'skipped';
pillText = strings.skipped;
} else if (sub.ok) {
substate = 'ok';
pillText = strings.ok;
}
var subcard = el('div', 'meetlet-conntest__sub meetlet-conntest__sub--' + substate);
var top = el('div', 'meetlet-conntest__rowtop');
top.appendChild(el('span', 'meetlet-dot meetlet-dot--' + substate));
top.appendChild(text('span', 'meetlet-conntest__sublabel', sub.label || sub.id || ''));
top.appendChild(text('span', 'meetlet-pill meetlet-pill--' + substate, pillText));
subcard.appendChild(top);
if (sub.detail) {
subcard.appendChild(text('div', 'meetlet-conntest__detail', sub.detail));
}
wrap.appendChild(subcard);
});
}
function sleep(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function init() {
var SAVE_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">'
+ '<path fill="currentColor" d="M17 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7l-4-4zm-5 16a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm3-10H5V5h10v4z"/>'
+ '</svg>';
document.querySelectorAll('#adminsettings .offset-sm-3').forEach(function (el) {
if (!el.querySelector('button.btn-primary, input.btn-primary[type="submit"]')) {
return;
}
el.classList.remove('offset-sm-3', 'col-sm-3', 'col-md-3');
el.classList.add('col-12', 'meetlet-savebar');
});
document.querySelectorAll(
'#adminsettings button.btn-primary[type="submit"],'
+ ' #adminsettings input.btn-primary[type="submit"]'
).forEach(function (btn) {
if (btn.getAttribute('data-meetlet-save-icon') === '1') {
return;
}
btn.setAttribute('data-meetlet-save-icon', '1');
btn.classList.add('meetlet-btn-with-icon');
if (btn.tagName === 'INPUT') {
var replacement = document.createElement('button');
replacement.type = 'submit';
replacement.name = btn.name || 'submitbutton';
replacement.value = btn.value || '';
replacement.id = btn.id || '';
replacement.className = btn.className;
replacement.setAttribute('data-meetlet-save-icon', '1');
replacement.innerHTML = SAVE_ICON
+ '<span class="meetlet-btn-with-icon__label">'
+ (btn.value || btn.getAttribute('value') || 'Save changes')
+ '</span>';
btn.parentNode.replaceChild(replacement, btn);
return;
}
if (btn.querySelector('svg')) {
return;
}
var label = (btn.textContent || '').trim() || 'Save changes';
btn.textContent = '';
btn.insertAdjacentHTML('afterbegin', SAVE_ICON);
var span = document.createElement('span');
span.className = 'meetlet-btn-with-icon__label';
span.textContent = label;
btn.appendChild(span);
});
var root = document.getElementById('meetlet-acb-conntest');
if (!root) {
return;
}
var btn = root.querySelector('[data-meetlet-run-test]');
var grid = root.querySelector('[data-meetlet-test-grid]');
var status = root.querySelector('[data-meetlet-test-status]');
var progressWrap = root.querySelector('[data-meetlet-progress]');
if (!btn || !grid) {
return;
}
var steps = (root.getAttribute('data-steps') || 'login,folders,user,sso,cleanup')
.split(',')
.map(function (s) { return s.trim(); })
.filter(Boolean);
var labels = parseLabels(root.getAttribute('data-step-labels'));
var strings = {
ok: root.getAttribute('data-str-ok') || 'OK',
fail: root.getAttribute('data-str-fail') || 'Fail',
running: root.getAttribute('data-str-running') || 'Running…',
idle: root.getAttribute('data-str-idle') || '',
waiting: root.getAttribute('data-str-waiting') || 'Waiting',
runningstep: root.getAttribute('data-str-runningstep') || 'Testing',
skipped: root.getAttribute('data-str-skipped') || 'Skipped',
progress: root.getAttribute('data-str-progress') || 'Step {$a->current} of {$a->total} ({$a->percent}%)'
};
btn.addEventListener('click', function () {
if (btn.disabled) {
return;
}
btn.disabled = true;
grid.innerHTML = '';
if (progressWrap) {
progressWrap.hidden = false;
}
if (status) {
status.textContent = strings.running;
}
var cards = {};
steps.forEach(function (step, index) {
var label = labels[step] || step;
var card = buildCard(index, steps.length, step, label, strings);
setCardState(card, 'pending', strings, '');
grid.appendChild(card);
cards[step] = card;
});
setProgress(root, 0, steps.length, strings);
(async function () {
var skipToCleanup = false;
var completed = 0;
for (var i = 0; i < steps.length; i++) {
var step = steps[i];
var card = cards[step];
if (skipToCleanup && step !== 'cleanup') {
setCardState(card, 'skipped', strings, '');
completed++;
setProgress(root, completed, steps.length, strings);
continue;
}
setCardState(card, 'running', strings, '');
setProgress(root, completed, steps.length, strings);
var result = await new Promise(function(resolve, reject) {
Ajax.call([{
methodname: 'mod_meetletacbfree_run_conntest_step',
args: {step: step}
}])[0].then(resolve).catch(reject);
});
setCardState(
card,
result.ok ? 'ok' : 'fail',
strings,
result.detail || ''
);
if (result.subresults && result.subresults.length) {
setSubResults(card, result.subresults, strings);
}
completed++;
setProgress(root, completed, steps.length, strings);
await sleep(220);
if (result.abort || (result.done && step !== 'cleanup')) {
skipToCleanup = true;
if (result.done && step !== 'cleanup') {
// Mark remaining non-cleanup as skipped, then stop if no cleanup needed.
for (var j = i + 1; j < steps.length; j++) {
if (steps[j] === 'cleanup') {
continue;
}
setCardState(cards[steps[j]], 'skipped', strings, '');
completed++;
}
// Still run cleanup if present later in loop via skipToCleanup.
}
if (step === 'cleanup') {
break;
}
}
}
setProgress(root, steps.length, steps.length, strings);
})().catch(function (err) {
var firstPending = grid.querySelector('.meetlet-conntest__card--pending, .meetlet-conntest__card--running');
if (firstPending) {
setCardState(firstPending, 'fail', strings, String(err && err.message ? err.message : err));
}
}).finally(function () {
btn.disabled = false;
if (status) {
status.textContent = strings.idle;
}
});
});
}
return {init: init};
});
+336
View File
@@ -0,0 +1,336 @@
// 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_registration
* @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';
function byId(id) {
return document.getElementById(id);
}
function setFlash(el, ok, message) {
if (!el) {
return;
}
el.className = 'alert mt-3 ' + (ok ? 'alert-success' : 'alert-danger');
el.textContent = message || '';
el.classList.toggle('d-none', !message);
}
function setActiveUi(active) {
var inactive = byId('meetlet-registration-actions-inactive');
var activeEl = byId('meetlet-registration-actions-active');
var product = byId('meetlet-registration-product');
if (inactive) {
inactive.classList.toggle('d-none', !!active);
}
if (activeEl) {
activeEl.classList.toggle('d-none', !active);
}
if (product) {
product.classList.toggle('d-none', !active);
}
}
function renderSummary(summary) {
if (!summary) {
return;
}
var badge = byId('meetlet-registration-status-badge');
var cfg = byId('meetlet-registration-config');
if (badge) {
badge.className = 'badge meetlet-license-badge '
+ (summary.active ? 'meetlet-license-badge--active' : 'meetlet-license-badge--inactive');
badge.textContent = summary.status_label
|| (summary.active
? (cfg && cfg.dataset.labelActive) || 'Active'
: (cfg && cfg.dataset.labelInactive) || 'Inactive');
}
var domain = byId('meetlet-registration-domain');
if (domain && cfg && cfg.dataset.labelDomain) {
domain.textContent = cfg.dataset.labelDomain.replace('__V__', summary.domain || '—');
}
var installation = byId('meetlet-registration-installation');
if (installation && cfg && cfg.dataset.labelInstallation) {
installation.textContent = cfg.dataset.labelInstallation.replace('__V__', summary.installation_id || '—');
}
var product = byId('meetlet-registration-product');
if (product) {
product.textContent = summary.product_label || '';
}
var err = byId('meetlet-registration-error');
if (err) {
if (summary.error) {
err.textContent = summary.error;
err.classList.remove('d-none');
} else {
err.textContent = '';
err.classList.add('d-none');
}
}
setActiveUi(!!summary.active);
}
function currentApiUrl(cfg) {
var input = cfg && cfg.dataset.apiInput ? document.getElementById(cfg.dataset.apiInput) : null;
if (input && input.value) {
return input.value.trim();
}
return '';
}
var modalState = {
open: false,
resolve: null,
lastFocus: null,
onKey: null,
};
function ensureModalOnBody(modal) {
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
return modal;
}
function modalScroll() {
return MeetletModalScroll;
}
function closeRegistrationModal(result) {
var modal = byId('meetlet-registration-modal');
if (!modal) {
return;
}
modal.setAttribute('hidden', 'hidden');
modal.style.display = 'none';
modalScroll().unlock();
if (modalState.onKey) {
document.removeEventListener('keydown', modalState.onKey, true);
modalState.onKey = null;
}
modalState.open = false;
var resolve = modalState.resolve;
modalState.resolve = null;
if (modalState.lastFocus) {
modalScroll().focusNoScroll(modalState.lastFocus);
}
modalState.lastFocus = null;
if (typeof resolve === 'function') {
resolve(!!result);
}
}
function openRegistrationModal(opts) {
var modal = ensureModalOnBody(byId('meetlet-registration-modal'));
var cfg = byId('meetlet-registration-config');
if (!modal) {
return Promise.resolve(false);
}
if (modalState.open) {
closeRegistrationModal(false);
}
var title = byId('meetlet-registration-modal-title');
var body = byId('meetlet-registration-modal-body');
var confirmBtn = byId('meetlet-registration-modal-confirm');
var tpl = opts.templateId ? document.getElementById(opts.templateId) : null;
if (!title || !body || !confirmBtn) {
return Promise.resolve(false);
}
title.textContent = opts.title || '';
if (tpl) {
body.innerHTML = '';
if (tpl.content && tpl.content.cloneNode) {
body.appendChild(tpl.content.cloneNode(true));
} else {
body.innerHTML = tpl.innerHTML;
}
} else {
body.innerHTML = '';
}
confirmBtn.textContent = opts.confirmLabel || '';
confirmBtn.className = 'btn ' + (opts.danger ? 'btn-outline-danger' : 'btn-primary');
modalState.lastFocus = document.activeElement;
modalScroll().lock();
modal.removeAttribute('hidden');
modal.style.display = 'flex';
modalState.open = true;
modalState.onKey = function (e) {
if (!modalState.open) {
return;
}
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
closeRegistrationModal(false);
return;
}
if (e.key !== 'Tab') {
return;
}
var focusable = modal.querySelectorAll(
'button:not([disabled]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (!focusable.length) {
return;
}
var first = focusable[0];
var last = focusable[focusable.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);
window.setTimeout(function () {
modalScroll().focusNoScroll(confirmBtn);
}, 20);
return new Promise(function (resolve) {
modalState.resolve = resolve;
});
}
function postAction(action) {
var cfg = byId('meetlet-registration-config');
var flash = byId('meetlet-registration-flash');
if (!cfg) {
return;
}
setFlash(flash, true, '…');
Ajax.call([{
methodname: 'mod_meetletacbfree_registration_action',
args: {
action: action,
registration_api_url: currentApiUrl(cfg)
}
}])[0].then(function(data) {
data.summary = {
active: !!data.active,
status: data.status,
status_label: data.status_label,
product_label: data.product_label,
domain: data.domain,
installation_id: data.installation_id,
can_deactivate: !!data.can_deactivate
};
if (data.summary) {
renderSummary(data.summary);
}
if (data.success) {
setFlash(flash, true, data.message);
if (action === 'activate' || action === 'deactivate') {
window.setTimeout(function() {
window.location.reload();
}, 600);
}
} else {
setFlash(flash, false, data.error || 'Request failed');
}
}).catch(function(err) {
setFlash(flash, false, err && err.message ? err.message : 'Request failed');
});
}
function bindModalChrome() {
var modal = byId('meetlet-registration-modal');
if (!modal || modal.dataset.bound === '1') {
return;
}
modal.dataset.bound = '1';
modal.addEventListener('click', function (e) {
if (e.target.closest('[data-meetlet-modal-dismiss]')) {
e.preventDefault();
closeRegistrationModal(false);
}
});
var confirmBtn = byId('meetlet-registration-modal-confirm');
if (confirmBtn) {
confirmBtn.addEventListener('click', function (e) {
e.preventDefault();
closeRegistrationModal(true);
});
}
}
function bindDeactivateButton(btn) {
if (!btn || btn.dataset.bound === '1') {
return;
}
var cfg = byId('meetlet-registration-config');
btn.dataset.bound = '1';
btn.addEventListener('click', function () {
openRegistrationModal({
title: (cfg && cfg.dataset.confirmDeactivateTitle) || '',
templateId: 'meetlet-registration-tpl-deactivate',
confirmLabel: (cfg && cfg.dataset.confirmDeactivateOk) || '',
danger: true,
}).then(function (ok) {
if (ok) {
postAction('deactivate');
}
});
});
}
function bind() {
var activate = byId('meetlet-registration-activate');
var recheck = byId('meetlet-registration-recheck');
var deactivate = byId('meetlet-registration-deactivate');
var cfg = byId('meetlet-registration-config');
if ((!activate && !recheck) || (activate && activate.dataset.bound === '1')) {
return;
}
bindModalChrome();
if (activate) {
activate.dataset.bound = '1';
activate.addEventListener('click', function () {
openRegistrationModal({
title: (cfg && cfg.dataset.confirmActivateTitle) || '',
templateId: 'meetlet-registration-tpl-activate',
confirmLabel: (cfg && cfg.dataset.confirmActivateOk) || '',
danger: false,
}).then(function (ok) {
if (ok) {
postAction('activate');
}
});
});
}
if (recheck) {
recheck.dataset.bound = '1';
recheck.addEventListener('click', function () {
postAction('recheck');
});
}
bindDeactivateButton(deactivate);
}
return {init: bind};
});
+160
View File
@@ -0,0 +1,160 @@
// 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_toggles
* @copyright 2026 MeetLet <info@meetlet.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
'use strict';
function enhanceInput(input) {
if (!input || input.getAttribute('data-meetlet-switch') === '1') {
return;
}
if (input.type !== 'checkbox') {
return;
}
input.setAttribute('data-meetlet-switch', '1');
input.classList.add('meetlet-switch__input');
var compact = input.classList.contains('meetlet-access-check');
var switchClass = compact ? 'meetlet-switch meetlet-switch--compact' : 'meetlet-switch';
var track = document.createElement('span');
track.className = 'meetlet-switch__track';
track.setAttribute('aria-hidden', 'true');
var thumb = document.createElement('span');
thumb.className = 'meetlet-switch__thumb';
track.appendChild(thumb);
var parentLabel = input.closest('label');
if (parentLabel && parentLabel.contains(input)) {
parentLabel.classList.add('meetlet-switch');
if (compact) {
parentLabel.classList.add('meetlet-switch--compact');
}
if (input.nextSibling) {
parentLabel.insertBefore(track, input.nextSibling);
} else {
parentLabel.appendChild(track);
}
return;
}
var wrap = document.createElement('label');
wrap.className = switchClass;
if (input.id) {
wrap.setAttribute('for', input.id);
}
input.parentNode.insertBefore(wrap, input);
wrap.appendChild(input);
wrap.appendChild(track);
}
function run() {
var adminRoot = document.querySelector('#adminsettings');
if (adminRoot) {
adminRoot.querySelectorAll('.form-setting input[type="checkbox"]').forEach(enhanceInput);
}
document.querySelectorAll('.meetlet-access-check').forEach(enhanceInput);
var form = document.getElementById('mform1') || document.querySelector('form.mform');
if (form) {
form.querySelectorAll(
' input[name="autooffline"], input[name="showzip"], input[name="showmp4"],' +
' input[name="showmakeoffline"], input[name="showrecordingstab"], input[name="showcontenttab"],' +
' input[name="featureaccess_override"], input[name="default_waitforhost"],' +
' input[name="waitforhost"], input[name="https"]'
).forEach(enhanceInput);
}
bindHourPicker();
}
function selectedHours(wrap) {
var hours = [];
wrap.querySelectorAll('.meetlet-hourchip.is-on').forEach(function (btn) {
hours.push(parseInt(btn.getAttribute('data-hour'), 10));
});
hours.sort(function (a, b) { return a - b; });
return hours;
}
function applyHours(wrap, list) {
var set = {};
(list || []).forEach(function (h) { set[String(h)] = true; });
wrap.querySelectorAll('.meetlet-hourchip').forEach(function (btn) {
var on = !!set[btn.getAttribute('data-hour')];
btn.classList.toggle('is-on', on);
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
});
var input = wrap.querySelector('[data-meetlet-hours-input]');
if (input) {
input.value = selectedHours(wrap).join(',');
}
}
function syncHoursEnabled() {
var sel = document.querySelector('select[name="s_meetletacbfree_agent_dispatch"]')
|| document.getElementById('id_s_meetletacbfree_agent_dispatch');
var wrap = document.querySelector('[data-meetlet-hours-wrap]');
if (!wrap) {
return;
}
var item = wrap.closest('.form-item');
var windowMode = !sel || sel.value === 'window';
wrap.classList.toggle('is-dimmed', !windowMode);
if (item) {
item.classList.toggle('is-dimmed', !windowMode);
}
}
function bindHourPicker() {
var wrap = document.querySelector('[data-meetlet-hours-wrap]');
if (!wrap || wrap.getAttribute('data-bound') === '1') {
return;
}
wrap.setAttribute('data-bound', '1');
wrap.addEventListener('click', function (e) {
var chip = e.target.closest('.meetlet-hourchip');
if (chip && wrap.contains(chip)) {
e.preventDefault();
chip.classList.toggle('is-on');
chip.setAttribute('aria-pressed', chip.classList.contains('is-on') ? 'true' : 'false');
applyHours(wrap, selectedHours(wrap));
return;
}
var preset = e.target.closest('.meetlet-hourpreset');
if (preset && wrap.contains(preset)) {
e.preventDefault();
var raw = preset.getAttribute('data-hours') || '';
var list = raw === '' ? [] : raw.split(',').map(function (x) { return parseInt(x, 10); });
applyHours(wrap, list);
}
});
var sel = document.querySelector('select[name="s_meetletacbfree_agent_dispatch"]')
|| document.getElementById('id_s_meetletacbfree_agent_dispatch');
if (sel) {
sel.addEventListener('change', syncHoursEnabled);
if (document.documentElement.getAttribute('dir') === 'rtl') {
sel.setAttribute('dir', 'rtl');
}
}
syncHoursEnabled();
}
return {init: run};
});
+1052
View File
File diff suppressed because it is too large Load Diff