ÿØÿà JFIF ÿÛ „ ( %!1"%)+...383-7(-.+
-%%--------+------------/----------------------------ÿÀ ¶" ÿÄ ÿÄ @ !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ ÿÄ ' 1!AQ2a"‘±#ÿÚ ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy åÁ§$á«%ºq˜™JyyË0y«^Ï
-3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx
LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒmq;Tt¶–}0"zÖPcd1š¤tˆ„ÐÓâqú[™öUCŠ2޿دÉ(¸ßúçšmW,Ò ºf‹Ån™ÄLêÃ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT]
ªb—ÊîKC^×M“xùäñz¬zJ×{4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi
[0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+«¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©HÈÀá«hi³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ
ƒÜÒ? :õdsÀ–3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LÇÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl
Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1
º©Ø¬wiÁ^l5×I/';Ë+´Z‡ÓÄFÕp[Èð’
HÄ ó
9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ
QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ
÷jsςҒKÈcø(é›;k(’#ôŒQ
™C>
õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu
w+"åÉǫ6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆÁ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ÉdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X&,Gt
·K¢ÅÅ·¥RÄÊÉbKn
NR2g$VµÕó
Qr#–ÓZUÏð0ÎtÙÀœz
ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z
à¾]
üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢Æ±qWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGކ)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„
g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ²áÂ!ÑLÄÿ
³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#»
Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï
–éXá³6WÒïIL-Ї
Ç
PK >\' src/providerchooser.jsnu [ // 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 .
/**
* AI provider selection handler.
*
* @module core_ai/providerchooser
* @copyright 2024 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Selectors = {
fields: {
selector: '[data-aiproviderchooser-field="selector"]',
updateButton: '[data-aiproviderchooser-field="updateButton"]',
},
};
/**
* Initialise the AI provider chooser.
*/
export const init = () => {
document.querySelector(Selectors.fields.selector)?.addEventListener('change', e => {
const form = e.target.closest('form');
const updateButton = form.querySelector(Selectors.fields.updateButton);
const url = new URL(form.action);
form.action = url.toString();
updateButton.click();
});
};
PK >\o` src/policymodal.jsnu [ // 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 .
import Modal from 'core/modal';
import Policy from './policy';
import CustomEvents from 'core/custom_interaction_events';
/**
* The Javascript module to handle the policy modal.
*
* @module core_ai/policymodal
* @copyright Andrew Lyons
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class PolicyModal extends Modal {
static TYPE = 'core_ai/policymodal';
static TEMPLATE = 'core_ai/policymodal';
/**
* Configure the modal.
*
* @param {object} modalConfig The modal configuration.
*/
configure(modalConfig) {
super.configure({
...modalConfig,
show: true,
removeOnClose: true,
});
this.context = modalConfig.context;
this.setXlarge();
}
/**
* Set the modal to be extra large.
*/
setXlarge() {
this.getModal().addClass('modal-xl');
}
/**
* Handle click events within the policy modal.
*/
registerEventListeners() {
super.registerEventListeners();
this.registerCloseOnSave();
this.registerCloseOnCancel();
this.getModal().on(CustomEvents.events.activate, this.getActionSelector('save'), (e) => {
e.preventDefault();
Policy.acceptPolicy();
});
}
}
PK >\X*B ) src/aiprovider_action_management_table.jsnu [ // 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 .
import PluginManagementTable from 'core_admin/plugin_management_table';
import {call as fetchMany} from 'core/ajax';
let watching = false;
/**
* Handles setting plugin state for the AI provider management table.
*
* @module core_ai/aiprovider_action_management_table
* @copyright 2024 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class extends PluginManagementTable {
/**
* Constructor for the class.
*
* @param {int} providerid The provider id.
*/
constructor(providerid) {
super(); // Call the parent constructor, so inherited properties and methods initialize properly.
this.providerid = providerid; // Store provider id as an instance field.
}
/**
* Initialise an instance of the class.
*
* @param {int} providerid The provider id.
*/
static init(providerid) {
if (watching) {
return;
}
watching = true;
new this(providerid);
}
/**
* Set the plugin state (enabled or disabled).
*
* @param {string} methodname The web service to call.
* @param {string} plugin The name of the plugin and action to set the state for.
* @param {number} state The state to set.
* @returns {Promise}
*/
setPluginState(methodname, plugin, state) {
const providerid = this.providerid;
return fetchMany([{
methodname,
args: {
plugin,
state,
providerid,
},
}])[0];
}
}
PK >\F" " + src/aiprovider_instance_management_table.jsnu [ // 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 .
import PluginManagementTable from 'core_admin/plugin_management_table';
import {call as fetchMany} from 'core/ajax';
import {refreshTableContent} from 'core_table/dynamic';
import Pending from 'core/pending';
import {fetchNotifications} from 'core/notification';
import {prefetchStrings} from 'core/prefetch';
import {getString} from 'core/str';
import DeleteCancelModal from 'core/modal_delete_cancel';
import ModalEvents from 'core/modal_events';
let watching = false;
/**
* Handles setting plugin state for the AI provider management table.
*
* @module core_ai/aiprovider_instance_management_table
* @copyright 2024 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class extends PluginManagementTable {
constructor() {
super();
this.addClickHandler(this.handleDelete);
}
/**
* Initialise an instance of the class.
*
* This is just a way of making it easier to initialise an instance of the class from PHP.
*/
static init() {
if (watching) {
return;
}
prefetchStrings('core_ai', [
'providerinstancedelete',
'providerinstancedeleteconfirm',
]);
watching = true;
new this();
}
/**
* Call the delete service.
*
* @param {string} methodname The web service to call
* @param {number} providerid The provider id.
* @return {Promise} The promise.
*/
deleteProvider(methodname, providerid) {
return fetchMany([{
methodname,
args: {
providerid,
},
}])[0];
}
/**
* Handle delete.
*
* @param {HTMLElement} tableRoot
* @param {Event} e
*/
async handleDelete(tableRoot, e) {
const deleteElement = e.target.closest('[data-delete-method]');
if (deleteElement) {
e.preventDefault();
const providerId = e.target.dataset.id;
const deleteMethod = e.target.dataset.deleteMethod;
const bodyParams = {
provider: e.target.dataset.provider,
name: e.target.dataset.name,
};
const modal = await DeleteCancelModal.create({
title: getString('providerinstancedelete', 'core_ai'),
body: getString('providerinstancedeleteconfirm', 'core_ai', bodyParams),
show: true,
removeOnClose: true,
});
// Handle delete event.
modal.getRoot().on(ModalEvents.delete, async(e) => {
e.preventDefault();
const pendingPromise = new Pending('core_table/dynamic:deleteProvider');
await this.deleteProvider(deleteMethod, providerId);
// Reload the table, so we get the updated list of providers, and any messages.
await Promise.all([
refreshTableContent(tableRoot),
fetchNotifications(),
]);
modal.destroy();
pendingPromise.resolve();
});
}
}
}
PK >\>; src/repository.jsnu [ // 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 .
/**
* Repository helper for the Moodle Tiny Autosave plugin.
*
* @module tiny_autosave/repository
* @copyright 2022 Andrew Lyons
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call} from 'core/ajax';
import * as config from 'core/config';
import * as Options from './options';
import Pending from 'core/pending';
import {ensureEditorIsValid} from 'editor_tiny/utils';
const fetchOne = (methodname, args) => call([{
methodname,
args,
}])[0];
/**
* Resume an Autosave session.
*
* @param {TinyMCE} editor The TinyMCE editor instance
* @returns {Promise} The Autosave session
*/
export const resumeAutosaveSession = (editor) => {
if (!ensureEditorIsValid(editor)) {
return Promise.reject('Invalid editor');
}
const pendingPromise = new Pending('tiny_autosave/repository:resumeAutosaveSession');
return fetchOne('tiny_autosave_resume_session', {
contextid: Options.getContextId(editor),
pagehash: Options.getPageHash(editor),
pageinstance: Options.getPageInstance(editor),
elementid: editor.targetElm.id,
draftid: Options.getDraftItemId(editor),
})
.then((result) => {
pendingPromise.resolve();
return result;
});
};
/**
* Update the content of the Autosave session.
*
* @param {TinyMCE} editor The TinyMCE editor instance
* @returns {Promise} The Autosave session
*/
export const updateAutosaveSession = (editor) => {
if (!ensureEditorIsValid(editor)) {
return Promise.reject('Invalid editor');
}
if (Options.hasAutosaveHasReset(editor)) {
return Promise.reject('Skipping store of autosave content - content has been reset');
}
const pendingPromise = new Pending('tiny_autosave/repository:updateAutosaveSession');
return fetchOne('tiny_autosave_update_session', {
contextid: Options.getContextId(editor),
pagehash: Options.getPageHash(editor),
pageinstance: Options.getPageInstance(editor),
elementid: editor.targetElm.id,
drafttext: editor.getContent(),
})
.then((result) => {
pendingPromise.resolve();
return result;
});
};
/**
* Remove the Autosave session.
*
* @param {TinyMCE} editor The TinyMCE editor instance
*/
export const removeAutosaveSession = (editor) => {
if (!ensureEditorIsValid(editor)) {
throw new Error('Invalid editor');
}
Options.setAutosaveHasReset(editor);
// Please note that we must use a Beacon send here.
// The XHR is not guaranteed because it will be aborted on page transition.
// https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API
// Note: Moodle does not currently have a sendBeacon API endpoint.
const requestUrl = new URL(`${config.wwwroot}/lib/ajax/service.php`);
requestUrl.searchParams.set('sesskey', config.sesskey);
const args = {
contextid: Options.getContextId(editor),
pagehash: Options.getPageHash(editor),
pageinstance: Options.getPageInstance(editor),
elementid: editor.targetElm.id,
};
navigator.sendBeacon(requestUrl, JSON.stringify([{
index: 0,
methodname: 'tiny_autosave_reset_session',
args,
}]));
};
PK >\Qk k
src/helper.jsnu [ // 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 .
/**
* The helper module or AI Subsystem.
*
* @module core_ai/helper
* @copyright 2024 Huong Nguyen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class AIHelper {
/**
* Replace double line breaks with and with
for paragraphs.
* This is to handle the difference in response from the AI to what is expected by the editor.
*
* @param {String} text The text to replace.
* @returns {String}
*/
static replaceLineBreaks(text) {
// Replace double line breaks with
for paragraphs
const textWithParagraphs = text.replace(/\n{2,}|\r\n/g, ' ');
// Replace remaining single line breaks with tags
const textWithBreaks = textWithParagraphs.replace(/\n/g, ' ');
// Add opening and closing
tags to wrap the entire content
return `
${textWithBreaks}
`;
}
/**
* Replace markdown formatting.
* Even when asked not to, AI models will sometimes return markdown.
*
* @param {String} text The text to replace.
* @returns {String}
*/
static replaceMarkdown(text) {
// Replace markdown bold formatting HTML equivalent.
const textWithMarkdown = text.replace(/\*\*(.*?)\*\*/g, '$1 ');
return textWithMarkdown;
}
/**
* Format the response provided by the AI model.
*
* @param {String} text The text to format.
* @returns {String}
*/
static formatResponse(text) {
let formattedText = this.replaceLineBreaks(text) ;
formattedText = this.replaceMarkdown(formattedText);
return formattedText;
}
}
PK >\j# #
src/policy.jsnu [ // 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 .
import {getPolicyStatus, setPolicyStatus} from "./repository";
/**
* The Javascript module to handle the policy acceptance.
*
* @module core_ai/policy
* @copyright 2024 Andrew Lyons
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class {
static #policyAcceptedFor = {};
static preconfigurePolicyState(userid, state) {
if (!this.#policyAcceptedFor.hasOwnProperty(userid)) {
this.#policyAcceptedFor[userid] = state;
}
}
/**
* Get the policy status for a user.
*
* @param {Number} userid The user ID.
* @return {Promise} The policy status.
*/
static async getPolicyStatus(userid) {
if (this.#policyAcceptedFor[userid]) {
return this.#policyAcceptedFor[userid];
}
const accepted = await getPolicyStatus(userid);
this.#policyAcceptedFor[userid] = accepted.status;
return accepted.status;
}
static acceptPolicy() {
this.#policyAcceptedFor[M.cfg.userId] = true;
return setPolicyStatus(M.cfg.contextid);
}
}
PK >\Q
build/repository.min.jsnu [ define("tiny_autosave/repository",["exports","core/ajax","core/config","./options","core/pending","editor_tiny/utils"],(function(_exports,_ajax,config,Options,_pending,_utils){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* Repository helper for the Moodle Tiny Autosave plugin.
*
* @module tiny_autosave/repository
* @copyright 2022 Andrew Lyons
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateAutosaveSession=_exports.resumeAutosaveSession=_exports.removeAutosaveSession=void 0,config=_interopRequireWildcard(config),Options=_interopRequireWildcard(Options),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};const fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];_exports.resumeAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))return Promise.reject("Invalid editor");const pendingPromise=new _pending.default("tiny_autosave/repository:resumeAutosaveSession");return fetchOne("tiny_autosave_resume_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,draftid:Options.getDraftItemId(editor)}).then((result=>(pendingPromise.resolve(),result)))};_exports.updateAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))return Promise.reject("Invalid editor");if(Options.hasAutosaveHasReset(editor))return Promise.reject("Skipping store of autosave content - content has been reset");const pendingPromise=new _pending.default("tiny_autosave/repository:updateAutosaveSession");return fetchOne("tiny_autosave_update_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,drafttext:editor.getContent()}).then((result=>(pendingPromise.resolve(),result)))};_exports.removeAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))throw new Error("Invalid editor");Options.setAutosaveHasReset(editor);const requestUrl=new URL("".concat(config.wwwroot,"/lib/ajax/service.php"));requestUrl.searchParams.set("sesskey",config.sesskey);const args={contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id};navigator.sendBeacon(requestUrl,JSON.stringify([{index:0,methodname:"tiny_autosave_reset_session",args:args}]))}}));
//# sourceMappingURL=repository.min.js.mapPK >\Fe e / build/aiprovider_action_management_table.min.jsnu [ define("core_ai/aiprovider_action_management_table",["exports","core_admin/plugin_management_table","core/ajax"],(function(_exports,_plugin_management_table,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_plugin_management_table=(obj=_plugin_management_table)&&obj.__esModule?obj:{default:obj};let watching=!1;
/**
* Handles setting plugin state for the AI provider management table.
*
* @module core_ai/aiprovider_action_management_table
* @copyright 2024 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/class _default extends _plugin_management_table.default{constructor(providerid){super(),this.providerid=providerid}static init(providerid){watching||(watching=!0,new this(providerid))}setPluginState(methodname,plugin,state){const providerid=this.providerid;return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state,providerid:providerid}}])[0]}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=aiprovider_action_management_table.min.js.mapPK >\SFd d 1 build/aiprovider_instance_management_table.min.jsnu [ define("core_ai/aiprovider_instance_management_table",["exports","core_admin/plugin_management_table","core/ajax","core_table/dynamic","core/pending","core/notification","core/prefetch","core/str","core/modal_delete_cancel","core/modal_events"],(function(_exports,_plugin_management_table,_ajax,_dynamic,_pending,_notification,_prefetch,_str,_modal_delete_cancel,_modal_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_plugin_management_table=_interopRequireDefault(_plugin_management_table),_pending=_interopRequireDefault(_pending),_modal_delete_cancel=_interopRequireDefault(_modal_delete_cancel),_modal_events=_interopRequireDefault(_modal_events);let watching=!1;
/**
* Handles setting plugin state for the AI provider management table.
*
* @module core_ai/aiprovider_instance_management_table
* @copyright 2024 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/class _default extends _plugin_management_table.default{constructor(){super(),this.addClickHandler(this.handleDelete)}static init(){watching||((0,_prefetch.prefetchStrings)("core_ai",["providerinstancedelete","providerinstancedeleteconfirm"]),watching=!0,new this)}deleteProvider(methodname,providerid){return(0,_ajax.call)([{methodname:methodname,args:{providerid:providerid}}])[0]}async handleDelete(tableRoot,e){if(e.target.closest("[data-delete-method]")){e.preventDefault();const providerId=e.target.dataset.id,deleteMethod=e.target.dataset.deleteMethod,bodyParams={provider:e.target.dataset.provider,name:e.target.dataset.name},modal=await _modal_delete_cancel.default.create({title:(0,_str.getString)("providerinstancedelete","core_ai"),body:(0,_str.getString)("providerinstancedeleteconfirm","core_ai",bodyParams),show:!0,removeOnClose:!0});modal.getRoot().on(_modal_events.default.delete,(async e=>{e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:deleteProvider");await this.deleteProvider(deleteMethod,providerId),await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),modal.destroy(),pendingPromise.resolve()}))}}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=aiprovider_instance_management_table.min.js.mapPK >\-8 build/repository.min.js.mapnu [ {"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Repository helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/repository\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport * as config from 'core/config';\nimport * as Options from './options';\nimport Pending from 'core/pending';\nimport {ensureEditorIsValid} from 'editor_tiny/utils';\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\n\n/**\n * Resume an Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise} The Autosave session\n */\nexport const resumeAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n const pendingPromise = new Pending('tiny_autosave/repository:resumeAutosaveSession');\n return fetchOne('tiny_autosave_resume_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n draftid: Options.getDraftItemId(editor),\n })\n .then((result) => {\n pendingPromise.resolve();\n return result;\n });\n};\n\n/**\n * Update the content of the Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise} The Autosave session\n */\nexport const updateAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n if (Options.hasAutosaveHasReset(editor)) {\n return Promise.reject('Skipping store of autosave content - content has been reset');\n }\n\n const pendingPromise = new Pending('tiny_autosave/repository:updateAutosaveSession');\n\n return fetchOne('tiny_autosave_update_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n drafttext: editor.getContent(),\n })\n .then((result) => {\n pendingPromise.resolve();\n return result;\n });\n};\n\n/**\n * Remove the Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n */\nexport const removeAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n throw new Error('Invalid editor');\n }\n Options.setAutosaveHasReset(editor);\n\n // Please note that we must use a Beacon send here.\n // The XHR is not guaranteed because it will be aborted on page transition.\n // https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API\n // Note: Moodle does not currently have a sendBeacon API endpoint.\n const requestUrl = new URL(`${config.wwwroot}/lib/ajax/service.php`);\n requestUrl.searchParams.set('sesskey', config.sesskey);\n\n const args = {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n };\n navigator.sendBeacon(requestUrl, JSON.stringify([{\n index: 0,\n methodname: 'tiny_autosave_reset_session',\n args,\n }]));\n};\n"],"names":["fetchOne","methodname","args","editor","Promise","reject","pendingPromise","Pending","contextid","Options","getContextId","pagehash","getPageHash","pageinstance","getPageInstance","elementid","targetElm","id","draftid","getDraftItemId","then","result","resolve","hasAutosaveHasReset","drafttext","getContent","Error","setAutosaveHasReset","requestUrl","URL","config","wwwroot","searchParams","set","sesskey","navigator","sendBeacon","JSON","stringify","index"],"mappings":";;;;;;;iTA6BMA,SAAW,CAACC,WAAYC,QAAS,cAAK,CAAC,CACzCD,WAAAA,WACAC,KAAAA,QACA,kCAQkCC,cAC7B,8BAAoBA,eACdC,QAAQC,OAAO,wBAEpBC,eAAiB,IAAIC,iBAAQ,yDAC5BP,SAAS,+BAAgC,CAC5CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,GAC5BC,QAAST,QAAQU,eAAehB,UAEnCiB,MAAMC,SACHf,eAAegB,UACRD,0CAUuBlB,cAC7B,8BAAoBA,eACdC,QAAQC,OAAO,qBAEtBI,QAAQc,oBAAoBpB,eACrBC,QAAQC,OAAO,qEAGpBC,eAAiB,IAAIC,iBAAQ,yDAE5BP,SAAS,+BAAgC,CAC5CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,GAC5BO,UAAWrB,OAAOsB,eAErBL,MAAMC,SACHf,eAAegB,UACRD,0CASuBlB,cAC7B,8BAAoBA,cACf,IAAIuB,MAAM,kBAEpBjB,QAAQkB,oBAAoBxB,cAMtByB,WAAa,IAAIC,cAAOC,OAAOC,kCACrCH,WAAWI,aAAaC,IAAI,UAAWH,OAAOI,eAExChC,KAAO,CACTM,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,IAEhCkB,UAAUC,WAAWR,WAAYS,KAAKC,UAAU,CAAC,CAC7CC,MAAO,EACPtC,WAAY,8BACZC,KAAAA"}PK >\Ec
build/policymodal.min.js.mapnu [ {"version":3,"file":"policymodal.min.js","sources":["../src/policymodal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport Modal from 'core/modal';\nimport Policy from './policy';\nimport CustomEvents from 'core/custom_interaction_events';\n\n/**\n * The Javascript module to handle the policy modal.\n *\n * @module core_ai/policymodal\n * @copyright Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class PolicyModal extends Modal {\n static TYPE = 'core_ai/policymodal';\n static TEMPLATE = 'core_ai/policymodal';\n\n /**\n * Configure the modal.\n *\n * @param {object} modalConfig The modal configuration.\n */\n configure(modalConfig) {\n super.configure({\n ...modalConfig,\n show: true,\n removeOnClose: true,\n });\n\n this.context = modalConfig.context;\n this.setXlarge();\n }\n\n /**\n * Set the modal to be extra large.\n */\n setXlarge() {\n this.getModal().addClass('modal-xl');\n }\n\n /**\n * Handle click events within the policy modal.\n */\n registerEventListeners() {\n super.registerEventListeners();\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n\n this.getModal().on(CustomEvents.events.activate, this.getActionSelector('save'), (e) => {\n e.preventDefault();\n Policy.acceptPolicy();\n });\n }\n}\n"],"names":["PolicyModal","Modal","configure","modalConfig","show","removeOnClose","context","setXlarge","getModal","addClass","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","on","CustomEvents","events","activate","this","getActionSelector","e","preventDefault","acceptPolicy"],"mappings":";;;;;;;uPA0BqBA,oBAAoBC,eASrCC,UAAUC,mBACAD,UAAU,IACTC,YACHC,MAAM,EACNC,eAAe,SAGdC,QAAUH,YAAYG,aACtBC,YAMTA,iBACSC,WAAWC,SAAS,YAM7BC,+BACUA,8BACDC,2BACAC,6BAEAJ,WAAWK,GAAGC,mCAAaC,OAAOC,SAAUC,KAAKC,kBAAkB,SAAUC,IAC9EA,EAAEC,iCACKC,uEArCErB,mBACH,uCADGA,uBAEC"}PK >\S6_F F 3 build/aiprovider_action_management_table.min.js.mapnu [ {"version":3,"file":"aiprovider_action_management_table.min.js","sources":["../src/aiprovider_action_management_table.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport PluginManagementTable from 'core_admin/plugin_management_table';\nimport {call as fetchMany} from 'core/ajax';\n\nlet watching = false;\n\n/**\n * Handles setting plugin state for the AI provider management table.\n *\n * @module core_ai/aiprovider_action_management_table\n * @copyright 2024 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends PluginManagementTable {\n\n /**\n * Constructor for the class.\n *\n * @param {int} providerid The provider id.\n */\n constructor(providerid) {\n super(); // Call the parent constructor, so inherited properties and methods initialize properly.\n this.providerid = providerid; // Store provider id as an instance field.\n }\n\n /**\n * Initialise an instance of the class.\n *\n * @param {int} providerid The provider id.\n */\n static init(providerid) {\n if (watching) {\n return;\n }\n watching = true;\n new this(providerid);\n }\n\n /**\n * Set the plugin state (enabled or disabled).\n *\n * @param {string} methodname The web service to call.\n * @param {string} plugin The name of the plugin and action to set the state for.\n * @param {number} state The state to set.\n * @returns {Promise}\n */\n setPluginState(methodname, plugin, state) {\n const providerid = this.providerid;\n return fetchMany([{\n methodname,\n args: {\n plugin,\n state,\n providerid,\n },\n }])[0];\n }\n\n}\n"],"names":["watching","PluginManagementTable","constructor","providerid","this","setPluginState","methodname","plugin","state","args"],"mappings":"0VAiBIA,UAAW;;;;;;;4BAScC,iCAOzBC,YAAYC,yBAEHA,WAAaA,uBAQVA,YACJH,WAGJA,UAAW,MACPI,KAAKD,aAWbE,eAAeC,WAAYC,OAAQC,aACzBL,WAAaC,KAAKD,kBACjB,cAAU,CAAC,CACdG,WAAAA,WACAG,KAAM,CACFF,OAAAA,OACAC,MAAAA,MACAL,WAAAA,eAEJ"}PK >\c:6 6 build/policymodal.min.jsnu [ define("core_ai/policymodal",["exports","core/modal","./policy","core/custom_interaction_events"],(function(_exports,_modal,_policy,_custom_interaction_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}
/**
* The Javascript module to handle the policy modal.
*
* @module core_ai/policymodal
* @copyright Andrew Lyons
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),_policy=_interopRequireDefault(_policy),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events);class PolicyModal extends _modal.default{configure(modalConfig){super.configure({...modalConfig,show:!0,removeOnClose:!0}),this.context=modalConfig.context,this.setXlarge()}setXlarge(){this.getModal().addClass("modal-xl")}registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel(),this.getModal().on(_custom_interaction_events.default.events.activate,this.getActionSelector("save"),(e=>{e.preventDefault(),_policy.default.acceptPolicy()}))}}return _exports.default=PolicyModal,_defineProperty(PolicyModal,"TYPE","core_ai/policymodal"),_defineProperty(PolicyModal,"TEMPLATE","core_ai/policymodal"),_exports.default}));
//# sourceMappingURL=policymodal.min.js.mapPK >\< build/providerchooser.min.jsnu [ define("core_ai/providerchooser",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* AI provider selection handler.
*
* @module core_ai/providerchooser
* @copyright 2024 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Selectors_fields={selector:'[data-aiproviderchooser-field="selector"]',updateButton:'[data-aiproviderchooser-field="updateButton"]'};_exports.init=()=>{var _document$querySelect;null===(_document$querySelect=document.querySelector(Selectors_fields.selector))||void 0===_document$querySelect||_document$querySelect.addEventListener("change",(e=>{const form=e.target.closest("form"),updateButton=form.querySelector(Selectors_fields.updateButton),url=new URL(form.action);form.action=url.toString(),updateButton.click()}))}}));
//# sourceMappingURL=providerchooser.min.js.mapPK >\>Ƃ2 2 build/helper.min.js.mapnu [ {"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The helper module or AI Subsystem.\n *\n * @module core_ai/helper\n * @copyright 2024 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class AIHelper {\n /**\n * Replace double line breaks with and with for paragraphs.\n * This is to handle the difference in response from the AI to what is expected by the editor.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceLineBreaks(text) {\n // Replace double line breaks with
for paragraphs\n const textWithParagraphs = text.replace(/\\n{2,}|\\r\\n/g, ' ');\n\n // Replace remaining single line breaks with tags\n const textWithBreaks = textWithParagraphs.replace(/\\n/g, ' ');\n\n // Add opening and closing
tags to wrap the entire content\n return `
${textWithBreaks}
`;\n }\n\n /**\n * Replace markdown formatting.\n * Even when asked not to, AI models will sometimes return markdown.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceMarkdown(text) {\n // Replace markdown bold formatting HTML equivalent.\n const textWithMarkdown = text.replace(/\\*\\*(.*?)\\*\\*/g, '$1 ');\n\n return textWithMarkdown;\n }\n\n /**\n * Format the response provided by the AI model.\n *\n * @param {String} text The text to format.\n * @returns {String}\n */\n static formatResponse(text) {\n let formattedText = this.replaceLineBreaks(text) ;\n formattedText = this.replaceMarkdown(formattedText);\n\n return formattedText;\n }\n}\n"],"names":["text","textWithBreaks","replace","formattedText","this","replaceLineBreaks","replaceMarkdown"],"mappings":";;;;;;;;+BA8B6BA,YAKfC,eAHqBD,KAAKE,QAAQ,eAAgB,cAGdA,QAAQ,MAAO,4BAG5CD,8CAUMD,aAEMA,KAAKE,QAAQ,iBAAkB,6CAWtCF,UACdG,cAAgBC,KAAKC,kBAAkBL,aAC3CG,cAAgBC,KAAKE,gBAAgBH,eAE9BA"}PK >\7]I I build/helper.min.jsnu [ define("core_ai/helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default=
/**
* The helper module or AI Subsystem.
*
* @module core_ai/helper
* @copyright 2024 Huong Nguyen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class{static replaceLineBreaks(text){const textWithBreaks=text.replace(/\n{2,}|\r\n/g," ").replace(/\n/g," ");return"".concat(textWithBreaks,"
")}static replaceMarkdown(text){return text.replace(/\*\*(.*?)\*\*/g,"$1 ")}static formatResponse(text){let formattedText=this.replaceLineBreaks(text);return formattedText=this.replaceMarkdown(formattedText),formattedText}},_exports.default}));
//# sourceMappingURL=helper.min.js.mapPK >\T 5 build/aiprovider_instance_management_table.min.js.mapnu [ {"version":3,"file":"aiprovider_instance_management_table.min.js","sources":["../src/aiprovider_instance_management_table.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport PluginManagementTable from 'core_admin/plugin_management_table';\nimport {call as fetchMany} from 'core/ajax';\nimport {refreshTableContent} from 'core_table/dynamic';\nimport Pending from 'core/pending';\nimport {fetchNotifications} from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport DeleteCancelModal from 'core/modal_delete_cancel';\nimport ModalEvents from 'core/modal_events';\n\nlet watching = false;\n\n/**\n * Handles setting plugin state for the AI provider management table.\n *\n * @module core_ai/aiprovider_instance_management_table\n * @copyright 2024 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends PluginManagementTable {\n constructor() {\n super();\n this.addClickHandler(this.handleDelete);\n }\n\n /**\n * Initialise an instance of the class.\n *\n * This is just a way of making it easier to initialise an instance of the class from PHP.\n */\n static init() {\n if (watching) {\n return;\n }\n\n prefetchStrings('core_ai', [\n 'providerinstancedelete',\n 'providerinstancedeleteconfirm',\n ]);\n\n watching = true;\n new this();\n }\n\n /**\n * Call the delete service.\n *\n * @param {string} methodname The web service to call\n * @param {number} providerid The provider id.\n * @return {Promise} The promise.\n */\n deleteProvider(methodname, providerid) {\n return fetchMany([{\n methodname,\n args: {\n providerid,\n },\n }])[0];\n }\n\n /**\n * Handle delete.\n *\n * @param {HTMLElement} tableRoot\n * @param {Event} e\n */\n async handleDelete(tableRoot, e) {\n const deleteElement = e.target.closest('[data-delete-method]');\n if (deleteElement) {\n e.preventDefault();\n const providerId = e.target.dataset.id;\n const deleteMethod = e.target.dataset.deleteMethod;\n const bodyParams = {\n provider: e.target.dataset.provider,\n name: e.target.dataset.name,\n };\n const modal = await DeleteCancelModal.create({\n title: getString('providerinstancedelete', 'core_ai'),\n body: getString('providerinstancedeleteconfirm', 'core_ai', bodyParams),\n show: true,\n removeOnClose: true,\n });\n\n // Handle delete event.\n modal.getRoot().on(ModalEvents.delete, async(e) => {\n e.preventDefault();\n const pendingPromise = new Pending('core_table/dynamic:deleteProvider');\n await this.deleteProvider(deleteMethod, providerId);\n // Reload the table, so we get the updated list of providers, and any messages.\n await Promise.all([\n refreshTableContent(tableRoot),\n fetchNotifications(),\n ]);\n modal.destroy();\n pendingPromise.resolve();\n });\n }\n }\n}\n"],"names":["watching","PluginManagementTable","constructor","addClickHandler","this","handleDelete","deleteProvider","methodname","providerid","args","tableRoot","e","target","closest","preventDefault","providerId","dataset","id","deleteMethod","bodyParams","provider","name","modal","DeleteCancelModal","create","title","body","show","removeOnClose","getRoot","on","ModalEvents","delete","async","pendingPromise","Pending","Promise","all","destroy","resolve"],"mappings":"0wBAyBIA,UAAW;;;;;;;4BAScC,iCACzBC,2BAESC,gBAAgBC,KAAKC,4BAStBL,yCAIY,UAAW,CACvB,yBACA,kCAGJA,UAAW,MACPI,MAURE,eAAeC,WAAYC,mBAChB,cAAU,CAAC,CACdD,WAAAA,WACAE,KAAM,CACFD,WAAAA,eAEJ,sBASWE,UAAWC,MACJA,EAAEC,OAAOC,QAAQ,wBACpB,CACfF,EAAEG,uBACIC,WAAaJ,EAAEC,OAAOI,QAAQC,GAC9BC,aAAeP,EAAEC,OAAOI,QAAQE,aAChCC,WAAa,CACfC,SAAUT,EAAEC,OAAOI,QAAQI,SAC3BC,KAAMV,EAAEC,OAAOI,QAAQK,MAErBC,YAAcC,6BAAkBC,OAAO,CACzCC,OAAO,kBAAU,yBAA0B,WAC3CC,MAAM,kBAAU,gCAAiC,UAAWP,YAC5DQ,MAAM,EACNC,eAAe,IAInBN,MAAMO,UAAUC,GAAGC,sBAAYC,QAAQC,MAAAA,IACnCtB,EAAEG,uBACIoB,eAAiB,IAAIC,iBAAQ,2CAC7B/B,KAAKE,eAAeY,aAAcH,kBAElCqB,QAAQC,IAAI,EACd,gCAAoB3B,YACpB,wCAEJY,MAAMgB,UACNJ,eAAeK"}PK >\7 build/policy.min.jsnu [ define("core_ai/policy",["exports","./repository"],(function(_exports,_repository){function _classStaticPrivateFieldSpecGet(receiver,classConstructor,descriptor){return function(receiver,classConstructor){if(receiver!==classConstructor)throw new TypeError("Private static access of wrong provenance")}(receiver,classConstructor),function(descriptor,action){if(void 0===descriptor)throw new TypeError("attempted to "+action+" private static field before its declaration")}(descriptor,"get"),function(receiver,descriptor){if(descriptor.get)return descriptor.get.call(receiver);return descriptor.value}
/**
* The Javascript module to handle the policy acceptance.
*
* @module core_ai/policy
* @copyright 2024 Andrew Lyons
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/(receiver,descriptor)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;class _class{static preconfigurePolicyState(userid,state){_classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor).hasOwnProperty(userid)||(_classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid]=state)}static async getPolicyStatus(userid){if(_classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid])return _classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid];const accepted=await(0,_repository.getPolicyStatus)(userid);return _classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid]=accepted.status,accepted.status}static acceptPolicy(){return _classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[M.cfg.userId]=!0,(0,_repository.setPolicyStatus)(M.cfg.contextid)}}_exports.default=_class;var _policyAcceptedFor={writable:!0,value:{}};return _exports.default}));
//# sourceMappingURL=policy.min.js.mapPK >\o\ build/providerchooser.min.js.mapnu [ {"version":3,"file":"providerchooser.min.js","sources":["../src/providerchooser.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AI provider selection handler.\n *\n * @module core_ai/providerchooser\n * @copyright 2024 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n fields: {\n selector: '[data-aiproviderchooser-field=\"selector\"]',\n updateButton: '[data-aiproviderchooser-field=\"updateButton\"]',\n },\n};\n\n/**\n * Initialise the AI provider chooser.\n */\nexport const init = () => {\n document.querySelector(Selectors.fields.selector)?.addEventListener('change', e => {\n const form = e.target.closest('form');\n const updateButton = form.querySelector(Selectors.fields.updateButton);\n const url = new URL(form.action);\n\n form.action = url.toString();\n updateButton.click();\n });\n};\n"],"names":["Selectors","selector","updateButton","document","querySelector","addEventListener","e","form","target","closest","url","URL","action","toString","click"],"mappings":";;;;;;;;MAsBMA,iBACM,CACJC,SAAU,4CACVC,aAAc,+DAOF,6DAChBC,SAASC,cAAcJ,iBAAiBC,kEAAWI,iBAAiB,UAAUC,UACpEC,KAAOD,EAAEE,OAAOC,QAAQ,QACxBP,aAAeK,KAAKH,cAAcJ,iBAAiBE,cACnDQ,IAAM,IAAIC,IAAIJ,KAAKK,QAEzBL,KAAKK,OAASF,IAAIG,WAClBX,aAAaY"}PK >\`vk build/policy.min.js.mapnu [ {"version":3,"file":"policy.min.js","sources":["../src/policy.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {getPolicyStatus, setPolicyStatus} from \"./repository\";\n\n/**\n * The Javascript module to handle the policy acceptance.\n *\n * @module core_ai/policy\n * @copyright 2024 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n static #policyAcceptedFor = {};\n\n static preconfigurePolicyState(userid, state) {\n if (!this.#policyAcceptedFor.hasOwnProperty(userid)) {\n this.#policyAcceptedFor[userid] = state;\n }\n }\n\n /**\n * Get the policy status for a user.\n *\n * @param {Number} userid The user ID.\n * @return {Promise} The policy status.\n */\n static async getPolicyStatus(userid) {\n if (this.#policyAcceptedFor[userid]) {\n return this.#policyAcceptedFor[userid];\n }\n\n const accepted = await getPolicyStatus(userid);\n\n this.#policyAcceptedFor[userid] = accepted.status;\n\n return accepted.status;\n }\n\n static acceptPolicy() {\n this.#policyAcceptedFor[M.cfg.userId] = true;\n\n return setPolicyStatus(M.cfg.contextid);\n }\n}\n"],"names":["userid","state","_classStaticPrivateFieldSpecGet","hasOwnProperty","accepted","status","M","cfg","userId","contextid"],"mappings":";;;;;;;uJA2BmCA,OAAQC,OAC9BC,gEAAwBC,eAAeH,0EAChBA,QAAUC,oCAUbD,WACrBE,gEAAwBF,eACjBE,gEAAwBF,cAG7BI,eAAiB,+BAAgBJ,+EAEfA,QAAUI,SAASC,OAEpCD,SAASC,oGAIQC,EAAEC,IAAIC,SAAU,GAEjC,+BAAgBF,EAAEC,IAAIE,8EA7BL"}PK B\ZJr
.
.
src/filter.jsnu [ // 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 .
/**
* Question bank filter management.
*
* @module core_question/filter
* @copyright 2021 Tomo Tsuyuki
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import CoreFilter from 'core/datafilter';
import Notification from 'core/notification';
import Selectors from 'core/datafilter/selectors';
import Templates from 'core/templates';
import Fragment from 'core/fragment';
import {getString} from 'core/str';
import {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';
/**
* Initialise the question bank filter on the element with the given id.
*
* @param {String} filterRegionId ID of the HTML element containing the filters.
* @param {String} defaultcourseid Course ID for the default course to pass back to the view.
* @param {String} defaultcategoryid Question bank category ID for the default course to pass back to the view.
* @param {Number} perpage The number of questions to display per page.
* @param {Number} bankContextId Context ID of the question bank being filtered.
* @param {Number} quizCmId Course module ID of the quiz as the viewing context.
* @param {string} component Frankenstyle name of the component for the fragment API callback (e.g. core_question)
* @param {string} callback Name of the callback for the fragment API (e.g question_data)
* @param {string} view The class name of the question bank view class used for this page.
* @param {Number} cmid If we are in an activitiy, the course module ID.
* @param {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.
* @param {Object} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.
*/
export const init = async(
filterRegionId,
defaultcourseid,
defaultcategoryid,
perpage,
bankContextId,
quizCmId,
component,
callback,
view,
cmid,
pagevars,
extraparams,
) => {
const SELECTORS = {
QUESTION_CONTAINER_ID: '#questionscontainer',
QUESTION_TABLE: '#questionscontainer table',
SORT_LINK: '#questionscontainer div.sorters a',
PAGINATION_LINK: '#questionscontainer a[href].page-link',
LASTCHANGED_FIELD: '#questionsubmit input[name=lastchanged]',
BULK_ACTIONS: '#bulkactionsui-container input',
MENU_ACTIONS: '.menu-action',
EDIT_SWITCH: '.editmode-switch-form input[name=setmode]',
EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]',
SHOW_ALL_LINK: '[data-filteraction="showall"]',
};
const filterSet = document.querySelector(`#${filterRegionId}`);
const viewData = {
extraparams: JSON.stringify(extraparams),
cmid,
view,
cat: defaultcategoryid,
courseid: defaultcourseid,
filter: {},
jointype: 0,
qpage: 0,
qperpage: perpage,
sortdata: {},
lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null,
};
let sortData = {};
const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort;
if (defaultSort) {
sortData = JSON.parse(defaultSort);
}
const [
showAllText,
showPerPageText,
] = await Promise.all([
getString('showall', 'core', ''),
getString('showperpage', 'core', extraparams.defaultqperpage),
]);
/**
* Retrieve table data.
*
* @param {Object} filterdata data
* @param {Promise} pendingPromise pending promise
*/
const applyFilter = (filterdata, pendingPromise) => {
// Reload the questions based on the specified filters. If no filters are provided,
// use the default category filter condition.
if (filterdata) {
// Main join types.
viewData.jointype = parseInt(filterSet.dataset.filterverb, 10);
delete filterdata.jointype;
// Retrieve filter info.
viewData.filter = filterdata;
if (Object.keys(filterdata).length !== 0) {
if (!isNaN(viewData.jointype)) {
filterdata.jointype = viewData.jointype;
}
}
}
// Load questions for first page.
viewData.filter = JSON.stringify(filterdata);
viewData.sortdata = JSON.stringify(sortData);
viewData.quizcmid = quizCmId;
const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);
// Clear the contents of the element, then append the loading icon.
questionscontainer.innerHTML = '';
addIconToContainerRemoveOnCompletion(questionscontainer, pendingPromise);
Fragment.loadFragment(component, callback, bankContextId, viewData)
// Render questions for first page and pagination.
.then((questionhtml, jsfooter) => {
updateUrlParams(filterdata);
if (questionhtml === undefined) {
questionhtml = '';
}
if (jsfooter === undefined) {
jsfooter = '';
}
Templates.replaceNode(questionscontainer, questionhtml, jsfooter);
// Resolve filter promise.
if (pendingPromise) {
pendingPromise.resolve();
}
return {questionhtml, jsfooter};
})
.catch(Notification.exception);
};
// Init core filter processor with apply callback.
const coreFilter = new CoreFilter(filterSet, applyFilter);
coreFilter.activeFilters = {}; // Unset useless courseid filter.
coreFilter.init();
/**
* Update URL Param based upon the current filter.
*
* @param {Object} filters Active filters.
*/
const updateUrlParams = (filters) => {
const url = new URL(location.href);
const filterQuery = JSON.stringify(filters);
url.searchParams.set('filter', filterQuery);
history.pushState(filters, '', url);
const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH);
if (editSwitch) {
const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL);
const editSwitchUrl = new URL(editSwitchUrlInput.value);
editSwitchUrl.searchParams.set('filter', filterQuery);
editSwitchUrlInput.value = editSwitchUrl;
editSwitch.dataset.pageurl = editSwitchUrl;
}
};
/**
* Cleans URL parameters.
*/
const cleanUrlParams = () => {
const queryString = location.search;
const urlParams = new URLSearchParams(queryString);
if (urlParams.has('cmid')) {
const cleanedUrl = new URL(location.href.replace(location.search, ''));
cleanedUrl.searchParams.set('cmid', urlParams.get('cmid'));
history.pushState({}, '', cleanedUrl);
}
if (urlParams.has('courseid')) {
const cleanedUrl = new URL(location.href.replace(location.search, ''));
cleanedUrl.searchParams.set('courseid', urlParams.get('courseid'));
history.pushState({}, '', cleanedUrl);
}
};
// Add listeners for the sorting, paging and clear actions.
document.querySelector('.questionbankwindow').addEventListener('click', e => {
const sortableLink = e.target.closest(SELECTORS.SORT_LINK);
const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK);
const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);
const showallLink = e.target.closest(SELECTORS.SHOW_ALL_LINK);
if (sortableLink) {
e.preventDefault();
const oldSort = sortData;
sortData = {};
sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;
for (const sortname in oldSort) {
if (sortname !== sortableLink.dataset.sortname) {
sortData[sortname] = oldSort[sortname];
}
}
viewData.qpage = 0;
coreFilter.updateTableFromFilter(false);
}
if (paginationLink) {
e.preventDefault();
const paginationURL = new URL(paginationLink.getAttribute("href"));
const qpage = paginationURL.searchParams.get('qpage');
if (paginationURL.search !== null) {
viewData.qpage = qpage;
coreFilter.updateTableFromFilter(false);
}
}
if (clearLink) {
cleanUrlParams();
}
if (showallLink) {
e.preventDefault();
// Toggle between showing all and going back to the original qperpage.
if (Number(showallLink.dataset.status) === 0) {
viewData.qperpage = extraparams.maxqperpage;
showallLink.dataset.status = 1;
showallLink.innerText = showPerPageText;
} else {
viewData.qperpage = extraparams.defaultqperpage;
showallLink.dataset.status = 0;
showallLink.innerText = showAllText;
}
viewData.qpage = 0;
coreFilter.updateTableFromFilter();
}
});
// Run apply filter at page load.
let initialFilters;
let jointype = null;
if (pagevars.filter) {
// Load initial filter based on page vars.
initialFilters = pagevars.filter;
if (pagevars.jointype) {
jointype = pagevars.jointype;
}
}
if (Object.entries(initialFilters).length !== 0) {
// Remove the default empty filter row.
const emptyFilterRow = filterSet.querySelector(Selectors.filterset.regions.emptyFilterRow);
if (emptyFilterRow) {
emptyFilterRow.remove();
}
// Add filters.
let rowcount = 0;
for (const urlFilter in initialFilters) {
if (urlFilter === 'jointype') {
jointype = initialFilters[urlFilter];
continue;
}
// Add each filter row.
rowcount += 1;
const filterdata = {
filtertype: urlFilter,
values: initialFilters[urlFilter].values,
jointype: initialFilters[urlFilter].jointype,
filteroptions: initialFilters[urlFilter].filteroptions,
rownum: rowcount
};
coreFilter.addFilterRow(filterdata);
}
coreFilter.filterSet.dataset.filterverb = jointype;
// Since we must filter by category, it does not make sense to allow the top-level "match any" or "match none" conditions,
// as this would exclude the category. Remove those options and disable the select.
const join = coreFilter.filterSet.querySelector(Selectors.filterset.fields.join);
join.querySelectorAll(`option:not([value="${jointype}"])`).forEach((option) => option.remove());
join.disabled = true;
}
};
PK B\x2
src/question_engine.jsnu [ // 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 .
/**
* JavaScript required by the question engine.
*
* @module core_question/question_engine
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as scrollManager from 'core/scroll_manager';
import * as formSubmit from 'core_form/submit';
/**
* Initialise a question submit button. This saves the scroll position and
* sets the fragment on the form submit URL so the page reloads in the right place.
*
* @param {string} button the id of the button in the HTML.
*/
export const initSubmitButton = button => {
formSubmit.init(button);
scrollManager.watchScrollButtonSaves();
};
/**
* Initialise a form that contains questions printed using print_question.
* This has the effect of:
* 1. Turning off browser autocomlete.
* 2. Stopping enter from submitting the form (or toggling the next flag) unless
* keyboard focus is on the submit button or the flag.
* 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle
* the flags using ajax.
* 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.
* 5. Prevent the user from repeatedly submitting the form.
*
* @param {string} formSelector Selector to identify the form.
*/
export const initForm = (formSelector) => {
const form = document.querySelector(formSelector);
form.setAttribute('autocomplete', 'off');
form.addEventListener('submit', preventRepeatSubmission);
form.addEventListener('key', (event) => {
if (event.keyCode !== 13) {
return;
}
if (event.target.matches('a')) {
return;
}
if (event.target.matches('input[type="submit"]')) {
return;
}
if (event.target.matches('input[type=img]')) {
return;
}
if (event.target.matches('textarea') || event.target.matches('[contenteditable=true]')) {
return;
}
event.preventDefault();
});
const questionFlagSaveButtons = form.querySelectorAll('.questionflagsavebutton');
[...questionFlagSaveButtons].forEach((node) => node.remove());
// Note: The scrollToSavedPosition function tries to wait until the content has loaded before firing.
scrollManager.scrollToSavedPosition();
};
/**
* Event handler to stop a question form being submitted more than once.
*
* @param {object} event the form submit event.
*/
export const preventRepeatSubmission = (event) => {
const form = event.target.closest('form');
if (form.dataset.formSubmitted === '1') {
event.preventDefault();
return;
}
setTimeout(() => {
[...form.querySelectorAll('input[type=submit]')].forEach((input) => input.setAttribute('disabled', true));
});
form.dataset.formSubmitted = '1';
};
PK B\V? src/refresh_ui.jsnu [ // 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 .
/**
* Question bank UI refresh utility
*
* @module core_question/refresh_ui
* @copyright 2023 Catalyst IT Europe Ltd.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Fragment from 'core/fragment';
import Templates from 'core/templates';
export default {
/**
* Reload the question bank UI, retaining the current filters and sort data.
*
* @param {Element} uiRoot The root element of the UI to be refreshed. Must contain "component", "callback" and "contextid" in
* its data attributes, to be passed to the Fragment API.
* @param {URL} returnUrl The url of the current page, containing filter and sort parameters.
* @return {Promise} Resolved when the refresh is complete.
*/
refresh: (uiRoot, returnUrl) => {
return new Promise((resolve, reject) => {
const fragmentData = uiRoot.dataset;
const viewData = {};
const sortData = {};
if (returnUrl) {
returnUrl.searchParams.forEach((value, key) => {
// Match keys like 'sortdata[fieldname]' and convert them to an array,
// because the fragment API doesn't like non-alphanum argument keys.
const sortItem = key.match(/sortdata\[([^\]]+)\]/);
if (sortItem) {
// The item returned by sortItem.pop() is the contents of the matching group, the field name.
sortData[sortItem.pop()] = value;
} else {
viewData[key] = value;
}
});
}
viewData.sortdata = JSON.stringify(sortData);
// We have to use then() there, as loadFragment doesn't appear to work with await.
Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData)
.then((html, js) => {
Templates.replaceNode(uiRoot, html, js);
resolve();
return html;
})
.catch(reject);
});
}
};
PK B\aWu src/question_banks_datasource.jsnu [ // 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 .
/**
* Autocomplete data source for shared question banks.
*
* @module core_question/question_banks_datasource
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import Notification from 'core/notification';
export default {
transport: function(selector, query, callback) {
const element = document.querySelector(selector);
const contextId = element.dataset.contextid;
let requiredcapabilities = ['use'];
if (element.dataset.requiredcapabilities) {
requiredcapabilities = JSON.parse(element.dataset.requiredcapabilities);
}
if (!contextId) {
throw new Error('The attribute data-contextid is required on ' + selector);
}
fetchMany([{
methodname: 'core_question_search_shared_banks',
args: {
contextid: contextId,
search: query,
requiredcapabilities: requiredcapabilities,
},
}])[0]
.then(callback)
.catch(Notification.exception);
},
processResults: (selector, results) => {
return results.sharedbanks;
},
};
PK B\m3J
J
build/question_engine.min.jsnu [ define("core_question/question_engine",["exports","core/scroll_manager","core_form/submit"],(function(_exports,scrollManager,formSubmit){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* JavaScript required by the question engine.
*
* @module core_question/question_engine
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.preventRepeatSubmission=_exports.initSubmitButton=_exports.initForm=void 0,scrollManager=_interopRequireWildcard(scrollManager),formSubmit=_interopRequireWildcard(formSubmit);_exports.initSubmitButton=button=>{formSubmit.init(button),scrollManager.watchScrollButtonSaves()};_exports.initForm=formSelector=>{const form=document.querySelector(formSelector);form.setAttribute("autocomplete","off"),form.addEventListener("submit",preventRepeatSubmission),form.addEventListener("key",(event=>{13===event.keyCode&&(event.target.matches("a")||event.target.matches('input[type="submit"]')||event.target.matches("input[type=img]")||event.target.matches("textarea")||event.target.matches("[contenteditable=true]")||event.preventDefault())}));[...form.querySelectorAll(".questionflagsavebutton")].forEach((node=>node.remove())),scrollManager.scrollToSavedPosition()};const preventRepeatSubmission=event=>{const form=event.target.closest("form");"1"!==form.dataset.formSubmitted?(setTimeout((()=>{[...form.querySelectorAll("input[type=submit]")].forEach((input=>input.setAttribute("disabled",!0)))})),form.dataset.formSubmitted="1"):event.preventDefault()};_exports.preventRepeatSubmission=preventRepeatSubmission}));
//# sourceMappingURL=question_engine.min.js.mapPK B\J+ + * build/question_banks_datasource.min.js.mapnu [ {"version":3,"file":"question_banks_datasource.min.js","sources":["../src/question_banks_datasource.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Autocomplete data source for shared question banks.\n *\n * @module core_question/question_banks_datasource\n * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}\n * @author Mark Johnson \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default {\n\n transport: function(selector, query, callback) {\n const element = document.querySelector(selector);\n const contextId = element.dataset.contextid;\n let requiredcapabilities = ['use'];\n if (element.dataset.requiredcapabilities) {\n requiredcapabilities = JSON.parse(element.dataset.requiredcapabilities);\n }\n\n if (!contextId) {\n throw new Error('The attribute data-contextid is required on ' + selector);\n }\n\n fetchMany([{\n methodname: 'core_question_search_shared_banks',\n args: {\n contextid: contextId,\n search: query,\n requiredcapabilities: requiredcapabilities,\n },\n }])[0]\n .then(callback)\n .catch(Notification.exception);\n },\n\n processResults: (selector, results) => {\n return results.sharedbanks;\n },\n};\n"],"names":["transport","selector","query","callback","element","document","querySelector","contextId","dataset","contextid","requiredcapabilities","JSON","parse","Error","methodname","args","search","then","catch","Notification","exception","processResults","results","sharedbanks"],"mappings":";;;;;;;;sKA2Be,CAEXA,UAAW,SAASC,SAAUC,MAAOC,gBAC3BC,QAAUC,SAASC,cAAcL,UACjCM,UAAYH,QAAQI,QAAQC,cAC9BC,qBAAuB,CAAC,UACxBN,QAAQI,QAAQE,uBAChBA,qBAAuBC,KAAKC,MAAMR,QAAQI,QAAQE,wBAGjDH,gBACK,IAAIM,MAAM,+CAAiDZ,yBAG3D,CAAC,CACPa,WAAY,oCACZC,KAAM,CACFN,UAAWF,UACXS,OAAQd,MACRQ,qBAAsBA,yBAE1B,GACHO,KAAKd,UACLe,MAAMC,sBAAaC,YAGxBC,eAAgB,CAACpB,SAAUqB,UAChBA,QAAQC"}PK B\ build/refresh_ui.min.jsnu [ define("core_question/refresh_ui",["exports","core/fragment","core/templates"],(function(_exports,_fragment,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Question bank UI refresh utility
*
* @module core_question/refresh_ui
* @copyright 2023 Catalyst IT Europe Ltd.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fragment=_interopRequireDefault(_fragment),_templates=_interopRequireDefault(_templates);var _default={refresh:(uiRoot,returnUrl)=>new Promise(((resolve,reject)=>{const fragmentData=uiRoot.dataset,viewData={},sortData={};returnUrl&&returnUrl.searchParams.forEach(((value,key)=>{const sortItem=key.match(/sortdata\[([^\]]+)\]/);sortItem?sortData[sortItem.pop()]=value:viewData[key]=value})),viewData.sortdata=JSON.stringify(sortData),_fragment.default.loadFragment(fragmentData.component,fragmentData.callback,fragmentData.contextid,viewData).then(((html,js)=>(_templates.default.replaceNode(uiRoot,html,js),resolve(),html))).catch(reject)}))};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=refresh_ui.min.js.mapPK B\~a build/refresh_ui.min.js.mapnu [ {"version":3,"file":"refresh_ui.min.js","sources":["../src/refresh_ui.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Question bank UI refresh utility\n *\n * @module core_question/refresh_ui\n * @copyright 2023 Catalyst IT Europe Ltd.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Fragment from 'core/fragment';\nimport Templates from 'core/templates';\n\nexport default {\n /**\n * Reload the question bank UI, retaining the current filters and sort data.\n *\n * @param {Element} uiRoot The root element of the UI to be refreshed. Must contain \"component\", \"callback\" and \"contextid\" in\n * its data attributes, to be passed to the Fragment API.\n * @param {URL} returnUrl The url of the current page, containing filter and sort parameters.\n * @return {Promise} Resolved when the refresh is complete.\n */\n refresh: (uiRoot, returnUrl) => {\n return new Promise((resolve, reject) => {\n const fragmentData = uiRoot.dataset;\n const viewData = {};\n const sortData = {};\n if (returnUrl) {\n returnUrl.searchParams.forEach((value, key) => {\n // Match keys like 'sortdata[fieldname]' and convert them to an array,\n // because the fragment API doesn't like non-alphanum argument keys.\n const sortItem = key.match(/sortdata\\[([^\\]]+)\\]/);\n if (sortItem) {\n // The item returned by sortItem.pop() is the contents of the matching group, the field name.\n sortData[sortItem.pop()] = value;\n } else {\n viewData[key] = value;\n }\n });\n }\n viewData.sortdata = JSON.stringify(sortData);\n // We have to use then() there, as loadFragment doesn't appear to work with await.\n Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData)\n .then((html, js) => {\n Templates.replaceNode(uiRoot, html, js);\n resolve();\n return html;\n })\n .catch(reject);\n });\n }\n};\n"],"names":["refresh","uiRoot","returnUrl","Promise","resolve","reject","fragmentData","dataset","viewData","sortData","searchParams","forEach","value","key","sortItem","match","pop","sortdata","JSON","stringify","loadFragment","component","callback","contextid","then","html","js","replaceNode","catch"],"mappings":";;;;;;;4LA0Be,CASXA,QAAS,CAACC,OAAQC,YACP,IAAIC,SAAQ,CAACC,QAASC,gBACnBC,aAAeL,OAAOM,QACtBC,SAAW,GACXC,SAAW,GACbP,WACAA,UAAUQ,aAAaC,SAAQ,CAACC,MAAOC,aAG7BC,SAAWD,IAAIE,MAAM,wBACvBD,SAEAL,SAASK,SAASE,OAASJ,MAE3BJ,SAASK,KAAOD,SAI5BJ,SAASS,SAAWC,KAAKC,UAAUV,4BAE1BW,aAAad,aAAae,UAAWf,aAAagB,SAAUhB,aAAaiB,UAAWf,UACxFgB,MAAK,CAACC,KAAMC,yBACCC,YAAY1B,OAAQwB,KAAMC,IACpCtB,UACOqB,QAEVG,MAAMvB"}PK B\"
build/filter.min.jsnu [ define("core_question/filter",["exports","core/datafilter","core/notification","core/datafilter/selectors","core/templates","core/fragment","core/str","core/loadingicon"],(function(_exports,_datafilter,_notification,_selectors,_templates,_fragment,_str,_loadingicon){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Question bank filter management.
*
* @module core_question/filter
* @copyright 2021 Tomo Tsuyuki
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_datafilter=_interopRequireDefault(_datafilter),_notification=_interopRequireDefault(_notification),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates),_fragment=_interopRequireDefault(_fragment);_exports.init=async(filterRegionId,defaultcourseid,defaultcategoryid,perpage,bankContextId,quizCmId,component,callback,view,cmid,pagevars,extraparams)=>{var _document$querySelect,_document$querySelect2,_document$querySelect3,_document$querySelect4;const SELECTORS_QUESTION_CONTAINER_ID="#questionscontainer",SELECTORS_QUESTION_TABLE="#questionscontainer table",SELECTORS_SORT_LINK="#questionscontainer div.sorters a",SELECTORS_PAGINATION_LINK="#questionscontainer a[href].page-link",SELECTORS_LASTCHANGED_FIELD="#questionsubmit input[name=lastchanged]",SELECTORS_EDIT_SWITCH=".editmode-switch-form input[name=setmode]",SELECTORS_EDIT_SWITCH_URL=".editmode-switch-form input[name=pageurl]",SELECTORS_SHOW_ALL_LINK='[data-filteraction="showall"]',filterSet=document.querySelector("#".concat(filterRegionId)),viewData={extraparams:JSON.stringify(extraparams),cmid:cmid,view:view,cat:defaultcategoryid,courseid:defaultcourseid,filter:{},jointype:0,qpage:0,qperpage:perpage,sortdata:{},lastchanged:null!==(_document$querySelect=null===(_document$querySelect2=document.querySelector(SELECTORS_LASTCHANGED_FIELD))||void 0===_document$querySelect2?void 0:_document$querySelect2.value)&&void 0!==_document$querySelect?_document$querySelect:null};let sortData={};const defaultSort=null===(_document$querySelect3=document.querySelector(SELECTORS_QUESTION_TABLE))||void 0===_document$querySelect3||null===(_document$querySelect4=_document$querySelect3.dataset)||void 0===_document$querySelect4?void 0:_document$querySelect4.defaultsort;defaultSort&&(sortData=JSON.parse(defaultSort));const[showAllText,showPerPageText]=await Promise.all([(0,_str.getString)("showall","core",""),(0,_str.getString)("showperpage","core",extraparams.defaultqperpage)]),coreFilter=new _datafilter.default(filterSet,((filterdata,pendingPromise)=>{filterdata&&(viewData.jointype=parseInt(filterSet.dataset.filterverb,10),delete filterdata.jointype,viewData.filter=filterdata,0!==Object.keys(filterdata).length&&(isNaN(viewData.jointype)||(filterdata.jointype=viewData.jointype))),viewData.filter=JSON.stringify(filterdata),viewData.sortdata=JSON.stringify(sortData),viewData.quizcmid=quizCmId;const questionscontainer=document.querySelector(SELECTORS_QUESTION_CONTAINER_ID);questionscontainer.innerHTML="",(0,_loadingicon.addIconToContainerRemoveOnCompletion)(questionscontainer,pendingPromise),_fragment.default.loadFragment(component,callback,bankContextId,viewData).then(((questionhtml,jsfooter)=>(updateUrlParams(filterdata),void 0===questionhtml&&(questionhtml=""),void 0===jsfooter&&(jsfooter=""),_templates.default.replaceNode(questionscontainer,questionhtml,jsfooter),pendingPromise&&pendingPromise.resolve(),{questionhtml:questionhtml,jsfooter:jsfooter}))).catch(_notification.default.exception)}));coreFilter.activeFilters={},coreFilter.init();const updateUrlParams=filters=>{const url=new URL(location.href),filterQuery=JSON.stringify(filters);url.searchParams.set("filter",filterQuery),history.pushState(filters,"",url);const editSwitch=document.querySelector(SELECTORS_EDIT_SWITCH);if(editSwitch){const editSwitchUrlInput=document.querySelector(SELECTORS_EDIT_SWITCH_URL),editSwitchUrl=new URL(editSwitchUrlInput.value);editSwitchUrl.searchParams.set("filter",filterQuery),editSwitchUrlInput.value=editSwitchUrl,editSwitch.dataset.pageurl=editSwitchUrl}};let initialFilters;document.querySelector(".questionbankwindow").addEventListener("click",(e=>{const sortableLink=e.target.closest(SELECTORS_SORT_LINK),paginationLink=e.target.closest(SELECTORS_PAGINATION_LINK),clearLink=e.target.closest(_selectors.default.filterset.actions.resetFilters),showallLink=e.target.closest(SELECTORS_SHOW_ALL_LINK);if(sortableLink){e.preventDefault();const oldSort=sortData;sortData={},sortData[sortableLink.dataset.sortname]=sortableLink.dataset.sortorder;for(const sortname in oldSort)sortname!==sortableLink.dataset.sortname&&(sortData[sortname]=oldSort[sortname]);viewData.qpage=0,coreFilter.updateTableFromFilter(!1)}if(paginationLink){e.preventDefault();const paginationURL=new URL(paginationLink.getAttribute("href")),qpage=paginationURL.searchParams.get("qpage");null!==paginationURL.search&&(viewData.qpage=qpage,coreFilter.updateTableFromFilter(!1))}clearLink&&(()=>{const queryString=location.search,urlParams=new URLSearchParams(queryString);if(urlParams.has("cmid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("cmid",urlParams.get("cmid")),history.pushState({},"",cleanedUrl)}if(urlParams.has("courseid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("courseid",urlParams.get("courseid")),history.pushState({},"",cleanedUrl)}})(),showallLink&&(e.preventDefault(),0===Number(showallLink.dataset.status)?(viewData.qperpage=extraparams.maxqperpage,showallLink.dataset.status=1,showallLink.innerText=showPerPageText):(viewData.qperpage=extraparams.defaultqperpage,showallLink.dataset.status=0,showallLink.innerText=showAllText),viewData.qpage=0,coreFilter.updateTableFromFilter())}));let jointype=null;if(pagevars.filter&&(initialFilters=pagevars.filter,pagevars.jointype&&(jointype=pagevars.jointype)),0!==Object.entries(initialFilters).length){const emptyFilterRow=filterSet.querySelector(_selectors.default.filterset.regions.emptyFilterRow);emptyFilterRow&&emptyFilterRow.remove();let rowcount=0;for(const urlFilter in initialFilters){if("jointype"===urlFilter){jointype=initialFilters[urlFilter];continue}rowcount+=1;const filterdata={filtertype:urlFilter,values:initialFilters[urlFilter].values,jointype:initialFilters[urlFilter].jointype,filteroptions:initialFilters[urlFilter].filteroptions,rownum:rowcount};coreFilter.addFilterRow(filterdata)}coreFilter.filterSet.dataset.filterverb=jointype;const join=coreFilter.filterSet.querySelector(_selectors.default.filterset.fields.join);join.querySelectorAll('option:not([value="'.concat(jointype,'"])')).forEach((option=>option.remove())),join.disabled=!0}}}));
//# sourceMappingURL=filter.min.js.mapPK B\I
B B build/filter.min.js.mapnu [ {"version":3,"file":"filter.min.js","sources":["../src/filter.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Question bank filter management.\n *\n * @module core_question/filter\n * @copyright 2021 Tomo Tsuyuki \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CoreFilter from 'core/datafilter';\nimport Notification from 'core/notification';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport Fragment from 'core/fragment';\nimport {getString} from 'core/str';\nimport {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';\n\n/**\n * Initialise the question bank filter on the element with the given id.\n *\n * @param {String} filterRegionId ID of the HTML element containing the filters.\n * @param {String} defaultcourseid Course ID for the default course to pass back to the view.\n * @param {String} defaultcategoryid Question bank category ID for the default course to pass back to the view.\n * @param {Number} perpage The number of questions to display per page.\n * @param {Number} bankContextId Context ID of the question bank being filtered.\n * @param {Number} quizCmId Course module ID of the quiz as the viewing context.\n * @param {string} component Frankenstyle name of the component for the fragment API callback (e.g. core_question)\n * @param {string} callback Name of the callback for the fragment API (e.g question_data)\n * @param {string} view The class name of the question bank view class used for this page.\n * @param {Number} cmid If we are in an activitiy, the course module ID.\n * @param {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.\n * @param {Object} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.\n */\nexport const init = async(\n filterRegionId,\n defaultcourseid,\n defaultcategoryid,\n perpage,\n bankContextId,\n quizCmId,\n component,\n callback,\n view,\n cmid,\n pagevars,\n extraparams,\n) => {\n\n const SELECTORS = {\n QUESTION_CONTAINER_ID: '#questionscontainer',\n QUESTION_TABLE: '#questionscontainer table',\n SORT_LINK: '#questionscontainer div.sorters a',\n PAGINATION_LINK: '#questionscontainer a[href].page-link',\n LASTCHANGED_FIELD: '#questionsubmit input[name=lastchanged]',\n BULK_ACTIONS: '#bulkactionsui-container input',\n MENU_ACTIONS: '.menu-action',\n EDIT_SWITCH: '.editmode-switch-form input[name=setmode]',\n EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]',\n SHOW_ALL_LINK: '[data-filteraction=\"showall\"]',\n };\n\n const filterSet = document.querySelector(`#${filterRegionId}`);\n\n const viewData = {\n extraparams: JSON.stringify(extraparams),\n cmid,\n view,\n cat: defaultcategoryid,\n courseid: defaultcourseid,\n filter: {},\n jointype: 0,\n qpage: 0,\n qperpage: perpage,\n sortdata: {},\n lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null,\n };\n\n let sortData = {};\n const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort;\n if (defaultSort) {\n sortData = JSON.parse(defaultSort);\n }\n\n const [\n showAllText,\n showPerPageText,\n ] = await Promise.all([\n getString('showall', 'core', ''),\n getString('showperpage', 'core', extraparams.defaultqperpage),\n ]);\n\n /**\n * Retrieve table data.\n *\n * @param {Object} filterdata data\n * @param {Promise} pendingPromise pending promise\n */\n const applyFilter = (filterdata, pendingPromise) => {\n // Reload the questions based on the specified filters. If no filters are provided,\n // use the default category filter condition.\n if (filterdata) {\n // Main join types.\n viewData.jointype = parseInt(filterSet.dataset.filterverb, 10);\n delete filterdata.jointype;\n // Retrieve filter info.\n viewData.filter = filterdata;\n if (Object.keys(filterdata).length !== 0) {\n if (!isNaN(viewData.jointype)) {\n filterdata.jointype = viewData.jointype;\n }\n }\n }\n // Load questions for first page.\n viewData.filter = JSON.stringify(filterdata);\n viewData.sortdata = JSON.stringify(sortData);\n viewData.quizcmid = quizCmId;\n\n const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);\n // Clear the contents of the element, then append the loading icon.\n questionscontainer.innerHTML = '';\n addIconToContainerRemoveOnCompletion(questionscontainer, pendingPromise);\n\n Fragment.loadFragment(component, callback, bankContextId, viewData)\n // Render questions for first page and pagination.\n .then((questionhtml, jsfooter) => {\n updateUrlParams(filterdata);\n if (questionhtml === undefined) {\n questionhtml = '';\n }\n if (jsfooter === undefined) {\n jsfooter = '';\n }\n Templates.replaceNode(questionscontainer, questionhtml, jsfooter);\n // Resolve filter promise.\n if (pendingPromise) {\n pendingPromise.resolve();\n }\n return {questionhtml, jsfooter};\n })\n .catch(Notification.exception);\n };\n\n // Init core filter processor with apply callback.\n const coreFilter = new CoreFilter(filterSet, applyFilter);\n coreFilter.activeFilters = {}; // Unset useless courseid filter.\n coreFilter.init();\n\n /**\n * Update URL Param based upon the current filter.\n *\n * @param {Object} filters Active filters.\n */\n const updateUrlParams = (filters) => {\n const url = new URL(location.href);\n const filterQuery = JSON.stringify(filters);\n url.searchParams.set('filter', filterQuery);\n history.pushState(filters, '', url);\n const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH);\n if (editSwitch) {\n const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL);\n const editSwitchUrl = new URL(editSwitchUrlInput.value);\n editSwitchUrl.searchParams.set('filter', filterQuery);\n editSwitchUrlInput.value = editSwitchUrl;\n editSwitch.dataset.pageurl = editSwitchUrl;\n }\n };\n\n /**\n * Cleans URL parameters.\n */\n const cleanUrlParams = () => {\n const queryString = location.search;\n const urlParams = new URLSearchParams(queryString);\n if (urlParams.has('cmid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('cmid', urlParams.get('cmid'));\n history.pushState({}, '', cleanedUrl);\n }\n\n if (urlParams.has('courseid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('courseid', urlParams.get('courseid'));\n history.pushState({}, '', cleanedUrl);\n }\n };\n\n // Add listeners for the sorting, paging and clear actions.\n document.querySelector('.questionbankwindow').addEventListener('click', e => {\n const sortableLink = e.target.closest(SELECTORS.SORT_LINK);\n const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK);\n const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);\n const showallLink = e.target.closest(SELECTORS.SHOW_ALL_LINK);\n if (sortableLink) {\n e.preventDefault();\n const oldSort = sortData;\n sortData = {};\n sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;\n for (const sortname in oldSort) {\n if (sortname !== sortableLink.dataset.sortname) {\n sortData[sortname] = oldSort[sortname];\n }\n }\n viewData.qpage = 0;\n coreFilter.updateTableFromFilter(false);\n }\n if (paginationLink) {\n e.preventDefault();\n const paginationURL = new URL(paginationLink.getAttribute(\"href\"));\n const qpage = paginationURL.searchParams.get('qpage');\n if (paginationURL.search !== null) {\n viewData.qpage = qpage;\n coreFilter.updateTableFromFilter(false);\n }\n }\n if (clearLink) {\n cleanUrlParams();\n }\n if (showallLink) {\n\n e.preventDefault();\n\n // Toggle between showing all and going back to the original qperpage.\n if (Number(showallLink.dataset.status) === 0) {\n viewData.qperpage = extraparams.maxqperpage;\n showallLink.dataset.status = 1;\n showallLink.innerText = showPerPageText;\n } else {\n viewData.qperpage = extraparams.defaultqperpage;\n showallLink.dataset.status = 0;\n showallLink.innerText = showAllText;\n }\n viewData.qpage = 0;\n coreFilter.updateTableFromFilter();\n }\n });\n\n // Run apply filter at page load.\n let initialFilters;\n let jointype = null;\n if (pagevars.filter) {\n // Load initial filter based on page vars.\n initialFilters = pagevars.filter;\n if (pagevars.jointype) {\n jointype = pagevars.jointype;\n }\n }\n\n if (Object.entries(initialFilters).length !== 0) {\n // Remove the default empty filter row.\n const emptyFilterRow = filterSet.querySelector(Selectors.filterset.regions.emptyFilterRow);\n if (emptyFilterRow) {\n emptyFilterRow.remove();\n }\n\n // Add filters.\n let rowcount = 0;\n for (const urlFilter in initialFilters) {\n if (urlFilter === 'jointype') {\n jointype = initialFilters[urlFilter];\n continue;\n }\n // Add each filter row.\n rowcount += 1;\n const filterdata = {\n filtertype: urlFilter,\n values: initialFilters[urlFilter].values,\n jointype: initialFilters[urlFilter].jointype,\n filteroptions: initialFilters[urlFilter].filteroptions,\n rownum: rowcount\n };\n coreFilter.addFilterRow(filterdata);\n }\n coreFilter.filterSet.dataset.filterverb = jointype;\n\n // Since we must filter by category, it does not make sense to allow the top-level \"match any\" or \"match none\" conditions,\n // as this would exclude the category. Remove those options and disable the select.\n const join = coreFilter.filterSet.querySelector(Selectors.filterset.fields.join);\n join.querySelectorAll(`option:not([value=\"${jointype}\"])`).forEach((option) => option.remove());\n join.disabled = true;\n }\n};\n"],"names":["async","filterRegionId","defaultcourseid","defaultcategoryid","perpage","bankContextId","quizCmId","component","callback","view","cmid","pagevars","extraparams","SELECTORS","filterSet","document","querySelector","viewData","JSON","stringify","cat","courseid","filter","jointype","qpage","qperpage","sortdata","lastchanged","_document$querySelect2","value","sortData","defaultSort","_document$querySelect3","dataset","_document$querySelect4","defaultsort","parse","showAllText","showPerPageText","Promise","all","defaultqperpage","coreFilter","CoreFilter","filterdata","pendingPromise","parseInt","filterverb","Object","keys","length","isNaN","quizcmid","questionscontainer","innerHTML","loadFragment","then","questionhtml","jsfooter","updateUrlParams","undefined","replaceNode","resolve","catch","Notification","exception","activeFilters","init","filters","url","URL","location","href","filterQuery","searchParams","set","history","pushState","editSwitch","editSwitchUrlInput","editSwitchUrl","pageurl","initialFilters","addEventListener","e","sortableLink","target","closest","paginationLink","clearLink","Selectors","filterset","actions","resetFilters","showallLink","preventDefault","oldSort","sortname","sortorder","updateTableFromFilter","paginationURL","getAttribute","get","search","queryString","urlParams","URLSearchParams","has","cleanedUrl","replace","cleanUrlParams","Number","status","maxqperpage","innerText","entries","emptyFilterRow","regions","remove","rowcount","urlFilter","filtertype","values","filteroptions","rownum","addFilterRow","join","fields","querySelectorAll","forEach","option","disabled"],"mappings":";;;;;;;4UA+CoBA,MAChBC,eACAC,gBACAC,kBACAC,QACAC,cACAC,SACAC,UACAC,SACAC,KACAC,KACAC,SACAC,oHAGMC,gCACqB,sBADrBA,yBAEc,4BAFdA,oBAGS,oCAHTA,0BAIe,wCAJfA,4BAKiB,0CALjBA,sBAQW,4CARXA,0BASe,4CATfA,wBAUa,gCAGbC,UAAYC,SAASC,yBAAkBf,iBAEvCgB,SAAW,CACbL,YAAaM,KAAKC,UAAUP,aAC5BF,KAAAA,KACAD,KAAAA,KACAW,IAAKjB,kBACLkB,SAAUnB,gBACVoB,OAAQ,GACRC,SAAU,EACVC,MAAO,EACPC,SAAUrB,QACVsB,SAAU,GACVC,yEAAaZ,SAASC,cAAcH,sEAAvBe,uBAAqDC,6DAAS,UAG3EC,SAAW,SACTC,2CAAchB,SAASC,cAAcH,4FAAvBmB,uBAAkDC,iDAAlDC,uBAA2DC,YAC3EJ,cACAD,SAAWZ,KAAKkB,MAAML,oBAItBM,YACAC,uBACMC,QAAQC,IAAI,EAClB,kBAAU,UAAW,OAAQ,KAC7B,kBAAU,cAAe,OAAQ5B,YAAY6B,mBAuD3CC,WAAa,IAAIC,oBAAW7B,WA9Cd,CAAC8B,WAAYC,kBAGzBD,aAEA3B,SAASM,SAAWuB,SAAShC,UAAUmB,QAAQc,WAAY,WACpDH,WAAWrB,SAElBN,SAASK,OAASsB,WACqB,IAAnCI,OAAOC,KAAKL,YAAYM,SACnBC,MAAMlC,SAASM,YAChBqB,WAAWrB,SAAWN,SAASM,YAK3CN,SAASK,OAASJ,KAAKC,UAAUyB,YACjC3B,SAASS,SAAWR,KAAKC,UAAUW,UACnCb,SAASmC,SAAW9C,eAEd+C,mBAAqBtC,SAASC,cAAcH,iCAElDwC,mBAAmBC,UAAY,yDACMD,mBAAoBR,kCAEhDU,aAAahD,UAAWC,SAAUH,cAAeY,UAErDuC,MAAK,CAACC,aAAcC,YACjBC,gBAAgBf,iBACKgB,IAAjBH,eACAA,aAAe,SAEFG,IAAbF,WACAA,SAAW,uBAELG,YAAYR,mBAAoBI,aAAcC,UAEpDb,gBACAA,eAAeiB,UAEZ,CAACL,aAAAA,aAAcC,SAAAA,aAEzBK,MAAMC,sBAAaC,cAK5BvB,WAAWwB,cAAgB,GAC3BxB,WAAWyB,aAOLR,gBAAmBS,gBACfC,IAAM,IAAIC,IAAIC,SAASC,MACvBC,YAAcvD,KAAKC,UAAUiD,SACnCC,IAAIK,aAAaC,IAAI,SAAUF,aAC/BG,QAAQC,UAAUT,QAAS,GAAIC,WACzBS,WAAa/D,SAASC,cAAcH,0BACtCiE,WAAY,OACNC,mBAAqBhE,SAASC,cAAcH,2BAC5CmE,cAAgB,IAAIV,IAAIS,mBAAmBlD,OACjDmD,cAAcN,aAAaC,IAAI,SAAUF,aACzCM,mBAAmBlD,MAAQmD,cAC3BF,WAAW7C,QAAQgD,QAAUD,oBA0EjCE,eAlDJnE,SAASC,cAAc,uBAAuBmE,iBAAiB,SAASC,UAC9DC,aAAeD,EAAEE,OAAOC,QAAQ1E,qBAChC2E,eAAiBJ,EAAEE,OAAOC,QAAQ1E,2BAClC4E,UAAYL,EAAEE,OAAOC,QAAQG,mBAAUC,UAAUC,QAAQC,cACzDC,YAAcV,EAAEE,OAAOC,QAAQ1E,4BACjCwE,aAAc,CACdD,EAAEW,uBACIC,QAAUlE,SAChBA,SAAW,GACXA,SAASuD,aAAapD,QAAQgE,UAAYZ,aAAapD,QAAQiE,cAC1D,MAAMD,YAAYD,QACfC,WAAaZ,aAAapD,QAAQgE,WAClCnE,SAASmE,UAAYD,QAAQC,WAGrChF,SAASO,MAAQ,EACjBkB,WAAWyD,uBAAsB,MAEjCX,eAAgB,CAChBJ,EAAEW,uBACIK,cAAgB,IAAI9B,IAAIkB,eAAea,aAAa,SACpD7E,MAAQ4E,cAAc1B,aAAa4B,IAAI,SAChB,OAAzBF,cAAcG,SACdtF,SAASO,MAAQA,MACjBkB,WAAWyD,uBAAsB,IAGrCV,WA5Ce,YACbe,YAAcjC,SAASgC,OACvBE,UAAY,IAAIC,gBAAgBF,gBAClCC,UAAUE,IAAI,QAAS,OACjBC,WAAa,IAAItC,IAAIC,SAASC,KAAKqC,QAAQtC,SAASgC,OAAQ,KAClEK,WAAWlC,aAAaC,IAAI,OAAQ8B,UAAUH,IAAI,SAClD1B,QAAQC,UAAU,GAAI,GAAI+B,eAG1BH,UAAUE,IAAI,YAAa,OACrBC,WAAa,IAAItC,IAAIC,SAASC,KAAKqC,QAAQtC,SAASgC,OAAQ,KAClEK,WAAWlC,aAAaC,IAAI,WAAY8B,UAAUH,IAAI,aACtD1B,QAAQC,UAAU,GAAI,GAAI+B,cAiC1BE,GAEAhB,cAEAV,EAAEW,iBAGyC,IAAvCgB,OAAOjB,YAAY7D,QAAQ+E,SAC3B/F,SAASQ,SAAWb,YAAYqG,YAChCnB,YAAY7D,QAAQ+E,OAAS,EAC7BlB,YAAYoB,UAAY5E,kBAExBrB,SAASQ,SAAWb,YAAY6B,gBAChCqD,YAAY7D,QAAQ+E,OAAS,EAC7BlB,YAAYoB,UAAY7E,aAE5BpB,SAASO,MAAQ,EACjBkB,WAAWyD,gCAMf5E,SAAW,QACXZ,SAASW,SAET4D,eAAiBvE,SAASW,OACtBX,SAASY,WACTA,SAAWZ,SAASY,WAIkB,IAA1CyB,OAAOmE,QAAQjC,gBAAgBhC,OAAc,OAEvCkE,eAAiBtG,UAAUE,cAAc0E,mBAAUC,UAAU0B,QAAQD,gBACvEA,gBACAA,eAAeE,aAIfC,SAAW,MACV,MAAMC,aAAatC,eAAgB,IAClB,aAAdsC,UAA0B,CAC1BjG,SAAW2D,eAAesC,oBAI9BD,UAAY,QACN3E,WAAa,CACf6E,WAAYD,UACZE,OAASxC,eAAesC,WAAWE,OACnCnG,SAAU2D,eAAesC,WAAWjG,SACpCoG,cAAezC,eAAesC,WAAWG,cACzCC,OAAQL,UAEZ7E,WAAWmF,aAAajF,YAE5BF,WAAW5B,UAAUmB,QAAQc,WAAaxB,eAIpCuG,KAAOpF,WAAW5B,UAAUE,cAAc0E,mBAAUC,UAAUoC,OAAOD,MAC3EA,KAAKE,8CAAuCzG,iBAAe0G,SAASC,QAAWA,OAAOZ,WACtFQ,KAAKK,UAAW"}PK B\梏? ? build/question_engine.min.js.mapnu [ {"version":3,"file":"question_engine.min.js","sources":["../src/question_engine.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * JavaScript required by the question engine.\n *\n * @module core_question/question_engine\n * @copyright 2021 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as scrollManager from 'core/scroll_manager';\nimport * as formSubmit from 'core_form/submit';\n\n/**\n * Initialise a question submit button. This saves the scroll position and\n * sets the fragment on the form submit URL so the page reloads in the right place.\n *\n * @param {string} button the id of the button in the HTML.\n */\nexport const initSubmitButton = button => {\n formSubmit.init(button);\n scrollManager.watchScrollButtonSaves();\n};\n\n/**\n * Initialise a form that contains questions printed using print_question.\n * This has the effect of:\n * 1. Turning off browser autocomlete.\n * 2. Stopping enter from submitting the form (or toggling the next flag) unless\n * keyboard focus is on the submit button or the flag.\n * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle\n * the flags using ajax.\n * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.\n * 5. Prevent the user from repeatedly submitting the form.\n *\n * @param {string} formSelector Selector to identify the form.\n */\nexport const initForm = (formSelector) => {\n const form = document.querySelector(formSelector);\n form.setAttribute('autocomplete', 'off');\n\n form.addEventListener('submit', preventRepeatSubmission);\n\n form.addEventListener('key', (event) => {\n if (event.keyCode !== 13) {\n return;\n }\n\n if (event.target.matches('a')) {\n return;\n }\n\n if (event.target.matches('input[type=\"submit\"]')) {\n return;\n }\n\n if (event.target.matches('input[type=img]')) {\n return;\n }\n\n if (event.target.matches('textarea') || event.target.matches('[contenteditable=true]')) {\n return;\n }\n\n event.preventDefault();\n });\n\n const questionFlagSaveButtons = form.querySelectorAll('.questionflagsavebutton');\n [...questionFlagSaveButtons].forEach((node) => node.remove());\n\n // Note: The scrollToSavedPosition function tries to wait until the content has loaded before firing.\n scrollManager.scrollToSavedPosition();\n};\n\n/**\n * Event handler to stop a question form being submitted more than once.\n *\n * @param {object} event the form submit event.\n */\nexport const preventRepeatSubmission = (event) => {\n const form = event.target.closest('form');\n if (form.dataset.formSubmitted === '1') {\n event.preventDefault();\n return;\n }\n\n setTimeout(() => {\n [...form.querySelectorAll('input[type=submit]')].forEach((input) => input.setAttribute('disabled', true));\n });\n form.dataset.formSubmitted = '1';\n};\n"],"names":["button","formSubmit","init","scrollManager","watchScrollButtonSaves","formSelector","form","document","querySelector","setAttribute","addEventListener","preventRepeatSubmission","event","keyCode","target","matches","preventDefault","querySelectorAll","forEach","node","remove","scrollToSavedPosition","closest","dataset","formSubmitted","setTimeout","input"],"mappings":";;;;;;;+QAgCgCA,SAC5BC,WAAWC,KAAKF,QAChBG,cAAcC,4CAgBOC,qBACfC,KAAOC,SAASC,cAAcH,cACpCC,KAAKG,aAAa,eAAgB,OAElCH,KAAKI,iBAAiB,SAAUC,yBAEhCL,KAAKI,iBAAiB,OAAQE,QACJ,KAAlBA,MAAMC,UAIND,MAAME,OAAOC,QAAQ,MAIrBH,MAAME,OAAOC,QAAQ,yBAIrBH,MAAME,OAAOC,QAAQ,oBAIrBH,MAAME,OAAOC,QAAQ,aAAeH,MAAME,OAAOC,QAAQ,2BAI7DH,MAAMI,yBAGsBV,KAAKW,iBAAiB,4BACzBC,SAASC,MAASA,KAAKC,WAGpDjB,cAAckB,+BAQLV,wBAA2BC,cAC9BN,KAAOM,MAAME,OAAOQ,QAAQ,QACC,MAA/BhB,KAAKiB,QAAQC,eAKjBC,YAAW,SACHnB,KAAKW,iBAAiB,uBAAuBC,SAASQ,OAAUA,MAAMjB,aAAa,YAAY,QAEvGH,KAAKiB,QAAQC,cAAgB,KAPzBZ,MAAMI"}PK B\q] ] &