From 80a111d25e423fdac2e6dce0a800fe38ae5aa705 Mon Sep 17 00:00:00 2001 From: Ruslan Kabalin Date: Wed, 24 May 2023 22:07:05 +0100 Subject: [PATCH] MDL-74301 eslint: Address issues reported by eslint --- .../local/courseeditor/fileuploader.min.js | 2 +- .../courseeditor/fileuploader.min.js.map | 2 +- .../amd/build/local/courseindex/cm.min.js | 2 +- .../amd/build/local/courseindex/cm.min.js.map | 2 +- .../src/local/courseeditor/fileuploader.js | 19 +++++-------------- course/format/amd/src/local/courseindex/cm.js | 8 ++------ lib/amd/build/local/reactive/overlay.min.js | 2 +- .../build/local/reactive/overlay.min.js.map | 2 +- lib/amd/build/sortable_list.min.js.map | 2 +- lib/amd/src/local/reactive/overlay.js | 15 +++++---------- lib/amd/src/sortable_list.js | 1 + .../amd/build/plugin.min.js.map | 2 +- .../accessibilitychecker/amd/src/plugin.js | 1 + .../autosave/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/autosave/amd/src/plugin.js | 1 + .../equation/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/equation/amd/src/plugin.js | 1 + .../plugins/h5p/amd/build/plugin.min.js.map | 2 +- lib/editor/tiny/plugins/h5p/amd/src/plugin.js | 1 + .../plugins/link/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/link/amd/src/plugin.js | 1 + .../plugins/media/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/media/amd/src/plugin.js | 1 + .../recordrtc/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/recordrtc/amd/src/plugin.js | 1 + ...dle-assignfeedback_editpdf-editor-debug.js | 1 + .../moodle-assignfeedback_editpdf-editor.js | 1 + .../editpdf/yui/src/editor/js/editor.js | 1 + 28 files changed, 37 insertions(+), 44 deletions(-) diff --git a/course/format/amd/build/local/courseeditor/fileuploader.min.js b/course/format/amd/build/local/courseeditor/fileuploader.min.js index e74631786a9..308e86a85c1 100644 --- a/course/format/amd/build/local/courseeditor/fileuploader.min.js +++ b/course/format/amd/build/local/courseeditor/fileuploader.min.js @@ -1,3 +1,3 @@ -define("core_courseformat/local/courseeditor/fileuploader",["exports","core/config","core/modal_factory","core/modal_events","core/templates","core/normalise","core/prefetch","core/str","core_courseformat/courseeditor","core/process_monitor","core/utils"],(function(_exports,_config,_modal_factory,_modal_events,_templates,_normalise,_prefetch,_str,_courseeditor,_process_monitor,_utils){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}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.uploadFilesToCourse=void 0,_config=_interopRequireDefault(_config),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates);const UPLOADURL=_config.default.wwwroot+"/course/dndupload.php";let uploadQueue=null,handlerManagers={},courseUpdates=new Map,errors=null;(0,_prefetch.prefetchStrings)("moodle",["addresourceoractivity","upload"]),(0,_prefetch.prefetchStrings)("core_error",["dndmaxbytes","dndread","dndupload","dndunkownfile"]);class FileUploader{constructor(courseId,sectionId,sectionNum,fileInfo,handler){this.courseId=courseId,this.sectionId=sectionId,this.sectionNum=sectionNum,this.fileInfo=fileInfo,this.handler=handler}execute(process){const fileInfo=this.fileInfo,xhr=this._createXhrRequest(process),formData=this._createUploadFormData(),reader=new FileReader;reader.onload=function(){xhr.open("POST",UPLOADURL,!0),xhr.send(formData)},reader.onerror=function(){process.setError(errors.dndread)},fileInfo.size>0?reader.readAsText(fileInfo.slice(0,5)):reader.readAsText(fileInfo)}getExecutionFunction(){return this.execute.bind(this)}_createXhrRequest(process){const xhr=new XMLHttpRequest;return xhr.upload.addEventListener("progress",(event=>{if(event.lengthComputable){const percent=Math.round(100*event.loaded/event.total);process.setPercentage(percent)}}),!1),xhr.onreadystatechange=()=>{if(1==xhr.readyState&&process.setPercentage(1),4==xhr.readyState)if(200==xhr.status){var result=JSON.parse(xhr.responseText);result&&0==result.error?this._finishProcess(process):process.setError(result.error)}else process.setError(errors.dndupload)},xhr}_createUploadFormData(){const formData=new FormData;try{formData.append("repo_upload_file",this.fileInfo)}catch(error){throw Error(error.dndread)}return formData.append("sesskey",_config.default.sesskey),formData.append("course",this.courseId),formData.append("section",this.sectionNum),formData.append("module",this.handler.module),formData.append("type","Files"),formData}_finishProcess(process){!function(courseId,sectionId){let refresh=courseUpdates.get(courseId);refresh||(refresh=new Set);refresh.add(sectionId),courseUpdates.set(courseId,refresh),refreshCourseEditors()}(this.courseId,this.sectionId),process.setPercentage(100),process.finish()}}class HandlerManager{constructor(courseId){var _this$courseEditor$ge,_this$courseEditor$ge2;if(_defineProperty(this,"lastHandlers",{}),_defineProperty(this,"allHandlers",null),this.courseId=courseId,this.lastUploadId=0,this.courseEditor=(0,_courseeditor.getCourseEditor)(courseId),!this.courseEditor)throw Error("Unkown course editor");this.maxbytes=null!==(_this$courseEditor$ge=null===(_this$courseEditor$ge2=this.courseEditor.get("course"))||void 0===_this$courseEditor$ge2?void 0:_this$courseEditor$ge2.maxbytes)&&void 0!==_this$courseEditor$ge?_this$courseEditor$ge:0}async loadHandlers(){this.allHandlers=await this.courseEditor.getFileHandlersPromise()}getFileExtension(fileInfo){let extension="";const dotpos=fileInfo.name.lastIndexOf(".");return-1!=dotpos&&(extension=fileInfo.name.substring(dotpos+1,fileInfo.name.length).toLowerCase()),extension}validateFile(fileInfo){if(-1!==this.maxbytes&&fileInfo.size>this.maxbytes)throw Error(errors.dndmaxbytes)}filterHandlers(fileInfo){const extension=this.getFileExtension(fileInfo);return this.allHandlers.filter((handler=>"*"==handler.extension||handler.extension==extension))}async getFileHandler(fileInfo){const fileHandlers=this.filterHandlers(fileInfo);if(0==fileHandlers.length)throw Error(errors.dndunkownfile);let fileHandler=null;return fileHandler=1==fileHandlers.length?fileHandlers[0]:await this.askHandlerToUser(fileHandlers,fileInfo),fileHandler}async askHandlerToUser(fileHandlers,fileInfo){var _this$lastHandlers$ex;const extension=this.getFileExtension(fileInfo),modalParams={title:(0,_str.get_string)("addresourceoractivity","moodle"),body:_templates.default.render("core_courseformat/fileuploader",this.getModalData(fileHandlers,fileInfo,null!==(_this$lastHandlers$ex=this.lastHandlers[extension])&&void 0!==_this$lastHandlers$ex?_this$lastHandlers$ex:null)),type:_modal_factory.default.types.SAVE_CANCEL,saveButtonText:(0,_str.get_string)("upload","moodle")},modal=await this.modalBodyRenderedPromise(modalParams),selectedHandler=await this.modalUserAnswerPromise(modal,fileHandlers);return null===selectedHandler?null:(this.lastHandlers[extension]=selectedHandler.module,selectedHandler)}getModalData(fileHandlers,fileInfo,defaultModule){const data={filename:fileInfo.name,uploadid:++this.lastUploadId,handlers:[]};let hasDefault=!1;if(fileHandlers.forEach(((handler,index)=>{const isDefault=defaultModule==handler.module;data.handlers.push({...handler,selected:isDefault,labelid:"fileuploader_".concat(data.uploadid),value:index}),hasDefault=hasDefault||isDefault})),!hasDefault&&data.handlers.length>0){const lastHandler=data.handlers.pop();lastHandler.selected=!0,data.handlers.push(lastHandler)}return data}modalUserAnswerPromise(modal,fileHandlers){const modalBody=(0,_normalise.getFirst)(modal.getBody());return new Promise(((resolve,reject)=>{modal.getRoot().on(_modal_events.default.save,(event=>{const index=modalBody.querySelector("input:checked").value;event.preventDefault(),modal.destroy(),fileHandlers[index]||reject("Invalid handler selected"),resolve(fileHandlers[index])})),modal.getRoot().on(_modal_events.default.cancel,(()=>{resolve(null)}))}))}modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}}const refreshCourseEditors=(0,_utils.debounce)((()=>{const refreshes=courseUpdates;courseUpdates=new Map,refreshes.forEach(((sectionIds,courseId)=>{const courseEditor=(0,_courseeditor.getCourseEditor)(courseId);courseEditor&&courseEditor.dispatch("sectionState",[...sectionIds])}))}),500);const queueFileUpload=async function(courseId,sectionId,sectionNum,fileInfo,handlerManager){let handler;uploadQueue=await _process_monitor.processMonitor.createProcessQueue();try{handlerManager.validateFile(fileInfo),handler=await handlerManager.getFileHandler(fileInfo)}catch(error){return void uploadQueue.addError(fileInfo.name,error.message)}if(!handler)return;const fileProcessor=new FileUploader(courseId,sectionId,sectionNum,fileInfo,handler);uploadQueue.addPending(fileInfo.name,fileProcessor.getExecutionFunction())};_exports.uploadFilesToCourse=async function(courseId,sectionId,sectionNum,files){let handlerManager;try{handlerManager=await async function(courseId){if(void 0!==handlerManagers[courseId])return handlerManagers[courseId];try{const handlerManager=new HandlerManager(courseId);await handlerManager.loadHandlers(),handlerManagers[courseId]=handlerManager}catch(error){throw error}return handlerManagers[courseId]}(courseId),await async function(courseId){var _courseEditor$get$max,_courseEditor$get;if(null!==errors)return;const maxbytestext=null!==(_courseEditor$get$max=null===(_courseEditor$get=(0,_courseeditor.getCourseEditor)(courseId).get("course"))||void 0===_courseEditor$get?void 0:_courseEditor$get.maxbytestext)&&void 0!==_courseEditor$get$max?_courseEditor$get$max:"0";errors={};const allStrings=[{key:"dndmaxbytes",component:"core_error",param:{size:maxbytestext}},{key:"dndread",component:"core_error"},{key:"dndupload",component:"core_error"},{key:"dndunkownfile",component:"core_error"}];window.console.log(allStrings);const loadedStrings=await(0,_str.get_strings)(allStrings);allStrings.forEach(((_ref,index)=>{let{key:key}=_ref;errors[key]=loadedStrings[index]}))}(courseId)}catch(error){throw error}for(let index=0;index0?reader.readAsText(fileInfo.slice(0,5)):reader.readAsText(fileInfo)}getExecutionFunction(){return this.execute.bind(this)}_createXhrRequest(process){const xhr=new XMLHttpRequest;return xhr.upload.addEventListener("progress",(event=>{if(event.lengthComputable){const percent=Math.round(100*event.loaded/event.total);process.setPercentage(percent)}}),!1),xhr.onreadystatechange=()=>{if(1==xhr.readyState&&process.setPercentage(1),4==xhr.readyState)if(200==xhr.status){var result=JSON.parse(xhr.responseText);result&&0==result.error?this._finishProcess(process):process.setError(result.error)}else process.setError(errors.dndupload)},xhr}_createUploadFormData(){const formData=new FormData;try{formData.append("repo_upload_file",this.fileInfo)}catch(error){throw Error(error.dndread)}return formData.append("sesskey",_config.default.sesskey),formData.append("course",this.courseId),formData.append("section",this.sectionNum),formData.append("module",this.handler.module),formData.append("type","Files"),formData}_finishProcess(process){!function(courseId,sectionId){let refresh=courseUpdates.get(courseId);refresh||(refresh=new Set);refresh.add(sectionId),courseUpdates.set(courseId,refresh),refreshCourseEditors()}(this.courseId,this.sectionId),process.setPercentage(100),process.finish()}}class HandlerManager{constructor(courseId){var _this$courseEditor$ge,_this$courseEditor$ge2;if(_defineProperty(this,"lastHandlers",{}),_defineProperty(this,"allHandlers",null),this.courseId=courseId,this.lastUploadId=0,this.courseEditor=(0,_courseeditor.getCourseEditor)(courseId),!this.courseEditor)throw Error("Unkown course editor");this.maxbytes=null!==(_this$courseEditor$ge=null===(_this$courseEditor$ge2=this.courseEditor.get("course"))||void 0===_this$courseEditor$ge2?void 0:_this$courseEditor$ge2.maxbytes)&&void 0!==_this$courseEditor$ge?_this$courseEditor$ge:0}async loadHandlers(){this.allHandlers=await this.courseEditor.getFileHandlersPromise()}getFileExtension(fileInfo){let extension="";const dotpos=fileInfo.name.lastIndexOf(".");return-1!=dotpos&&(extension=fileInfo.name.substring(dotpos+1,fileInfo.name.length).toLowerCase()),extension}validateFile(fileInfo){if(-1!==this.maxbytes&&fileInfo.size>this.maxbytes)throw Error(errors.dndmaxbytes)}filterHandlers(fileInfo){const extension=this.getFileExtension(fileInfo);return this.allHandlers.filter((handler=>"*"==handler.extension||handler.extension==extension))}async getFileHandler(fileInfo){const fileHandlers=this.filterHandlers(fileInfo);if(0==fileHandlers.length)throw Error(errors.dndunkownfile);let fileHandler=null;return fileHandler=1==fileHandlers.length?fileHandlers[0]:await this.askHandlerToUser(fileHandlers,fileInfo),fileHandler}async askHandlerToUser(fileHandlers,fileInfo){var _this$lastHandlers$ex;const extension=this.getFileExtension(fileInfo),modalParams={title:(0,_str.get_string)("addresourceoractivity","moodle"),body:_templates.default.render("core_courseformat/fileuploader",this.getModalData(fileHandlers,fileInfo,null!==(_this$lastHandlers$ex=this.lastHandlers[extension])&&void 0!==_this$lastHandlers$ex?_this$lastHandlers$ex:null)),type:_modal_factory.default.types.SAVE_CANCEL,saveButtonText:(0,_str.get_string)("upload","moodle")},modal=await this.modalBodyRenderedPromise(modalParams),selectedHandler=await this.modalUserAnswerPromise(modal,fileHandlers);return null===selectedHandler?null:(this.lastHandlers[extension]=selectedHandler.module,selectedHandler)}getModalData(fileHandlers,fileInfo,defaultModule){const data={filename:fileInfo.name,uploadid:++this.lastUploadId,handlers:[]};let hasDefault=!1;if(fileHandlers.forEach(((handler,index)=>{const isDefault=defaultModule==handler.module;data.handlers.push({...handler,selected:isDefault,labelid:"fileuploader_".concat(data.uploadid),value:index}),hasDefault=hasDefault||isDefault})),!hasDefault&&data.handlers.length>0){const lastHandler=data.handlers.pop();lastHandler.selected=!0,data.handlers.push(lastHandler)}return data}modalUserAnswerPromise(modal,fileHandlers){const modalBody=(0,_normalise.getFirst)(modal.getBody());return new Promise(((resolve,reject)=>{modal.getRoot().on(_modal_events.default.save,(event=>{const index=modalBody.querySelector("input:checked").value;event.preventDefault(),modal.destroy(),fileHandlers[index]||reject("Invalid handler selected"),resolve(fileHandlers[index])})),modal.getRoot().on(_modal_events.default.cancel,(()=>{resolve(null)}))}))}modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}}const refreshCourseEditors=(0,_utils.debounce)((()=>{const refreshes=courseUpdates;courseUpdates=new Map,refreshes.forEach(((sectionIds,courseId)=>{const courseEditor=(0,_courseeditor.getCourseEditor)(courseId);courseEditor&&courseEditor.dispatch("sectionState",[...sectionIds])}))}),500);const queueFileUpload=async function(courseId,sectionId,sectionNum,fileInfo,handlerManager){let handler;uploadQueue=await _process_monitor.processMonitor.createProcessQueue();try{handlerManager.validateFile(fileInfo),handler=await handlerManager.getFileHandler(fileInfo)}catch(error){return void uploadQueue.addError(fileInfo.name,error.message)}if(!handler)return;const fileProcessor=new FileUploader(courseId,sectionId,sectionNum,fileInfo,handler);uploadQueue.addPending(fileInfo.name,fileProcessor.getExecutionFunction())};_exports.uploadFilesToCourse=async function(courseId,sectionId,sectionNum,files){const handlerManager=await async function(courseId){if(void 0!==handlerManagers[courseId])return handlerManagers[courseId];const handlerManager=new HandlerManager(courseId);return await handlerManager.loadHandlers(),handlerManagers[courseId]=handlerManager,handlerManagers[courseId]}(courseId);await async function(courseId){var _courseEditor$get$max,_courseEditor$get;if(null!==errors)return;const maxbytestext=null!==(_courseEditor$get$max=null===(_courseEditor$get=(0,_courseeditor.getCourseEditor)(courseId).get("course"))||void 0===_courseEditor$get?void 0:_courseEditor$get.maxbytestext)&&void 0!==_courseEditor$get$max?_courseEditor$get$max:"0";errors={};const allStrings=[{key:"dndmaxbytes",component:"core_error",param:{size:maxbytestext}},{key:"dndread",component:"core_error"},{key:"dndupload",component:"core_error"},{key:"dndunkownfile",component:"core_error"}];window.console.log(allStrings);const loadedStrings=await(0,_str.get_strings)(allStrings);allStrings.forEach(((_ref,index)=>{let{key:key}=_ref;errors[key]=loadedStrings[index]}))}(courseId);for(let index=0;index.\n\n/**\n * The course file uploader.\n *\n * This module is used to upload files directly into the course.\n *\n * @module core_courseformat/local/courseeditor/fileuploader\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * @typedef {Object} Handler\n * @property {String} extension the handled extension or * for any\n * @property {String} message the handler message\n * @property {String} module the module name\n */\n\nimport Config from 'core/config';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {getFirst} from 'core/normalise';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {getCourseEditor} from 'core_courseformat/courseeditor';\nimport {processMonitor} from 'core/process_monitor';\nimport {debounce} from 'core/utils';\n\n// Uploading url.\nconst UPLOADURL = Config.wwwroot + '/course/dndupload.php';\nconst DEBOUNCETIMER = 500;\nconst USERCANIGNOREFILESIZELIMITS = -1;\n\n/** @var {ProcessQueue} uploadQueue the internal uploadQueue instance. */\nlet uploadQueue = null;\n/** @var {Object} handlerManagers the courseId indexed loaded handler managers. */\nlet handlerManagers = {};\n/** @var {Map} courseUpdates the pending course sections updates. */\nlet courseUpdates = new Map();\n/** @var {Object} errors the error messages. */\nlet errors = null;\n\n// Load global strings.\nprefetchStrings('moodle', ['addresourceoractivity', 'upload']);\nprefetchStrings('core_error', ['dndmaxbytes', 'dndread', 'dndupload', 'dndunkownfile']);\n\n/**\n * Class to upload a file into the course.\n * @private\n */\nclass FileUploader {\n /**\n * Class constructor.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the section id\n * @param {number} sectionNum the section number\n * @param {File} fileInfo the file information object\n * @param {Handler} handler the file selected file handler\n */\n constructor(courseId, sectionId, sectionNum, fileInfo, handler) {\n this.courseId = courseId;\n this.sectionId = sectionId;\n this.sectionNum = sectionNum;\n this.fileInfo = fileInfo;\n this.handler = handler;\n }\n\n /**\n * Execute the file upload and update the state in the given process.\n *\n * @param {LoadingProcess} process the process to store the upload result\n */\n execute(process) {\n const fileInfo = this.fileInfo;\n const xhr = this._createXhrRequest(process);\n const formData = this._createUploadFormData();\n\n // Try reading the file to check it is not a folder, before sending it to the server.\n const reader = new FileReader();\n reader.onload = function() {\n // File was read OK - send it to the server.\n xhr.open(\"POST\", UPLOADURL, true);\n xhr.send(formData);\n };\n reader.onerror = function() {\n // Unable to read the file (it is probably a folder) - display an error message.\n process.setError(errors.dndread);\n };\n if (fileInfo.size > 0) {\n // If this is a non-empty file, try reading the first few bytes.\n // This will trigger reader.onerror() for folders and reader.onload() for ordinary, readable files.\n reader.readAsText(fileInfo.slice(0, 5));\n } else {\n // If you call slice() on a 0-byte folder, before calling readAsText, then Firefox triggers reader.onload(),\n // instead of reader.onerror().\n // So, for 0-byte files, just call readAsText on the whole file (and it will trigger load/error functions as expected).\n reader.readAsText(fileInfo);\n }\n }\n\n /**\n * Returns the bind version of execute function.\n *\n * This method is used to queue the process into a ProcessQueue instance.\n *\n * @returns {Function} the bind function to execute the process\n */\n getExecutionFunction() {\n return this.execute.bind(this);\n }\n\n /**\n * Generate a upload XHR file request.\n *\n * @param {LoadingProcess} process the current process\n * @return {XMLHttpRequest} the XHR request\n */\n _createXhrRequest(process) {\n const xhr = new XMLHttpRequest();\n // Update the progress bar as the file is uploaded.\n xhr.upload.addEventListener(\n 'progress',\n (event) => {\n if (event.lengthComputable) {\n const percent = Math.round((event.loaded * 100) / event.total);\n process.setPercentage(percent);\n }\n },\n false\n );\n // Wait for the AJAX call to complete.\n xhr.onreadystatechange = () => {\n if (xhr.readyState == 1) {\n // Add a 1% just to indicate that it is uploading.\n process.setPercentage(1);\n }\n // State 4 is DONE. Otherwise the connection is still ongoing.\n if (xhr.readyState != 4) {\n return;\n }\n if (xhr.status == 200) {\n var result = JSON.parse(xhr.responseText);\n if (result && result.error == 0) {\n // All OK.\n this._finishProcess(process);\n } else {\n process.setError(result.error);\n }\n } else {\n process.setError(errors.dndupload);\n }\n };\n return xhr;\n }\n\n /**\n * Upload a file into the course.\n *\n * @return {FormData|null} the new form data object\n */\n _createUploadFormData() {\n const formData = new FormData();\n try {\n formData.append('repo_upload_file', this.fileInfo);\n } catch (error) {\n throw Error(error.dndread);\n }\n formData.append('sesskey', Config.sesskey);\n formData.append('course', this.courseId);\n formData.append('section', this.sectionNum);\n formData.append('module', this.handler.module);\n formData.append('type', 'Files');\n return formData;\n }\n\n /**\n * Finishes the current process.\n * @param {LoadingProcess} process the process\n */\n _finishProcess(process) {\n addRefreshSection(this.courseId, this.sectionId);\n process.setPercentage(100);\n process.finish();\n }\n}\n\n/**\n * The file handler manager class.\n *\n * @private\n */\nclass HandlerManager {\n\n /** @var {Object} lastHandlers the last handlers selected per each file extension. */\n lastHandlers = {};\n\n /** @var {Handler[]|null} allHandlers all the available handlers. */\n allHandlers = null;\n\n /**\n * Class constructor.\n *\n * @param {Number} courseId\n */\n constructor(courseId) {\n this.courseId = courseId;\n this.lastUploadId = 0;\n this.courseEditor = getCourseEditor(courseId);\n if (!this.courseEditor) {\n throw Error('Unkown course editor');\n }\n this.maxbytes = this.courseEditor.get('course')?.maxbytes ?? 0;\n }\n\n /**\n * Load the course file handlers.\n */\n async loadHandlers() {\n this.allHandlers = await this.courseEditor.getFileHandlersPromise();\n }\n\n /**\n * Extract the file extension from a fileInfo.\n *\n * @param {File} fileInfo\n * @returns {String} the file extension or an empty string.\n */\n getFileExtension(fileInfo) {\n let extension = '';\n const dotpos = fileInfo.name.lastIndexOf('.');\n if (dotpos != -1) {\n extension = fileInfo.name.substring(dotpos + 1, fileInfo.name.length).toLowerCase();\n }\n return extension;\n }\n\n /**\n * Check if the file is valid.\n *\n * @param {File} fileInfo the file info\n */\n validateFile(fileInfo) {\n if (this.maxbytes !== USERCANIGNOREFILESIZELIMITS && fileInfo.size > this.maxbytes) {\n throw Error(errors.dndmaxbytes);\n }\n }\n\n /**\n * Get the file handlers of an specific file.\n *\n * @param {File} fileInfo the file indo\n * @return {Array} Array of handlers\n */\n filterHandlers(fileInfo) {\n const extension = this.getFileExtension(fileInfo);\n return this.allHandlers.filter(handler => handler.extension == '*' || handler.extension == extension);\n }\n\n /**\n * Get the Handler to upload a specific file.\n *\n * It will ask the used if more than one handler is available.\n *\n * @param {File} fileInfo the file info\n * @returns {Promise} the selected handler or null if the user cancel\n */\n async getFileHandler(fileInfo) {\n const fileHandlers = this.filterHandlers(fileInfo);\n if (fileHandlers.length == 0) {\n throw Error(errors.dndunkownfile);\n }\n let fileHandler = null;\n if (fileHandlers.length == 1) {\n fileHandler = fileHandlers[0];\n } else {\n fileHandler = await this.askHandlerToUser(fileHandlers, fileInfo);\n }\n return fileHandler;\n }\n\n /**\n * Ask the user to select a specific handler.\n *\n * @param {Handler[]} fileHandlers\n * @param {File} fileInfo the file info\n * @return {Promise} the selected handler\n */\n async askHandlerToUser(fileHandlers, fileInfo) {\n const extension = this.getFileExtension(fileInfo);\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('addresourceoractivity', 'moodle'),\n body: Templates.render(\n 'core_courseformat/fileuploader',\n this.getModalData(\n fileHandlers,\n fileInfo,\n this.lastHandlers[extension] ?? null\n )\n ),\n type: ModalFactory.types.SAVE_CANCEL,\n saveButtonText: getString('upload', 'moodle'),\n };\n // Create the modal.\n const modal = await this.modalBodyRenderedPromise(modalParams);\n const selectedHandler = await this.modalUserAnswerPromise(modal, fileHandlers);\n // Cancel action.\n if (selectedHandler === null) {\n return null;\n }\n // Save last selected handler.\n this.lastHandlers[extension] = selectedHandler.module;\n return selectedHandler;\n }\n\n /**\n * Generated the modal template data.\n *\n * @param {Handler[]} fileHandlers\n * @param {File} fileInfo the file info\n * @param {String|null} defaultModule the default module if any\n * @return {Object} the modal template data.\n */\n getModalData(fileHandlers, fileInfo, defaultModule) {\n const data = {\n filename: fileInfo.name,\n uploadid: ++this.lastUploadId,\n handlers: [],\n };\n let hasDefault = false;\n fileHandlers.forEach((handler, index) => {\n const isDefault = (defaultModule == handler.module);\n data.handlers.push({\n ...handler,\n selected: isDefault,\n labelid: `fileuploader_${data.uploadid}`,\n value: index,\n });\n hasDefault = hasDefault || isDefault;\n });\n if (!hasDefault && data.handlers.length > 0) {\n const lastHandler = data.handlers.pop();\n lastHandler.selected = true;\n data.handlers.push(lastHandler);\n }\n return data;\n }\n\n /**\n * Get the user handler choice.\n *\n * Wait for the user answer in the modal and resolve with the selected index.\n *\n * @param {Modal} modal the modal instance\n * @param {Handler[]} fileHandlers the availabvle file handlers\n * @return {Promise} with the option selected by the user.\n */\n modalUserAnswerPromise(modal, fileHandlers) {\n const modalBody = getFirst(modal.getBody());\n return new Promise((resolve, reject) => {\n modal.getRoot().on(\n ModalEvents.save,\n event => {\n // Get the selected option.\n const index = modalBody.querySelector('input:checked').value;\n event.preventDefault();\n modal.destroy();\n if (!fileHandlers[index]) {\n reject('Invalid handler selected');\n }\n resolve(fileHandlers[index]);\n\n }\n );\n modal.getRoot().on(\n ModalEvents.cancel,\n () => {\n resolve(null);\n }\n );\n });\n }\n\n /**\n * Create a new modal and return a Promise to the body rendered.\n *\n * @param {Object} modalParams the modal params\n * @returns {Promise} the modal body rendered promise\n */\n modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n}\n\n/**\n * Add a section to refresh.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the seciton id\n */\nfunction addRefreshSection(courseId, sectionId) {\n let refresh = courseUpdates.get(courseId);\n if (!refresh) {\n refresh = new Set();\n }\n refresh.add(sectionId);\n courseUpdates.set(courseId, refresh);\n refreshCourseEditors();\n}\n\n/**\n * Debounced processing all pending course refreshes.\n * @private\n */\nconst refreshCourseEditors = debounce(\n () => {\n const refreshes = courseUpdates;\n courseUpdates = new Map();\n refreshes.forEach((sectionIds, courseId) => {\n const courseEditor = getCourseEditor(courseId);\n if (!courseEditor) {\n return;\n }\n courseEditor.dispatch('sectionState', [...sectionIds]);\n });\n },\n DEBOUNCETIMER\n);\n\n/**\n * Load and return the course handler manager instance.\n *\n * @param {Number} courseId the course Id to load\n * @returns {Promise} promise of the the loaded handleManager\n */\nasync function loadCourseHandlerManager(courseId) {\n if (handlerManagers[courseId] !== undefined) {\n return handlerManagers[courseId];\n }\n try {\n const handlerManager = new HandlerManager(courseId);\n await handlerManager.loadHandlers();\n handlerManagers[courseId] = handlerManager;\n } catch (error) {\n throw error;\n }\n return handlerManagers[courseId];\n}\n\n/**\n * Load all the erros messages at once in the module \"errors\" variable.\n * @param {Number} courseId the course id\n */\nasync function loadErrorStrings(courseId) {\n if (errors !== null) {\n return;\n }\n const courseEditor = getCourseEditor(courseId);\n const maxbytestext = courseEditor.get('course')?.maxbytestext ?? '0';\n\n errors = {};\n const allStrings = [\n {key: 'dndmaxbytes', component: 'core_error', param: {size: maxbytestext}},\n {key: 'dndread', component: 'core_error'},\n {key: 'dndupload', component: 'core_error'},\n {key: 'dndunkownfile', component: 'core_error'},\n ];\n window.console.log(allStrings);\n const loadedStrings = await getStrings(allStrings);\n allStrings.forEach(({key}, index) => {\n errors[key] = loadedStrings[index];\n });\n}\n\n/**\n * Start a batch file uploading into the course.\n *\n * @private\n * @param {number} courseId the course id.\n * @param {number} sectionId the section id.\n * @param {number} sectionNum the section number.\n * @param {File} fileInfo the file information object\n * @param {HandlerManager} handlerManager the course handler manager\n */\nconst queueFileUpload = async function(courseId, sectionId, sectionNum, fileInfo, handlerManager) {\n let handler;\n uploadQueue = await processMonitor.createProcessQueue();\n try {\n handlerManager.validateFile(fileInfo);\n handler = await handlerManager.getFileHandler(fileInfo);\n } catch (error) {\n uploadQueue.addError(fileInfo.name, error.message);\n return;\n }\n // If we don't have a handler means the user cancel the upload.\n if (!handler) {\n return;\n }\n const fileProcessor = new FileUploader(courseId, sectionId, sectionNum, fileInfo, handler);\n uploadQueue.addPending(fileInfo.name, fileProcessor.getExecutionFunction());\n};\n\n/**\n * Upload a file to the course.\n *\n * This method will show any necesary modal to handle the request.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the section id\n * @param {number} sectionNum the section number\n * @param {Array} files and array of files\n */\nexport const uploadFilesToCourse = async function(courseId, sectionId, sectionNum, files) {\n // Get the course handlers.\n let handlerManager;\n try {\n handlerManager = await loadCourseHandlerManager(courseId);\n await loadErrorStrings(courseId);\n } catch (error) {\n throw error;\n }\n for (let index = 0; index < files.length; index++) {\n const fileInfo = files[index];\n await queueFileUpload(courseId, sectionId, sectionNum, fileInfo, handlerManager);\n }\n};\n"],"names":["UPLOADURL","Config","wwwroot","uploadQueue","handlerManagers","courseUpdates","Map","errors","FileUploader","constructor","courseId","sectionId","sectionNum","fileInfo","handler","execute","process","this","xhr","_createXhrRequest","formData","_createUploadFormData","reader","FileReader","onload","open","send","onerror","setError","dndread","size","readAsText","slice","getExecutionFunction","bind","XMLHttpRequest","upload","addEventListener","event","lengthComputable","percent","Math","round","loaded","total","setPercentage","onreadystatechange","readyState","status","result","JSON","parse","responseText","error","_finishProcess","dndupload","FormData","append","Error","sesskey","module","refresh","get","Set","add","set","refreshCourseEditors","addRefreshSection","finish","HandlerManager","lastUploadId","courseEditor","maxbytes","_this$courseEditor$ge2","allHandlers","getFileHandlersPromise","getFileExtension","extension","dotpos","name","lastIndexOf","substring","length","toLowerCase","validateFile","dndmaxbytes","filterHandlers","filter","fileHandlers","dndunkownfile","fileHandler","askHandlerToUser","modalParams","title","body","Templates","render","getModalData","lastHandlers","type","ModalFactory","types","SAVE_CANCEL","saveButtonText","modal","modalBodyRenderedPromise","selectedHandler","modalUserAnswerPromise","defaultModule","data","filename","uploadid","handlers","hasDefault","forEach","index","isDefault","push","selected","labelid","value","lastHandler","pop","modalBody","getBody","Promise","resolve","reject","getRoot","on","ModalEvents","save","querySelector","preventDefault","destroy","cancel","create","then","setRemoveOnClose","bodyRendered","undefined","setSaveButtonText","show","catch","refreshes","sectionIds","dispatch","queueFileUpload","async","handlerManager","processMonitor","createProcessQueue","getFileHandler","addError","message","fileProcessor","addPending","files","loadHandlers","loadCourseHandlerManager","maxbytestext","_courseEditor$get","allStrings","key","component","param","window","console","log","loadedStrings","loadErrorStrings"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"fileuploader.min.js","sources":["../../../src/local/courseeditor/fileuploader.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 course file uploader.\n *\n * This module is used to upload files directly into the course.\n *\n * @module core_courseformat/local/courseeditor/fileuploader\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * @typedef {Object} Handler\n * @property {String} extension the handled extension or * for any\n * @property {String} message the handler message\n * @property {String} module the module name\n */\n\nimport Config from 'core/config';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {getFirst} from 'core/normalise';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {getCourseEditor} from 'core_courseformat/courseeditor';\nimport {processMonitor} from 'core/process_monitor';\nimport {debounce} from 'core/utils';\n\n// Uploading url.\nconst UPLOADURL = Config.wwwroot + '/course/dndupload.php';\nconst DEBOUNCETIMER = 500;\nconst USERCANIGNOREFILESIZELIMITS = -1;\n\n/** @var {ProcessQueue} uploadQueue the internal uploadQueue instance. */\nlet uploadQueue = null;\n/** @var {Object} handlerManagers the courseId indexed loaded handler managers. */\nlet handlerManagers = {};\n/** @var {Map} courseUpdates the pending course sections updates. */\nlet courseUpdates = new Map();\n/** @var {Object} errors the error messages. */\nlet errors = null;\n\n// Load global strings.\nprefetchStrings('moodle', ['addresourceoractivity', 'upload']);\nprefetchStrings('core_error', ['dndmaxbytes', 'dndread', 'dndupload', 'dndunkownfile']);\n\n/**\n * Class to upload a file into the course.\n * @private\n */\nclass FileUploader {\n /**\n * Class constructor.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the section id\n * @param {number} sectionNum the section number\n * @param {File} fileInfo the file information object\n * @param {Handler} handler the file selected file handler\n */\n constructor(courseId, sectionId, sectionNum, fileInfo, handler) {\n this.courseId = courseId;\n this.sectionId = sectionId;\n this.sectionNum = sectionNum;\n this.fileInfo = fileInfo;\n this.handler = handler;\n }\n\n /**\n * Execute the file upload and update the state in the given process.\n *\n * @param {LoadingProcess} process the process to store the upload result\n */\n execute(process) {\n const fileInfo = this.fileInfo;\n const xhr = this._createXhrRequest(process);\n const formData = this._createUploadFormData();\n\n // Try reading the file to check it is not a folder, before sending it to the server.\n const reader = new FileReader();\n reader.onload = function() {\n // File was read OK - send it to the server.\n xhr.open(\"POST\", UPLOADURL, true);\n xhr.send(formData);\n };\n reader.onerror = function() {\n // Unable to read the file (it is probably a folder) - display an error message.\n process.setError(errors.dndread);\n };\n if (fileInfo.size > 0) {\n // If this is a non-empty file, try reading the first few bytes.\n // This will trigger reader.onerror() for folders and reader.onload() for ordinary, readable files.\n reader.readAsText(fileInfo.slice(0, 5));\n } else {\n // If you call slice() on a 0-byte folder, before calling readAsText, then Firefox triggers reader.onload(),\n // instead of reader.onerror().\n // So, for 0-byte files, just call readAsText on the whole file (and it will trigger load/error functions as expected).\n reader.readAsText(fileInfo);\n }\n }\n\n /**\n * Returns the bind version of execute function.\n *\n * This method is used to queue the process into a ProcessQueue instance.\n *\n * @returns {Function} the bind function to execute the process\n */\n getExecutionFunction() {\n return this.execute.bind(this);\n }\n\n /**\n * Generate a upload XHR file request.\n *\n * @param {LoadingProcess} process the current process\n * @return {XMLHttpRequest} the XHR request\n */\n _createXhrRequest(process) {\n const xhr = new XMLHttpRequest();\n // Update the progress bar as the file is uploaded.\n xhr.upload.addEventListener(\n 'progress',\n (event) => {\n if (event.lengthComputable) {\n const percent = Math.round((event.loaded * 100) / event.total);\n process.setPercentage(percent);\n }\n },\n false\n );\n // Wait for the AJAX call to complete.\n xhr.onreadystatechange = () => {\n if (xhr.readyState == 1) {\n // Add a 1% just to indicate that it is uploading.\n process.setPercentage(1);\n }\n // State 4 is DONE. Otherwise the connection is still ongoing.\n if (xhr.readyState != 4) {\n return;\n }\n if (xhr.status == 200) {\n var result = JSON.parse(xhr.responseText);\n if (result && result.error == 0) {\n // All OK.\n this._finishProcess(process);\n } else {\n process.setError(result.error);\n }\n } else {\n process.setError(errors.dndupload);\n }\n };\n return xhr;\n }\n\n /**\n * Upload a file into the course.\n *\n * @return {FormData|null} the new form data object\n */\n _createUploadFormData() {\n const formData = new FormData();\n try {\n formData.append('repo_upload_file', this.fileInfo);\n } catch (error) {\n throw Error(error.dndread);\n }\n formData.append('sesskey', Config.sesskey);\n formData.append('course', this.courseId);\n formData.append('section', this.sectionNum);\n formData.append('module', this.handler.module);\n formData.append('type', 'Files');\n return formData;\n }\n\n /**\n * Finishes the current process.\n * @param {LoadingProcess} process the process\n */\n _finishProcess(process) {\n addRefreshSection(this.courseId, this.sectionId);\n process.setPercentage(100);\n process.finish();\n }\n}\n\n/**\n * The file handler manager class.\n *\n * @private\n */\nclass HandlerManager {\n\n /** @var {Object} lastHandlers the last handlers selected per each file extension. */\n lastHandlers = {};\n\n /** @var {Handler[]|null} allHandlers all the available handlers. */\n allHandlers = null;\n\n /**\n * Class constructor.\n *\n * @param {Number} courseId\n */\n constructor(courseId) {\n this.courseId = courseId;\n this.lastUploadId = 0;\n this.courseEditor = getCourseEditor(courseId);\n if (!this.courseEditor) {\n throw Error('Unkown course editor');\n }\n this.maxbytes = this.courseEditor.get('course')?.maxbytes ?? 0;\n }\n\n /**\n * Load the course file handlers.\n */\n async loadHandlers() {\n this.allHandlers = await this.courseEditor.getFileHandlersPromise();\n }\n\n /**\n * Extract the file extension from a fileInfo.\n *\n * @param {File} fileInfo\n * @returns {String} the file extension or an empty string.\n */\n getFileExtension(fileInfo) {\n let extension = '';\n const dotpos = fileInfo.name.lastIndexOf('.');\n if (dotpos != -1) {\n extension = fileInfo.name.substring(dotpos + 1, fileInfo.name.length).toLowerCase();\n }\n return extension;\n }\n\n /**\n * Check if the file is valid.\n *\n * @param {File} fileInfo the file info\n */\n validateFile(fileInfo) {\n if (this.maxbytes !== USERCANIGNOREFILESIZELIMITS && fileInfo.size > this.maxbytes) {\n throw Error(errors.dndmaxbytes);\n }\n }\n\n /**\n * Get the file handlers of an specific file.\n *\n * @param {File} fileInfo the file indo\n * @return {Array} Array of handlers\n */\n filterHandlers(fileInfo) {\n const extension = this.getFileExtension(fileInfo);\n return this.allHandlers.filter(handler => handler.extension == '*' || handler.extension == extension);\n }\n\n /**\n * Get the Handler to upload a specific file.\n *\n * It will ask the used if more than one handler is available.\n *\n * @param {File} fileInfo the file info\n * @returns {Promise} the selected handler or null if the user cancel\n */\n async getFileHandler(fileInfo) {\n const fileHandlers = this.filterHandlers(fileInfo);\n if (fileHandlers.length == 0) {\n throw Error(errors.dndunkownfile);\n }\n let fileHandler = null;\n if (fileHandlers.length == 1) {\n fileHandler = fileHandlers[0];\n } else {\n fileHandler = await this.askHandlerToUser(fileHandlers, fileInfo);\n }\n return fileHandler;\n }\n\n /**\n * Ask the user to select a specific handler.\n *\n * @param {Handler[]} fileHandlers\n * @param {File} fileInfo the file info\n * @return {Promise} the selected handler\n */\n async askHandlerToUser(fileHandlers, fileInfo) {\n const extension = this.getFileExtension(fileInfo);\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('addresourceoractivity', 'moodle'),\n body: Templates.render(\n 'core_courseformat/fileuploader',\n this.getModalData(\n fileHandlers,\n fileInfo,\n this.lastHandlers[extension] ?? null\n )\n ),\n type: ModalFactory.types.SAVE_CANCEL,\n saveButtonText: getString('upload', 'moodle'),\n };\n // Create the modal.\n const modal = await this.modalBodyRenderedPromise(modalParams);\n const selectedHandler = await this.modalUserAnswerPromise(modal, fileHandlers);\n // Cancel action.\n if (selectedHandler === null) {\n return null;\n }\n // Save last selected handler.\n this.lastHandlers[extension] = selectedHandler.module;\n return selectedHandler;\n }\n\n /**\n * Generated the modal template data.\n *\n * @param {Handler[]} fileHandlers\n * @param {File} fileInfo the file info\n * @param {String|null} defaultModule the default module if any\n * @return {Object} the modal template data.\n */\n getModalData(fileHandlers, fileInfo, defaultModule) {\n const data = {\n filename: fileInfo.name,\n uploadid: ++this.lastUploadId,\n handlers: [],\n };\n let hasDefault = false;\n fileHandlers.forEach((handler, index) => {\n const isDefault = (defaultModule == handler.module);\n data.handlers.push({\n ...handler,\n selected: isDefault,\n labelid: `fileuploader_${data.uploadid}`,\n value: index,\n });\n hasDefault = hasDefault || isDefault;\n });\n if (!hasDefault && data.handlers.length > 0) {\n const lastHandler = data.handlers.pop();\n lastHandler.selected = true;\n data.handlers.push(lastHandler);\n }\n return data;\n }\n\n /**\n * Get the user handler choice.\n *\n * Wait for the user answer in the modal and resolve with the selected index.\n *\n * @param {Modal} modal the modal instance\n * @param {Handler[]} fileHandlers the availabvle file handlers\n * @return {Promise} with the option selected by the user.\n */\n modalUserAnswerPromise(modal, fileHandlers) {\n const modalBody = getFirst(modal.getBody());\n return new Promise((resolve, reject) => {\n modal.getRoot().on(\n ModalEvents.save,\n event => {\n // Get the selected option.\n const index = modalBody.querySelector('input:checked').value;\n event.preventDefault();\n modal.destroy();\n if (!fileHandlers[index]) {\n reject('Invalid handler selected');\n }\n resolve(fileHandlers[index]);\n\n }\n );\n modal.getRoot().on(\n ModalEvents.cancel,\n () => {\n resolve(null);\n }\n );\n });\n }\n\n /**\n * Create a new modal and return a Promise to the body rendered.\n *\n * @param {Object} modalParams the modal params\n * @returns {Promise} the modal body rendered promise\n */\n modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n}\n\n/**\n * Add a section to refresh.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the seciton id\n */\nfunction addRefreshSection(courseId, sectionId) {\n let refresh = courseUpdates.get(courseId);\n if (!refresh) {\n refresh = new Set();\n }\n refresh.add(sectionId);\n courseUpdates.set(courseId, refresh);\n refreshCourseEditors();\n}\n\n/**\n * Debounced processing all pending course refreshes.\n * @private\n */\nconst refreshCourseEditors = debounce(\n () => {\n const refreshes = courseUpdates;\n courseUpdates = new Map();\n refreshes.forEach((sectionIds, courseId) => {\n const courseEditor = getCourseEditor(courseId);\n if (!courseEditor) {\n return;\n }\n courseEditor.dispatch('sectionState', [...sectionIds]);\n });\n },\n DEBOUNCETIMER\n);\n\n/**\n * Load and return the course handler manager instance.\n *\n * @param {Number} courseId the course Id to load\n * @returns {Promise} promise of the the loaded handleManager\n */\nasync function loadCourseHandlerManager(courseId) {\n if (handlerManagers[courseId] !== undefined) {\n return handlerManagers[courseId];\n }\n const handlerManager = new HandlerManager(courseId);\n await handlerManager.loadHandlers();\n handlerManagers[courseId] = handlerManager;\n return handlerManagers[courseId];\n}\n\n/**\n * Load all the erros messages at once in the module \"errors\" variable.\n * @param {Number} courseId the course id\n */\nasync function loadErrorStrings(courseId) {\n if (errors !== null) {\n return;\n }\n const courseEditor = getCourseEditor(courseId);\n const maxbytestext = courseEditor.get('course')?.maxbytestext ?? '0';\n\n errors = {};\n const allStrings = [\n {key: 'dndmaxbytes', component: 'core_error', param: {size: maxbytestext}},\n {key: 'dndread', component: 'core_error'},\n {key: 'dndupload', component: 'core_error'},\n {key: 'dndunkownfile', component: 'core_error'},\n ];\n window.console.log(allStrings);\n const loadedStrings = await getStrings(allStrings);\n allStrings.forEach(({key}, index) => {\n errors[key] = loadedStrings[index];\n });\n}\n\n/**\n * Start a batch file uploading into the course.\n *\n * @private\n * @param {number} courseId the course id.\n * @param {number} sectionId the section id.\n * @param {number} sectionNum the section number.\n * @param {File} fileInfo the file information object\n * @param {HandlerManager} handlerManager the course handler manager\n */\nconst queueFileUpload = async function(courseId, sectionId, sectionNum, fileInfo, handlerManager) {\n let handler;\n uploadQueue = await processMonitor.createProcessQueue();\n try {\n handlerManager.validateFile(fileInfo);\n handler = await handlerManager.getFileHandler(fileInfo);\n } catch (error) {\n uploadQueue.addError(fileInfo.name, error.message);\n return;\n }\n // If we don't have a handler means the user cancel the upload.\n if (!handler) {\n return;\n }\n const fileProcessor = new FileUploader(courseId, sectionId, sectionNum, fileInfo, handler);\n uploadQueue.addPending(fileInfo.name, fileProcessor.getExecutionFunction());\n};\n\n/**\n * Upload a file to the course.\n *\n * This method will show any necesary modal to handle the request.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the section id\n * @param {number} sectionNum the section number\n * @param {Array} files and array of files\n */\nexport const uploadFilesToCourse = async function(courseId, sectionId, sectionNum, files) {\n // Get the course handlers.\n const handlerManager = await loadCourseHandlerManager(courseId);\n await loadErrorStrings(courseId);\n for (let index = 0; index < files.length; index++) {\n const fileInfo = files[index];\n await queueFileUpload(courseId, sectionId, sectionNum, fileInfo, handlerManager);\n }\n};\n"],"names":["UPLOADURL","Config","wwwroot","uploadQueue","handlerManagers","courseUpdates","Map","errors","FileUploader","constructor","courseId","sectionId","sectionNum","fileInfo","handler","execute","process","this","xhr","_createXhrRequest","formData","_createUploadFormData","reader","FileReader","onload","open","send","onerror","setError","dndread","size","readAsText","slice","getExecutionFunction","bind","XMLHttpRequest","upload","addEventListener","event","lengthComputable","percent","Math","round","loaded","total","setPercentage","onreadystatechange","readyState","status","result","JSON","parse","responseText","error","_finishProcess","dndupload","FormData","append","Error","sesskey","module","refresh","get","Set","add","set","refreshCourseEditors","addRefreshSection","finish","HandlerManager","lastUploadId","courseEditor","maxbytes","_this$courseEditor$ge2","allHandlers","getFileHandlersPromise","getFileExtension","extension","dotpos","name","lastIndexOf","substring","length","toLowerCase","validateFile","dndmaxbytes","filterHandlers","filter","fileHandlers","dndunkownfile","fileHandler","askHandlerToUser","modalParams","title","body","Templates","render","getModalData","lastHandlers","type","ModalFactory","types","SAVE_CANCEL","saveButtonText","modal","modalBodyRenderedPromise","selectedHandler","modalUserAnswerPromise","defaultModule","data","filename","uploadid","handlers","hasDefault","forEach","index","isDefault","push","selected","labelid","value","lastHandler","pop","modalBody","getBody","Promise","resolve","reject","getRoot","on","ModalEvents","save","querySelector","preventDefault","destroy","cancel","create","then","setRemoveOnClose","bodyRendered","undefined","setSaveButtonText","show","catch","refreshes","sectionIds","dispatch","queueFileUpload","async","handlerManager","processMonitor","createProcessQueue","getFileHandler","addError","message","fileProcessor","addPending","files","loadHandlers","loadCourseHandlerManager","maxbytestext","_courseEditor$get","allStrings","key","component","param","window","console","log","loadedStrings","loadErrorStrings"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/cm.min.js b/course/format/amd/build/local/courseindex/cm.min.js index 45adeaafc39..eecb63244ab 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js +++ b/course/format/amd/build/local/courseindex/cm.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/cm",["exports","core_courseformat/lo * @class core_courseformat/local/courseindex/cm * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible&&cm.url||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element);try{const{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}catch(error){throw error}}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"§ion=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible&&cm.url||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"§ion=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default})); //# sourceMappingURL=cm.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/cm.min.js.map b/course/format/amd/build/local/courseindex/cm.min.js.map index d88f840507c..b61121fd712 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js.map +++ b/course/format/amd/build/local/courseindex/cm.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cm.min.js","sources":["../../../src/local/courseindex/cm.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 * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'restrictions',\n PAGEITEM: 'pageitem',\n INDENTED: 'indented',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n const url = new URL(window.location.href);\n const anchor = url.hash.replace('#', '');\n // Check if the current url is the cm url.\n if (window.location.href == cm.url\n || (window.location.href.includes(course.baseurl) && anchor == cm.anchor)\n ) {\n this.reactive.dispatch('setPageItem', 'cm', this.id);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible or the element hasn't URL.\n if (!cm.uservisible || !cm.url) {\n this.addEventListener(\n this.getElement(this.selectors.CM_NAME),\n 'click',\n this._activityAnchor,\n );\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);\n this.element.classList.toggle(this.classes.INDENTED, element.indent);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n try {\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n } catch (error) {\n throw error;\n }\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Marc the element as page item once the event is handled.\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n const course = this.reactive.get('course');\n const section = this.reactive.get('section', cm.sectionid);\n if (!section) {\n return;\n }\n const url = `${course.baseurl}§ion=${section.number}#${cm.anchor}`;\n event.preventDefault();\n window.location = url;\n }\n}\n"],"names":["prefetchTemplate","Component","DndCmItem","create","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","INDENTED","id","this","element","dataset","target","document","getElementById","stateReady","state","configDragDrop","cm","get","course","_refreshCompletion","anchor","URL","window","location","href","hash","replace","url","includes","baseurl","reactive","dispatch","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","uservisible","addEventListener","getElement","_activityAnchor","getWatchers","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrestrictions","indent","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","data","getExporter","cmCompletion","html","js","Templates","renderForPromise","replaceNode","error","event","setTimeout","section","sectionid","number","preventDefault"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cm.min.js","sources":["../../../src/local/courseindex/cm.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 * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'restrictions',\n PAGEITEM: 'pageitem',\n INDENTED: 'indented',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n const url = new URL(window.location.href);\n const anchor = url.hash.replace('#', '');\n // Check if the current url is the cm url.\n if (window.location.href == cm.url\n || (window.location.href.includes(course.baseurl) && anchor == cm.anchor)\n ) {\n this.reactive.dispatch('setPageItem', 'cm', this.id);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible or the element hasn't URL.\n if (!cm.uservisible || !cm.url) {\n this.addEventListener(\n this.getElement(this.selectors.CM_NAME),\n 'click',\n this._activityAnchor,\n );\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);\n this.element.classList.toggle(this.classes.INDENTED, element.indent);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Marc the element as page item once the event is handled.\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n const course = this.reactive.get('course');\n const section = this.reactive.get('section', cm.sectionid);\n if (!section) {\n return;\n }\n const url = `${course.baseurl}§ion=${section.number}#${cm.anchor}`;\n event.preventDefault();\n window.location = url;\n }\n}\n"],"names":["prefetchTemplate","Component","DndCmItem","create","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","INDENTED","id","this","element","dataset","target","document","getElementById","stateReady","state","configDragDrop","cm","get","course","_refreshCompletion","anchor","URL","window","location","href","hash","replace","url","includes","baseurl","reactive","dispatch","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","uservisible","addEventListener","getElement","_activityAnchor","getWatchers","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrestrictions","indent","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","data","getExporter","cmCompletion","html","js","Templates","renderForPromise","replaceNode","event","setTimeout","section","sectionid","number","preventDefault"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseeditor/fileuploader.js b/course/format/amd/src/local/courseeditor/fileuploader.js index 2c1e6c30bd1..278e2dd4e49 100644 --- a/course/format/amd/src/local/courseeditor/fileuploader.js +++ b/course/format/amd/src/local/courseeditor/fileuploader.js @@ -469,13 +469,9 @@ async function loadCourseHandlerManager(courseId) { if (handlerManagers[courseId] !== undefined) { return handlerManagers[courseId]; } - try { - const handlerManager = new HandlerManager(courseId); - await handlerManager.loadHandlers(); - handlerManagers[courseId] = handlerManager; - } catch (error) { - throw error; - } + const handlerManager = new HandlerManager(courseId); + await handlerManager.loadHandlers(); + handlerManagers[courseId] = handlerManager; return handlerManagers[courseId]; } @@ -544,13 +540,8 @@ const queueFileUpload = async function(courseId, sectionId, sectionNum, fileInfo */ export const uploadFilesToCourse = async function(courseId, sectionId, sectionNum, files) { // Get the course handlers. - let handlerManager; - try { - handlerManager = await loadCourseHandlerManager(courseId); - await loadErrorStrings(courseId); - } catch (error) { - throw error; - } + const handlerManager = await loadCourseHandlerManager(courseId); + await loadErrorStrings(courseId); for (let index = 0; index < files.length; index++) { const fileInfo = files[index]; await queueFileUpload(courseId, sectionId, sectionNum, fileInfo, handlerManager); diff --git a/course/format/amd/src/local/courseindex/cm.js b/course/format/amd/src/local/courseindex/cm.js index 62a0dfef986..e33099923ae 100644 --- a/course/format/amd/src/local/courseindex/cm.js +++ b/course/format/amd/src/local/courseindex/cm.js @@ -180,12 +180,8 @@ export default class Component extends DndCmItem { const exporter = this.reactive.getExporter(); const data = exporter.cmCompletion(state, element); - try { - const {html, js} = await Templates.renderForPromise(completionTemplate, data); - Templates.replaceNode(completionElement, html, js); - } catch (error) { - throw error; - } + const {html, js} = await Templates.renderForPromise(completionTemplate, data); + Templates.replaceNode(completionElement, html, js); } /** diff --git a/lib/amd/build/local/reactive/overlay.min.js b/lib/amd/build/local/reactive/overlay.min.js index 2b2d0ea5eae..97cd2e5d532 100644 --- a/lib/amd/build/local/reactive/overlay.min.js +++ b/lib/amd/build/local/reactive/overlay.min.js @@ -8,6 +8,6 @@ define("core/local/reactive/overlay",["exports","core/templates","core/prefetch" * @module core/local/reactive/overlay * @copyright 2022 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.removeOverlay=_exports.removeAllOverlays=_exports.addOverlay=void 0,_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch);_prefetch.default.prefetchTemplate("core/local/reactive/overlay");const selectors_OVERLAY="[data-overlay]",selectors_REPOSITION="[data-overlay-dynamic]",selectors_NAVBAR="nav.navbar.fixed-top";_exports.addOverlay=async(definition,parent)=>{var _definition$classes;definition.content&&"string"!=typeof definition.content&&(definition.content=await definition.content),definition.icon&&"string"!=typeof definition.icon&&(definition.icon=await definition.icon);const data={content:definition.content,css:null!==(_definition$classes=definition.classes)&&void 0!==_definition$classes?_definition$classes:"file-drop-zone"};let overlay;try{const{html:html,js:js}=await _templates.default.renderForPromise("core/local/reactive/overlay",data);_templates.default.appendNodeContents(parent,html,js),overlay=parent.querySelector(selectors_OVERLAY),rePositionPreviewInfoElement(overlay),init()}catch(error){throw error}return overlay};const removeOverlay=overlay=>{var _overlay$dataset;overlay&&overlay.parentNode&&(null!==(_overlay$dataset=overlay.dataset)&&void 0!==_overlay$dataset&&_overlay$dataset.overlayPosition&&delete overlay.parentNode.style.position,overlay.parentNode.removeChild(overlay))};_exports.removeOverlay=removeOverlay;_exports.removeAllOverlays=()=>{document.querySelectorAll(selectors_OVERLAY).forEach((overlay=>{removeOverlay(overlay)}))};const rePositionPreviewInfoElement=function(overlay){var _overlay$parentNode,_overlay$parentNode$s;if(!overlay)throw new Error("Inexistent overlay element");null!==(_overlay$parentNode=overlay.parentNode)&&void 0!==_overlay$parentNode&&null!==(_overlay$parentNode$s=_overlay$parentNode.style)&&void 0!==_overlay$parentNode$s&&_overlay$parentNode$s.position||(overlay.parentNode.style.position="relative",overlay.dataset.overlayPosition="true");const target=overlay.querySelector(selectors_REPOSITION);if(!target)return;const rect=overlay.getBoundingClientRect(),sectionHeight=parseInt(window.getComputedStyle(overlay).height,10),sectionOffset=rect.top,previewHeight=parseInt(window.getComputedStyle(target).height,10)+2*parseInt(window.getComputedStyle(target).padding,10);let top,bottom;if(sectionOffset<0)if(sectionHeight+sectionOffset>=previewHeight){let offSetTop=0-sectionOffset;const navBar=document.querySelector(selectors_NAVBAR);navBar&&(offSetTop+=navBar.offsetHeight),top=offSetTop+"px",bottom="unset"}else top="unset",bottom=0;else top=0,bottom="unset";target.style.top=top,target.style.bottom=bottom},init=()=>{document.addEventListener("scroll",(()=>{document.querySelectorAll(selectors_OVERLAY).forEach((overlay=>{rePositionPreviewInfoElement(overlay)}))}),!0)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.removeOverlay=_exports.removeAllOverlays=_exports.addOverlay=void 0,_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch);_prefetch.default.prefetchTemplate("core/local/reactive/overlay");const selectors_OVERLAY="[data-overlay]",selectors_REPOSITION="[data-overlay-dynamic]",selectors_NAVBAR="nav.navbar.fixed-top";_exports.addOverlay=async(definition,parent)=>{var _definition$classes;definition.content&&"string"!=typeof definition.content&&(definition.content=await definition.content),definition.icon&&"string"!=typeof definition.icon&&(definition.icon=await definition.icon);const data={content:definition.content,css:null!==(_definition$classes=definition.classes)&&void 0!==_definition$classes?_definition$classes:"file-drop-zone"},{html:html,js:js}=await _templates.default.renderForPromise("core/local/reactive/overlay",data);_templates.default.appendNodeContents(parent,html,js);const overlay=parent.querySelector(selectors_OVERLAY);return rePositionPreviewInfoElement(overlay),init(),overlay};const removeOverlay=overlay=>{var _overlay$dataset;overlay&&overlay.parentNode&&(null!==(_overlay$dataset=overlay.dataset)&&void 0!==_overlay$dataset&&_overlay$dataset.overlayPosition&&delete overlay.parentNode.style.position,overlay.parentNode.removeChild(overlay))};_exports.removeOverlay=removeOverlay;_exports.removeAllOverlays=()=>{document.querySelectorAll(selectors_OVERLAY).forEach((overlay=>{removeOverlay(overlay)}))};const rePositionPreviewInfoElement=function(overlay){var _overlay$parentNode,_overlay$parentNode$s;if(!overlay)throw new Error("Inexistent overlay element");null!==(_overlay$parentNode=overlay.parentNode)&&void 0!==_overlay$parentNode&&null!==(_overlay$parentNode$s=_overlay$parentNode.style)&&void 0!==_overlay$parentNode$s&&_overlay$parentNode$s.position||(overlay.parentNode.style.position="relative",overlay.dataset.overlayPosition="true");const target=overlay.querySelector(selectors_REPOSITION);if(!target)return;const rect=overlay.getBoundingClientRect(),sectionHeight=parseInt(window.getComputedStyle(overlay).height,10),sectionOffset=rect.top,previewHeight=parseInt(window.getComputedStyle(target).height,10)+2*parseInt(window.getComputedStyle(target).padding,10);let top,bottom;if(sectionOffset<0)if(sectionHeight+sectionOffset>=previewHeight){let offSetTop=0-sectionOffset;const navBar=document.querySelector(selectors_NAVBAR);navBar&&(offSetTop+=navBar.offsetHeight),top=offSetTop+"px",bottom="unset"}else top="unset",bottom=0;else top=0,bottom="unset";target.style.top=top,target.style.bottom=bottom},init=()=>{document.addEventListener("scroll",(()=>{document.querySelectorAll(selectors_OVERLAY).forEach((overlay=>{rePositionPreviewInfoElement(overlay)}))}),!0)}})); //# sourceMappingURL=overlay.min.js.map \ No newline at end of file diff --git a/lib/amd/build/local/reactive/overlay.min.js.map b/lib/amd/build/local/reactive/overlay.min.js.map index 76623879487..32236986887 100644 --- a/lib/amd/build/local/reactive/overlay.min.js.map +++ b/lib/amd/build/local/reactive/overlay.min.js.map @@ -1 +1 @@ -{"version":3,"file":"overlay.min.js","sources":["../../../src/local/reactive/overlay.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 * Element overlay methods.\n *\n * This module is used to create overlay information on components. For example\n * to generate or destroy file drop-zones.\n *\n * @module core/local/reactive/overlay\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\n\n// Prefetch the overlay html.\nconst overlayTemplate = 'core/local/reactive/overlay';\nPrefetch.prefetchTemplate(overlayTemplate);\n\n/**\n * @var {boolean} isInitialized if the module is capturing the proper page events.\n */\nlet isInitialized = false;\n\n/**\n * @var {Object} isInitialized if the module is capturing the proper page events.\n */\nconst selectors = {\n OVERLAY: \"[data-overlay]\",\n REPOSITION: \"[data-overlay-dynamic]\",\n NAVBAR: \"nav.navbar.fixed-top\",\n};\n\n/**\n * Adds an overlay to a specific page element.\n *\n * @param {Object} definition the overlay definition.\n * @param {String|Promise} definition.content an optional overlay content.\n * @param {String|Promise} definition.icon an optional icon content.\n * @param {String} definition.classes an optional CSS classes\n * @param {HTMLElement} parent the parent object\n * @return {HTMLElement|undefined} the new page element.\n */\nexport const addOverlay = async(definition, parent) => {\n // Validate non of the passed params is a promise.\n if (definition.content && typeof definition.content !== 'string') {\n definition.content = await definition.content;\n }\n if (definition.icon && typeof definition.icon !== 'string') {\n definition.icon = await definition.icon;\n }\n const data = {\n content: definition.content,\n css: definition.classes ?? 'file-drop-zone',\n };\n let overlay;\n try {\n const {html, js} = await Templates.renderForPromise(overlayTemplate, data);\n Templates.appendNodeContents(parent, html, js);\n overlay = parent.querySelector(selectors.OVERLAY);\n rePositionPreviewInfoElement(overlay);\n init();\n } catch (error) {\n throw error;\n }\n return overlay;\n};\n\n/**\n * Adds an overlay to a specific page element.\n *\n * @param {HTMLElement} overlay the parent object\n */\nexport const removeOverlay = (overlay) => {\n if (!overlay || !overlay.parentNode) {\n return;\n }\n // Remove any forced parentNode position.\n if (overlay.dataset?.overlayPosition) {\n delete overlay.parentNode.style.position;\n }\n overlay.parentNode.removeChild(overlay);\n};\n\nexport const removeAllOverlays = () => {\n document.querySelectorAll(selectors.OVERLAY).forEach(\n (overlay) => {\n removeOverlay(overlay);\n }\n );\n};\n\n/**\n * Re-position the preview information element by calculating the section position.\n *\n * @param {Object} overlay the overlay element.\n */\nconst rePositionPreviewInfoElement = function(overlay) {\n if (!overlay) {\n throw new Error('Inexistent overlay element');\n }\n // Add relative position to the parent object.\n if (!overlay.parentNode?.style?.position) {\n overlay.parentNode.style.position = 'relative';\n overlay.dataset.overlayPosition = \"true\";\n }\n // Get the element to reposition.\n const target = overlay.querySelector(selectors.REPOSITION);\n if (!target) {\n return;\n }\n // Get the new bounds.\n const rect = overlay.getBoundingClientRect();\n const sectionHeight = parseInt(window.getComputedStyle(overlay).height, 10);\n const sectionOffset = rect.top;\n const previewHeight = parseInt(window.getComputedStyle(target).height, 10) +\n (2 * parseInt(window.getComputedStyle(target).padding, 10));\n // Calculate the new target position.\n let top, bottom;\n if (sectionOffset < 0) {\n if (sectionHeight + sectionOffset >= previewHeight) {\n // We have enough space here, just stick the preview to the top.\n let offSetTop = 0 - sectionOffset;\n const navBar = document.querySelector(selectors.NAVBAR);\n if (navBar) {\n offSetTop = offSetTop + navBar.offsetHeight;\n }\n top = offSetTop + 'px';\n bottom = 'unset';\n } else {\n // We do not have enough space here, just stick the preview to the bottom.\n top = 'unset';\n bottom = 0;\n }\n } else {\n top = 0;\n bottom = 'unset';\n }\n\n target.style.top = top;\n target.style.bottom = bottom;\n};\n\n// Update overlays when the page scrolls.\nconst init = () => {\n if (isInitialized) {\n return;\n }\n // Add scroll events.\n document.addEventListener('scroll', () => {\n document.querySelectorAll(selectors.OVERLAY).forEach(\n (overlay) => {\n rePositionPreviewInfoElement(overlay);\n }\n );\n }, true);\n};\n"],"names":["prefetchTemplate","selectors","async","definition","parent","content","icon","data","css","classes","overlay","html","js","Templates","renderForPromise","appendNodeContents","querySelector","rePositionPreviewInfoElement","init","error","removeOverlay","parentNode","dataset","_overlay$dataset","overlayPosition","style","position","removeChild","document","querySelectorAll","forEach","Error","_overlay$parentNode","_overlay$parentNode$s","target","rect","getBoundingClientRect","sectionHeight","parseInt","window","getComputedStyle","height","sectionOffset","top","previewHeight","padding","bottom","offSetTop","navBar","offsetHeight","addEventListener"],"mappings":";;;;;;;;;;sPA+BSA,iBADe,qCAWlBC,kBACO,iBADPA,qBAEU,yBAFVA,iBAGM,2CAacC,MAAMC,WAAYC,kCAEpCD,WAAWE,SAAyC,iBAAvBF,WAAWE,UACxCF,WAAWE,cAAgBF,WAAWE,SAEtCF,WAAWG,MAAmC,iBAApBH,WAAWG,OACrCH,WAAWG,WAAaH,WAAWG,YAEjCC,KAAO,CACTF,QAASF,WAAWE,QACpBG,gCAAKL,WAAWM,2DAAW,sBAE3BC,kBAEMC,KAACA,KAADC,GAAOA,UAAYC,mBAAUC,iBAzCnB,8BAyCqDP,yBAC3DQ,mBAAmBX,OAAQO,KAAMC,IAC3CF,QAAUN,OAAOY,cAAcf,mBAC/BgB,6BAA6BP,SAC7BQ,OACF,MAAOC,aACCA,aAEHT,eAQEU,cAAiBV,+BACrBA,SAAYA,QAAQW,sCAIrBX,QAAQY,qCAARC,iBAAiBC,wBACVd,QAAQW,WAAWI,MAAMC,SAEpChB,QAAQW,WAAWM,YAAYjB,2EAGF,KAC7BkB,SAASC,iBAAiB5B,mBAAmB6B,SACxCpB,UACGU,cAAcV,mBAUpBO,6BAA+B,SAASP,2DACrCA,cACK,IAAIqB,MAAM,0DAGfrB,QAAQW,yEAARW,oBAAoBP,wCAApBQ,sBAA2BP,WAC5BhB,QAAQW,WAAWI,MAAMC,SAAW,WACpChB,QAAQY,QAAQE,gBAAkB,cAGhCU,OAASxB,QAAQM,cAAcf,0BAChCiC,oBAICC,KAAOzB,QAAQ0B,wBACfC,cAAgBC,SAASC,OAAOC,iBAAiB9B,SAAS+B,OAAQ,IAClEC,cAAgBP,KAAKQ,IACrBC,cAAgBN,SAASC,OAAOC,iBAAiBN,QAAQO,OAAQ,IAClE,EAAIH,SAASC,OAAOC,iBAAiBN,QAAQW,QAAS,QAEvDF,IAAKG,UACLJ,cAAgB,KACZL,cAAgBK,eAAiBE,cAAe,KAE5CG,UAAY,EAAIL,oBACdM,OAASpB,SAASZ,cAAcf,kBAClC+C,SACAD,WAAwBC,OAAOC,cAEnCN,IAAMI,UAAY,KAClBD,OAAS,aAGTH,IAAM,QACNG,OAAS,OAGbH,IAAM,EACNG,OAAS,QAGbZ,OAAOT,MAAMkB,IAAMA,IACnBT,OAAOT,MAAMqB,OAASA,QAIpB5B,KAAO,KAKTU,SAASsB,iBAAiB,UAAU,KAChCtB,SAASC,iBAAiB5B,mBAAmB6B,SACxCpB,UACGO,6BAA6BP,eAGtC"} \ No newline at end of file +{"version":3,"file":"overlay.min.js","sources":["../../../src/local/reactive/overlay.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 * Element overlay methods.\n *\n * This module is used to create overlay information on components. For example\n * to generate or destroy file drop-zones.\n *\n * @module core/local/reactive/overlay\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\n\n// Prefetch the overlay html.\nconst overlayTemplate = 'core/local/reactive/overlay';\nPrefetch.prefetchTemplate(overlayTemplate);\n\n/**\n * @var {boolean} isInitialized if the module is capturing the proper page events.\n */\nlet isInitialized = false;\n\n/**\n * @var {Object} isInitialized if the module is capturing the proper page events.\n */\nconst selectors = {\n OVERLAY: \"[data-overlay]\",\n REPOSITION: \"[data-overlay-dynamic]\",\n NAVBAR: \"nav.navbar.fixed-top\",\n};\n\n/**\n * Adds an overlay to a specific page element.\n *\n * @param {Object} definition the overlay definition.\n * @param {String|Promise} definition.content an optional overlay content.\n * @param {String|Promise} definition.icon an optional icon content.\n * @param {String} definition.classes an optional CSS classes\n * @param {HTMLElement} parent the parent object\n * @return {HTMLElement|undefined} the new page element.\n */\nexport const addOverlay = async(definition, parent) => {\n // Validate non of the passed params is a promise.\n if (definition.content && typeof definition.content !== 'string') {\n definition.content = await definition.content;\n }\n if (definition.icon && typeof definition.icon !== 'string') {\n definition.icon = await definition.icon;\n }\n const data = {\n content: definition.content,\n css: definition.classes ?? 'file-drop-zone',\n };\n const {html, js} = await Templates.renderForPromise(overlayTemplate, data);\n Templates.appendNodeContents(parent, html, js);\n const overlay = parent.querySelector(selectors.OVERLAY);\n rePositionPreviewInfoElement(overlay);\n init();\n return overlay;\n};\n\n/**\n * Adds an overlay to a specific page element.\n *\n * @param {HTMLElement} overlay the parent object\n */\nexport const removeOverlay = (overlay) => {\n if (!overlay || !overlay.parentNode) {\n return;\n }\n // Remove any forced parentNode position.\n if (overlay.dataset?.overlayPosition) {\n delete overlay.parentNode.style.position;\n }\n overlay.parentNode.removeChild(overlay);\n};\n\nexport const removeAllOverlays = () => {\n document.querySelectorAll(selectors.OVERLAY).forEach(\n (overlay) => {\n removeOverlay(overlay);\n }\n );\n};\n\n/**\n * Re-position the preview information element by calculating the section position.\n *\n * @param {Object} overlay the overlay element.\n */\nconst rePositionPreviewInfoElement = function(overlay) {\n if (!overlay) {\n throw new Error('Inexistent overlay element');\n }\n // Add relative position to the parent object.\n if (!overlay.parentNode?.style?.position) {\n overlay.parentNode.style.position = 'relative';\n overlay.dataset.overlayPosition = \"true\";\n }\n // Get the element to reposition.\n const target = overlay.querySelector(selectors.REPOSITION);\n if (!target) {\n return;\n }\n // Get the new bounds.\n const rect = overlay.getBoundingClientRect();\n const sectionHeight = parseInt(window.getComputedStyle(overlay).height, 10);\n const sectionOffset = rect.top;\n const previewHeight = parseInt(window.getComputedStyle(target).height, 10) +\n (2 * parseInt(window.getComputedStyle(target).padding, 10));\n // Calculate the new target position.\n let top, bottom;\n if (sectionOffset < 0) {\n if (sectionHeight + sectionOffset >= previewHeight) {\n // We have enough space here, just stick the preview to the top.\n let offSetTop = 0 - sectionOffset;\n const navBar = document.querySelector(selectors.NAVBAR);\n if (navBar) {\n offSetTop = offSetTop + navBar.offsetHeight;\n }\n top = offSetTop + 'px';\n bottom = 'unset';\n } else {\n // We do not have enough space here, just stick the preview to the bottom.\n top = 'unset';\n bottom = 0;\n }\n } else {\n top = 0;\n bottom = 'unset';\n }\n\n target.style.top = top;\n target.style.bottom = bottom;\n};\n\n// Update overlays when the page scrolls.\nconst init = () => {\n if (isInitialized) {\n return;\n }\n // Add scroll events.\n document.addEventListener('scroll', () => {\n document.querySelectorAll(selectors.OVERLAY).forEach(\n (overlay) => {\n rePositionPreviewInfoElement(overlay);\n }\n );\n }, true);\n};\n"],"names":["prefetchTemplate","selectors","async","definition","parent","content","icon","data","css","classes","html","js","Templates","renderForPromise","appendNodeContents","overlay","querySelector","rePositionPreviewInfoElement","init","removeOverlay","parentNode","dataset","_overlay$dataset","overlayPosition","style","position","removeChild","document","querySelectorAll","forEach","Error","_overlay$parentNode","_overlay$parentNode$s","target","rect","getBoundingClientRect","sectionHeight","parseInt","window","getComputedStyle","height","sectionOffset","top","previewHeight","padding","bottom","offSetTop","navBar","offsetHeight","addEventListener"],"mappings":";;;;;;;;;;sPA+BSA,iBADe,qCAWlBC,kBACO,iBADPA,qBAEU,yBAFVA,iBAGM,2CAacC,MAAMC,WAAYC,kCAEpCD,WAAWE,SAAyC,iBAAvBF,WAAWE,UACxCF,WAAWE,cAAgBF,WAAWE,SAEtCF,WAAWG,MAAmC,iBAApBH,WAAWG,OACrCH,WAAWG,WAAaH,WAAWG,YAEjCC,KAAO,CACTF,QAASF,WAAWE,QACpBG,gCAAKL,WAAWM,2DAAW,mBAEzBC,KAACA,KAADC,GAAOA,UAAYC,mBAAUC,iBAvCf,8BAuCiDN,yBAC3DO,mBAAmBV,OAAQM,KAAMC,UACrCI,QAAUX,OAAOY,cAAcf,0BACrCgB,6BAA6BF,SAC7BG,OACOH,eAQEI,cAAiBJ,+BACrBA,SAAYA,QAAQK,sCAIrBL,QAAQM,qCAARC,iBAAiBC,wBACVR,QAAQK,WAAWI,MAAMC,SAEpCV,QAAQK,WAAWM,YAAYX,2EAGF,KAC7BY,SAASC,iBAAiB3B,mBAAmB4B,SACxCd,UACGI,cAAcJ,mBAUpBE,6BAA+B,SAASF,2DACrCA,cACK,IAAIe,MAAM,0DAGff,QAAQK,yEAARW,oBAAoBP,wCAApBQ,sBAA2BP,WAC5BV,QAAQK,WAAWI,MAAMC,SAAW,WACpCV,QAAQM,QAAQE,gBAAkB,cAGhCU,OAASlB,QAAQC,cAAcf,0BAChCgC,oBAICC,KAAOnB,QAAQoB,wBACfC,cAAgBC,SAASC,OAAOC,iBAAiBxB,SAASyB,OAAQ,IAClEC,cAAgBP,KAAKQ,IACrBC,cAAgBN,SAASC,OAAOC,iBAAiBN,QAAQO,OAAQ,IAClE,EAAIH,SAASC,OAAOC,iBAAiBN,QAAQW,QAAS,QAEvDF,IAAKG,UACLJ,cAAgB,KACZL,cAAgBK,eAAiBE,cAAe,KAE5CG,UAAY,EAAIL,oBACdM,OAASpB,SAASX,cAAcf,kBAClC8C,SACAD,WAAwBC,OAAOC,cAEnCN,IAAMI,UAAY,KAClBD,OAAS,aAGTH,IAAM,QACNG,OAAS,OAGbH,IAAM,EACNG,OAAS,QAGbZ,OAAOT,MAAMkB,IAAMA,IACnBT,OAAOT,MAAMqB,OAASA,QAIpB3B,KAAO,KAKTS,SAASsB,iBAAiB,UAAU,KAChCtB,SAASC,iBAAiB3B,mBAAmB4B,SACxCd,UACGE,6BAA6BF,eAGtC"} \ No newline at end of file diff --git a/lib/amd/build/sortable_list.min.js.map b/lib/amd/build/sortable_list.min.js.map index 454f008be49..a4d6e4713bd 100644 --- a/lib/amd/build/sortable_list.min.js.map +++ b/lib/amd/build/sortable_list.min.js.map @@ -1 +1 @@ -{"version":3,"file":"sortable_list.min.js","sources":["../src/sortable_list.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 * A javascript module to handle list items drag and drop\n *\n * Example of usage:\n *\n * Create a list (for example `