Merge branch 'MDL-71228-master-v02' of git://github.com/ferranrecio/moodle

This commit is contained in:
Jun Pataleta
2021-09-13 22:10:19 +08:00
62 changed files with 2046 additions and 319 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -3
View File
@@ -279,11 +279,12 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
*
* Used after d&d of the module to another section
*
* @param {JQuery} activityElement
* @param {JQuery|Element} element
* @param {Number} cmid
* @param {Number} sectionreturn
*/
var refreshModule = function(activityElement, cmid, sectionreturn) {
var refreshModule = function(element, cmid, sectionreturn) {
const activityElement = $(element);
var spinner = addActivitySpinner(activityElement);
var promises = ajax.call([{
methodname: 'core_course_get_module',
@@ -766,6 +767,8 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
log.debug('replaceSectionActionItem() is deprecated and will be removed.');
var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);
replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);
}
},
// Method to refresh a module.
refreshModule,
};
});
@@ -1 +1 @@
{"version":3,"sources":["../src/courseeditor.js"],"names":["courseEditorMap","Map","dispatchStateChangedEvent","detail","target","document","dispatchEvent","CustomEvent","events","stateChanged","bubbles","setViewFormat","courseId","setup","editor","getCourseEditor","parseInt","has","set","CourseEditor","name","eventName","eventDispatch","mutations","DefaultMutations","get","loadCourse","getCurrentCourseEditor","M","cfg"],"mappings":"iUAuBA,OACA,OACA,O,mDAGA,GAAMA,CAAAA,CAAe,CAAG,GAAIC,CAAAA,GAA5B,CAYA,QAASC,CAAAA,CAAT,CAAmCC,CAAnC,CAA2CC,CAA3C,CAAmD,CAC/C,GAAIA,CAAM,SAAV,CAA0B,CACtBA,CAAM,CAAGC,QACZ,CACDD,CAAM,CAACE,aAAP,CAAqB,GAAIC,CAAAA,WAAJ,CAAgBC,UAAOC,YAAvB,CAAqC,CACtDC,OAAO,GAD+C,CAEtDP,MAAM,CAAEA,CAF8C,CAArC,CAArB,CAIH,C,gBAS4B,QAAhBQ,CAAAA,aAAgB,CAACC,CAAD,CAAWC,CAAX,CAAqB,CAC9C,GAAMC,CAAAA,CAAM,CAAGC,CAAe,CAACH,CAAD,CAA9B,CACAE,CAAM,CAACH,aAAP,CAAqBE,CAArB,CACH,C,CAQM,GAAME,CAAAA,CAAe,CAAG,SAACH,CAAD,CAAc,CACzCA,CAAQ,CAAGI,QAAQ,CAACJ,CAAD,CAAnB,CAEA,GAAI,CAACZ,CAAe,CAACiB,GAAhB,CAAoBL,CAApB,CAAL,CAAoC,CAChCZ,CAAe,CAACkB,GAAhB,CACIN,CADJ,CAEI,GAAIO,UAAJ,CAAiB,CACbC,IAAI,uBAAiBR,CAAjB,CADS,CAEbS,SAAS,CAAEb,UAAOC,YAFL,CAGba,aAAa,CAAEpB,CAHF,CAMbqB,SAAS,CAAE,GAAIC,UANF,CAAjB,CAFJ,EAWAxB,CAAe,CAACyB,GAAhB,CAAoBb,CAApB,EAA8Bc,UAA9B,CAAyCd,CAAzC,CACH,CACD,MAAOZ,CAAAA,CAAe,CAACyB,GAAhB,CAAoBb,CAApB,CACV,CAlBM,C,6CAyB+B,QAAzBe,CAAAA,sBAAyB,SAAMZ,CAAAA,CAAe,CAACa,CAAC,CAACC,GAAF,CAAMjB,QAAP,CAArB,C","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core_courseformat/courseeditor\n * @copyright 2021 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\nimport CourseEditor from 'core_courseformat/local/courseeditor/courseeditor';\nimport events from 'core_course/events';\n\n// A map with all the course editor instances.\nconst courseEditorMap = new Map();\n\n/**\n * Trigger a state changed event.\n *\n * This function will be moved to core_course/events module\n * when the file is migrated to the new JS events structure proposed in MDL-70990.\n *\n * @method dispatchStateChangedEvent\n * @param {object} detail the full state\n * @param {object} target the custom event target (document if none provided)\n */\nfunction dispatchStateChangedEvent(detail, target) {\n if (target === undefined) {\n target = document;\n }\n target.dispatchEvent(new CustomEvent(events.stateChanged, {\n bubbles: true,\n detail: detail,\n }));\n}\n\n/**\n * Setup the current view settings\n *\n * @param {number} courseId the course id\n * @param {setup} setup format, page and course settings\n * @property {boolean} setup.editing if the page is in edit mode\n */\nexport const setViewFormat = (courseId, setup) => {\n const editor = getCourseEditor(courseId);\n editor.setViewFormat(setup);\n};\n\n/**\n * Get a specific course editor reactive instance.\n *\n * @param {number} courseId the course id\n * @returns {CourseEditor}\n */\nexport const getCourseEditor = (courseId) => {\n courseId = parseInt(courseId);\n\n if (!courseEditorMap.has(courseId)) {\n courseEditorMap.set(\n courseId,\n new CourseEditor({\n name: `CourseEditor${courseId}`,\n eventName: events.stateChanged,\n eventDispatch: dispatchStateChangedEvent,\n // Mutations can be overridden by the format plugin using setMutations\n // but we need the default one at least.\n mutations: new DefaultMutations(),\n })\n );\n courseEditorMap.get(courseId).loadCourse(courseId);\n }\n return courseEditorMap.get(courseId);\n};\n\n/**\n * Get the current course reactive instance.\n *\n * @returns {CourseEditor}\n */\nexport const getCurrentCourseEditor = () => getCourseEditor(M.cfg.courseId);\n"],"file":"courseeditor.min.js"}
{"version":3,"sources":["../src/courseeditor.js"],"names":["courseEditorMap","Map","dispatchStateChangedEvent","detail","target","document","dispatchEvent","CustomEvent","events","stateChanged","bubbles","setViewFormat","courseId","setup","editor","getCourseEditor","parseInt","has","set","CourseEditor","name","eventName","eventDispatch","mutations","DefaultMutations","get","loadCourse","getCurrentCourseEditor","M","cfg"],"mappings":"iUAuBA,OACA,OACA,O,mDAGA,GAAMA,CAAAA,CAAe,CAAG,GAAIC,CAAAA,GAA5B,CAYA,QAASC,CAAAA,CAAT,CAAmCC,CAAnC,CAA2CC,CAA3C,CAAmD,CAC/C,GAAIA,CAAM,SAAV,CAA0B,CACtBA,CAAM,CAAGC,QACZ,CACDD,CAAM,CAACE,aAAP,CAAqB,GAAIC,CAAAA,WAAJ,CAAgBC,UAAOC,YAAvB,CAAqC,CACtDC,OAAO,GAD+C,CAEtDP,MAAM,CAAEA,CAF8C,CAArC,CAArB,CAIH,C,gBAU4B,QAAhBQ,CAAAA,aAAgB,CAACC,CAAD,CAAWC,CAAX,CAAqB,CAC9C,GAAMC,CAAAA,CAAM,CAAGC,CAAe,CAACH,CAAD,CAA9B,CACAE,CAAM,CAACH,aAAP,CAAqBE,CAArB,CACH,C,CAQM,GAAME,CAAAA,CAAe,CAAG,SAACH,CAAD,CAAc,CACzCA,CAAQ,CAAGI,QAAQ,CAACJ,CAAD,CAAnB,CAEA,GAAI,CAACZ,CAAe,CAACiB,GAAhB,CAAoBL,CAApB,CAAL,CAAoC,CAChCZ,CAAe,CAACkB,GAAhB,CACIN,CADJ,CAEI,GAAIO,UAAJ,CAAiB,CACbC,IAAI,uBAAiBR,CAAjB,CADS,CAEbS,SAAS,CAAEb,UAAOC,YAFL,CAGba,aAAa,CAAEpB,CAHF,CAMbqB,SAAS,CAAE,GAAIC,UANF,CAAjB,CAFJ,EAWAxB,CAAe,CAACyB,GAAhB,CAAoBb,CAApB,EAA8Bc,UAA9B,CAAyCd,CAAzC,CACH,CACD,MAAOZ,CAAAA,CAAe,CAACyB,GAAhB,CAAoBb,CAApB,CACV,CAlBM,C,6CAyB+B,QAAzBe,CAAAA,sBAAyB,SAAMZ,CAAAA,CAAe,CAACa,CAAC,CAACC,GAAF,CAAMjB,QAAP,CAArB,C","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core_courseformat/courseeditor\n * @copyright 2021 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\nimport CourseEditor from 'core_courseformat/local/courseeditor/courseeditor';\nimport events from 'core_course/events';\n\n// A map with all the course editor instances.\nconst courseEditorMap = new Map();\n\n/**\n * Trigger a state changed event.\n *\n * This function will be moved to core_course/events module\n * when the file is migrated to the new JS events structure proposed in MDL-70990.\n *\n * @method dispatchStateChangedEvent\n * @param {object} detail the full state\n * @param {object} target the custom event target (document if none provided)\n */\nfunction dispatchStateChangedEvent(detail, target) {\n if (target === undefined) {\n target = document;\n }\n target.dispatchEvent(new CustomEvent(events.stateChanged, {\n bubbles: true,\n detail: detail,\n }));\n}\n\n/**\n * Setup the current view settings\n *\n * @param {number} courseId the course id\n * @param {setup} setup format, page and course settings\n * @property {boolean} setup.editing if the page is in edit mode\n * @property {boolean} setup.supportscomponents if the format supports components for content\n */\nexport const setViewFormat = (courseId, setup) => {\n const editor = getCourseEditor(courseId);\n editor.setViewFormat(setup);\n};\n\n/**\n * Get a specific course editor reactive instance.\n *\n * @param {number} courseId the course id\n * @returns {CourseEditor}\n */\nexport const getCourseEditor = (courseId) => {\n courseId = parseInt(courseId);\n\n if (!courseEditorMap.has(courseId)) {\n courseEditorMap.set(\n courseId,\n new CourseEditor({\n name: `CourseEditor${courseId}`,\n eventName: events.stateChanged,\n eventDispatch: dispatchStateChangedEvent,\n // Mutations can be overridden by the format plugin using setMutations\n // but we need the default one at least.\n mutations: new DefaultMutations(),\n })\n );\n courseEditorMap.get(courseId).loadCourse(courseId);\n }\n return courseEditorMap.get(courseId);\n};\n\n/**\n * Get the current course reactive instance.\n *\n * @returns {CourseEditor}\n */\nexport const getCurrentCourseEditor = () => getCourseEditor(M.cfg.courseId);\n"],"file":"courseeditor.min.js"}
+2
View File
@@ -0,0 +1,2 @@
define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_course/actions"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function i(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function j(a,b,c){if(b)i(a.prototype,b);if(c)i(a,c);return a}function k(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)l(a,b)}function l(a,b){l=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return l(a,b)}function m(a){return function(){var b=q(a),c;if(p()){var d=q(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return n(this,c)}}function n(a,b){if(b&&("object"===g(b)||"function"==typeof b)){return b}return o(a)}function o(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function p(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function q(a){q=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return q(a)}var r=function(a){k(b,a);var f=m(b);function b(){h(this,b);return f.apply(this,arguments)}j(b,[{key:"create",value:function create(){this.name="course_format";this.selectors={SECTION:"[data-for='section']",SECTION_ITEM:"[data-for='section_item']",SECTION_TITLE:"[data-for='section_title']",SECTION_CMLIST:"[data-for='cmlist']",COURSE_SECTIONLIST:"[data-for='course_sectionlist']",CM:"[data-for='cmitem']"};this.dettachedCms={};this.dettachedSections={}}},{key:"getWatchers",value:function getWatchers(){if(!this.reactive.supportComponents){return[]}return[{watch:"cm.visible:updated",handler:this._reloadCm},{watch:"section.number:updated",handler:this._refreshSectionNumber},{watch:"transaction:start",handler:this._startProcessing},{watch:"course.sectionlist:updated",handler:this._refreshCourseSectionlist},{watch:"section.cmlist:updated",handler:this._refreshSectionCmlist}]}},{key:"_reloadCm",value:function _reloadCm(a){var b=a.element,c=this.getElement(this.selectors.CM,b.id);if(c){e.default.refreshModule(c,b.id)}}},{key:"_startProcessing",value:function _startProcessing(){this.dettachedCms={};this.dettachedSections={}}},{key:"_refreshSectionNumber",value:function _refreshSectionNumber(a){var b=a.element,c=this.getElement(this.selectors.SECTION,b.id);if(!c){throw new Error("Unkown section with ID ".concat(b.id))}c.id="section-".concat(b.number);c.dataset.sectionid=b.number;c.dataset.number=b.number;var e=d.default.getInplaceEditable(c.querySelector(this.selectors.SECTION_TITLE));if(e){var f=e.getValue(),g=e.getItemId();if(""===e.getValue()){if(g==b.id&&(f!=b.rawtitle||""==b.rawtitle)){e.setValue(b.rawtitle)}}}}},{key:"_refreshSectionCmlist",value:function _refreshSectionCmlist(a){var b,c=a.element,d=null!==(b=c.cmlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.SECTION,c.id),f=null===e||void 0===e?void 0:e.querySelector(this.selectors.SECTION_CMLIST);if(f){this._fixOrder(f,d,this.selectors.CM,this.dettachedCms)}}},{key:"_refreshCourseSectionlist",value:function _refreshCourseSectionlist(a){var b,c=a.element,d=null!==(b=c.sectionlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.COURSE_SECTIONLIST);if(e){this._fixOrder(e,d,this.selectors.SECTION,this.dettachedSections)}}},{key:"_fixOrder",value:function _fixOrder(a,b,c,d){var e=this;if(!b.length){a.classList.add("hidden");a.innerHTML="";return}a.classList.remove("hidden");b.forEach(function(b,f){var g,h=null!==(g=e.getElement(c,b))&&void 0!==g?g:d[b],i=a.children[f];if(i===void 0){a.append(h);return}if(i!==h){a.insertBefore(h,i)}});while(a.children.length>b.length){var f,g,h=a.lastChild;d[null!==(f=null===h||void 0===h?void 0:null===(g=h.dataset)||void 0===g?void 0:g.id)&&void 0!==f?f:0]=h;a.removeChild(h)}}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=r;return a.default});
//# sourceMappingURL=content.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
define ("core_courseformat/local/courseeditor/dndcmitem",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(c,a);var e=i(c);function c(){d(this,c);return e.apply(this,arguments)}f(c,[{key:"configDragDrop",value:function configDragDrop(a){this.id=a;if(this.reactive.isEditing&&this.reactive.supportComponents){this.dragdrop=new b.DragDrop(this);this.classes=this.dragdrop.getClasses()}}},{key:"destroy",value:function destroy(){if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"getDraggableData",value:function getDraggableData(){var a=this.reactive.getExporter();return a.cmDraggableData(this.reactive.state,this.id)}},{key:"validateDropData",value:function validateDropData(a){return"cm"===(null===a||void 0===a?void 0:a.type)}},{key:"showDropZone",value:function showDropZone(a){if(a.nextcmid!=this.id&&a.id!=this.id){this.element.classList.add(this.classes.DROPUP)}}},{key:"hideDropZone",value:function hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}},{key:"drop",value:function drop(a){if(a.id!=this.id&&a.nextcmid!=this.id){this.reactive.dispatch("cmMove",[a.id],null,this.id)}}}]);return c}(b.BaseComponent);a.default=n;return a.default});
//# sourceMappingURL=dndcmitem.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
define ("core_courseformat/local/courseeditor/dndsection",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(c,a);var e=i(c);function c(){d(this,c);return e.apply(this,arguments)}f(c,[{key:"configState",value:function configState(a){this.id=this.element.dataset.id;this.section=a.section.get(this.id);this.course=a.course}},{key:"configDragDrop",value:function configDragDrop(a){if(this.reactive.isEditing&&this.reactive.supportComponents){this.sectionitem=a;this.dragdrop=new b.DragDrop(this);this.classes=this.dragdrop.getClasses()}}},{key:"destroy",value:function destroy(){if(this.sectionitem!==void 0){this.sectionitem.unregister()}if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"getLastCm",value:function getLastCm(){return null}},{key:"validateDropData",value:function validateDropData(a){if("cm"===(null===a||void 0===a?void 0:a.type)){return!0}if("section"===(null===a||void 0===a?void 0:a.type)){var b=this.course.sectionlist[0];return(null===a||void 0===a?void 0:a.id)!=this.id&&(null===a||void 0===a?void 0:a.id)!=b&&this.id!=b}return!1}},{key:"showDropZone",value:function showDropZone(a){if("cm"==a.type){var b;null===(b=this.getLastCm())||void 0===b?void 0:b.classList.add(this.classes.DROPDOWN)}if("section"==a.type){if(this.section.number>a.number){this.element.classList.remove(this.classes.DROPUP);this.element.classList.add(this.classes.DROPDOWN)}else{this.element.classList.add(this.classes.DROPUP);this.element.classList.remove(this.classes.DROPDOWN)}}}},{key:"hideDropZone",value:function hideDropZone(){var a;null===(a=this.getLastCm())||void 0===a?void 0:a.classList.remove(this.classes.DROPDOWN);this.element.classList.remove(this.classes.DROPUP);this.element.classList.remove(this.classes.DROPDOWN)}},{key:"drop",value:function drop(a){if("cm"==a.type){this.reactive.dispatch("cmMove",[a.id],this.id)}if("section"==a.type){this.reactive.dispatch("sectionMove",[a.id],this.id)}}}]);return c}(b.BaseComponent);a.default=n;return a.default});
//# sourceMappingURL=dndsection.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
define ("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(c,a);var e=i(c);function c(){d(this,c);return e.apply(this,arguments)}f(c,[{key:"configDragDrop",value:function configDragDrop(a,c,d){this.id=a;if(this.section===void 0){this.section=c.section.get(this.id)}if(this.course===void 0){this.course=c.course}if(0<this.section.number){this.getDraggableData=this._getDraggableData}this.fullregion=d;if(this.reactive.isEditing&&this.reactive.supportComponents){this.dragdrop=new b.DragDrop(this);this.classes=this.dragdrop.getClasses()}}},{key:"destroy",value:function destroy(){if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"_getDraggableData",value:function _getDraggableData(){var a=this.reactive.getExporter();return a.sectionDraggableData(this.reactive.state,this.id)}},{key:"validateDropData",value:function validateDropData(a){if("cm"===(null===a||void 0===a?void 0:a.type)){var b,c=null===(b=this.section)||void 0===b?void 0:b.cmlist[0];return a.id!==c}return!1}},{key:"showDropZone",value:function showDropZone(){this.element.classList.add(this.classes.DROPZONE)}},{key:"hideDropZone",value:function hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}},{key:"drop",value:function drop(a){if("cm"==a.type){var b;this.reactive.dispatch("cmMove",[a.id],this.id,null===(b=this.section)||void 0===b?void 0:b.cmlist[0])}}}]);return c}(b.BaseComponent);a.default=n;return a.default});
//# sourceMappingURL=dndsectionitem.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function c(a){for(var c=1,e;c<arguments.length;c++){e=null!=arguments[c]?arguments[c]:{};if(c%2){b(Object(e),!0).forEach(function(b){d(a,b,e[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(e))}else{b(Object(e)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(e,b))})}}return a}function d(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(b){e(this,a);this.reactive=b}g(a,[{key:"course",value:function course(a){var b,c=this,d={sections:[],editmode:this.reactive.isEditing},e=null!==(b=a.course.sectionlist)&&void 0!==b?b:[];e.forEach(function(b){var e,f=null!==(e=a.section.get(b))&&void 0!==e?e:{},g=c.section(a,f);d.sections.push(g)});d.hassections=0!=d.sections.length;return d}},{key:"section",value:function(a,b){var d,e=this,f=c({},b,{cms:[],isactive:!1}),g=null!==(d=b.cmlist)&&void 0!==d?d:[];g.forEach(function(b){var c=a.cm.get(b),d=e.cm(a,c);f.cms.push(d)});f.hascms=0!=f.cms.length;return f}},{key:"cm",value:function(a,b){var d=c({},b,{isactive:!1});return d}}]);return a}();a.default=h;return a.default});
define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function c(a){for(var c=1,e;c<arguments.length;c++){e=null!=arguments[c]?arguments[c]:{};if(c%2){b(Object(e),!0).forEach(function(b){d(a,b,e[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(e))}else{b(Object(e)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(e,b))})}}return a}function d(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(b){e(this,a);this.reactive=b}g(a,[{key:"course",value:function course(a){var b,c=this,d={sections:[],editmode:this.reactive.isEditing},e=null!==(b=a.course.sectionlist)&&void 0!==b?b:[];e.forEach(function(b){var e,f=null!==(e=a.section.get(b))&&void 0!==e?e:{},g=c.section(a,f);d.sections.push(g)});d.hassections=0!=d.sections.length;return d}},{key:"section",value:function(a,b){var d,e=this,f=c({},b,{cms:[],isactive:!1}),g=null!==(d=b.cmlist)&&void 0!==d?d:[];g.forEach(function(b){var c=a.cm.get(b),d=e.cm(a,c);f.cms.push(d)});f.hascms=0!=f.cms.length;return f}},{key:"cm",value:function(a,b){var d=c({},b,{isactive:!1});return d}},{key:"cmDraggableData",value:function cmDraggableData(a,b){var c=a.cm.get(b);if(!c){return null}var d,e=a.section.get(c.sectionid),f=null===e||void 0===e?void 0:e.cmlist.indexOf(c.id);if(f!==void 0){d=null===e||void 0===e?void 0:e.cmlist[f+1]}return{type:"cm",id:c.id,name:c.name,nextcmid:d}}},{key:"sectionDraggableData",value:function sectionDraggableData(a,b){var c=a.section.get(b);if(!c){return null}return{type:"section",id:c.id,name:c.name,number:c.number}}}]);return a}();a.default=h;return a.default});
//# sourceMappingURL=exporter.min.js.map
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
define ("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function d(a){return function(){var b=this,d=arguments;return new Promise(function(e,f){var i=a.apply(b,d);function g(a){c(i,e,f,g,h,"next",a)}function h(a){c(i,e,f,g,h,"throw",a)}g(void 0)})}}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(){e(this,a)}g(a,[{key:"_callEditWebservice",value:function(){var a=d(regeneratorRuntime.mark(function a(c,d,e){var f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return b.default.call([{methodname:"core_courseformat_update_course",args:{action:c,courseid:d,ids:e}}])[0];case 2:f=a.sent;return a.abrupt("return",JSON.parse(f));case 4:case"end":return a.stop();}}},a)}));return function _callEditWebservice(){return a.apply(this,arguments)}}()},{key:"cmState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("cm_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function cmState(){return a.apply(this,arguments)}}()},{key:"sectionState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("section_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function sectionState(){return a.apply(this,arguments)}}()},{key:"courseState",value:function(){var a=d(regeneratorRuntime.mark(function a(b){var c,d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=b.get("course");a.next=3;return this._callEditWebservice("course_state",c.id);case 3:d=a.sent;b.processUpdates(d);case 5:case"end":return a.stop();}}},a,this)}));return function courseState(){return a.apply(this,arguments)}}()}]);return a}();a.default=h;return a.default});
define ("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function d(a){return function(){var b=this,d=arguments;return new Promise(function(e,f){var i=a.apply(b,d);function g(a){c(i,e,f,g,h,"next",a)}function h(a){c(i,e,f,g,h,"throw",a)}g(void 0)})}}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(){e(this,a)}g(a,[{key:"_callEditWebservice",value:function(){var a=d(regeneratorRuntime.mark(function a(c,d,e,f,g){var h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:h={action:c,courseid:d,ids:e};if(f){h.targetsectionid=f}if(g){h.targetcmid=g}a.next=5;return b.default.call([{methodname:"core_courseformat_update_course",args:h}])[0];case 5:i=a.sent;return a.abrupt("return",JSON.parse(i));case 7:case"end":return a.stop();}}},a)}));return function _callEditWebservice(){return a.apply(this,arguments)}}()},{key:"cmMove",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c,d,e){var f,g;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!(!d&&!e)){a.next=2;break}throw new Error("Mutation cmMove requires targetSectionId or targetCmId");case 2:f=b.get("course");a.next=5;return this._callEditWebservice("cm_move",f.id,c,d,e);case 5:g=a.sent;b.processUpdates(g);case 7:case"end":return a.stop();}}},a,this)}));return function cmMove(){return a.apply(this,arguments)}}()},{key:"sectionMove",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c,d){var e,f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(d){a.next=2;break}throw new Error("Mutation sectionMove requires targetSectionId");case 2:e=b.get("course");a.next=5;return this._callEditWebservice("section_move",e.id,c,d);case 5:f=a.sent;b.processUpdates(f);case 7:case"end":return a.stop();}}},a,this)}));return function sectionMove(){return a.apply(this,arguments)}}()},{key:"cmState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("cm_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function cmState(){return a.apply(this,arguments)}}()},{key:"sectionState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("section_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function sectionState(){return a.apply(this,arguments)}}()},{key:"courseState",value:function(){var a=d(regeneratorRuntime.mark(function a(b){var c,d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=b.get("course");a.next=3;return this._callEditWebservice("course_state",c.id);case 3:d=a.sent;b.processUpdates(d);case 5:case"end":return a.stop();}}},a,this)}));return function courseState(){return a.apply(this,arguments)}}()}]);return a}();a.default=h;return a.default});
//# sourceMappingURL=mutations.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core_courseformat/local/courseindex/cm",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(){this.name="courseindex_cm";this.selectors={};this.id=this.element.dataset.id}},{key:"stateReady",value:function stateReady(){}},{key:"getWatchers",value:function getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove}]}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.BaseComponent);a.default=n;return a.default});
define ("core_courseformat/local/courseindex/cm",["exports","core_courseformat/local/courseeditor/dndcmitem"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(){this.name="courseindex_cm";this.id=this.element.dataset.id}},{key:"stateReady",value:function stateReady(){this.configDragDrop(this.id)}},{key:"getWatchers",value:function getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove}]}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.default);a.default=n;return a.default});
//# sourceMappingURL=cm.min.js.map
@@ -1 +1 @@
{"version":3,"sources":["../../../src/local/courseindex/cm.js"],"names":["Component","name","selectors","id","element","dataset","watch","handler","remove","target","document","getElementById","BaseComponent"],"mappings":"yyDA4BqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,gBAAZ,CAEA,KAAKC,SAAL,CAAiB,EAAjB,CAGA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,+CAmBY,CAEZ,C,iDAEa,CACV,MAAO,CACH,CAACG,KAAK,cAAQ,KAAKH,EAAb,aAAN,CAAkCI,OAAO,CAAE,KAAKC,MAAhD,CADG,CAGV,C,oCAlBWC,C,CAAQP,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBI,OAAO,CAAEM,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBP,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cA3BkCU,e","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 <http://www.gnu.org/licenses/>.\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 <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\n\nexport default class Component extends BaseComponent {\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 };\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 Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate drag and drop soon.\n }\n\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n ];\n }\n\n}\n"],"file":"cm.min.js"}
{"version":3,"sources":["../../../src/local/courseindex/cm.js"],"names":["Component","name","id","element","dataset","configDragDrop","watch","handler","remove","target","selectors","document","getElementById","DndCmItem"],"mappings":"2MA0BA,uD,+nDAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,gBAAZ,CAEA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,+CAmBY,CACT,KAAKG,cAAL,CAAoB,KAAKH,EAAzB,CACH,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,cAAQ,KAAKJ,EAAb,aAAN,CAAkCK,OAAO,CAAE,KAAKC,MAAhD,CADG,CAGV,C,oCAvBWC,C,CAAQC,C,CAAW,CAC3B,MAAO,IAAIV,CAAAA,CAAJ,CAAc,CACjBG,OAAO,CAAEQ,QAAQ,CAACC,cAAT,CAAwBH,CAAxB,CADQ,CAEjBC,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cAxBkCG,S","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 <http://www.gnu.org/licenses/>.\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 <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\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 // 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 Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n this.configDragDrop(this.id);\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 ];\n }\n\n}\n"],"file":"cm.min.js"}
@@ -0,0 +1,2 @@
define ("core_courseformat/local/courseindex/section",["exports","core_courseformat/local/courseindex/sectiontitle","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function g(a){for(var b=1,c;b<arguments.length;b++){c=null!=arguments[b]?arguments[b]:{};if(b%2){f(Object(c),!0).forEach(function(b){h(a,b,c[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(c))}else{f(Object(c)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(c,b))})}}return a}function h(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function i(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function j(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function k(a,b,c){if(b)j(a.prototype,b);if(c)j(a,c);return a}function l(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)m(a,b)}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){return function(){var b=r(a),c;if(q()){var d=r(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return o(this,c)}}function o(a,b){if(b&&("object"===e(b)||"function"==typeof b)){return b}return p(a)}function p(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function q(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function r(a){r=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return r(a)}var s=function(a){l(c,a);var d=n(c);function c(){i(this,c);return d.apply(this,arguments)}k(c,[{key:"create",value:function create(){this.name="courseindex_section";this.selectors={SECTION_ITEM:"[data-for='section_item']",CM_LAST:"[data-for=\"cm\"]:last-child"}}},{key:"stateReady",value:function stateReady(a){this.configState(a);if(this.reactive.isEditing&&this.reactive.supportComponents){var c=new b.default(g({},this,{element:this.getElement(this.selectors.SECTION_ITEM),fullregion:this.element}));this.configDragDrop(c)}}},{key:"getLastCm",value:function getLastCm(){return this.getElement(this.selectors.CM_LAST)}}],[{key:"init",value:function init(a,b){return new c({element:document.getElementById(a),selectors:b})}}]);return c}(c.default);a.default=s;return a.default});
//# sourceMappingURL=section.min.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/courseindex/section.js"],"names":["Component","name","selectors","SECTION_ITEM","CM_LAST","state","configState","reactive","isEditing","supportComponents","titleitem","SectionTitle","element","getElement","fullregion","configDragDrop","target","document","getElementById","DndSection"],"mappings":"sQA0BA,OACA,O,63EAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,qBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,4BADC,CAEbC,OAAO,+BAFM,CAIpB,C,8CAqBUC,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAS,CAAG,GAAIC,UAAJ,MACX,IADW,EAEdC,OAAO,CAAE,KAAKC,UAAL,CAAgB,KAAKX,SAAL,CAAeC,YAA/B,CAFK,CAGdW,UAAU,CAAE,KAAKF,OAHH,GAAlB,CAKA,KAAKG,cAAL,CAAoBL,CAApB,CACH,CACJ,C,6CAOW,CACR,MAAO,MAAKG,UAAL,CAAgB,KAAKX,SAAL,CAAeE,OAA/B,CACV,C,oCAjCWY,C,CAAQd,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBY,OAAO,CAAEK,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBd,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cA3BkCiB,S","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {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 Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n}\n"],"file":"section.min.js"}
@@ -0,0 +1,2 @@
define ("core_courseformat/local/courseindex/sectiontitle",["exports","core_courseformat/local/courseeditor/dndsectionitem"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(a){this.name="courseindex_sectiontitle";this.id=a.id;this.section=a.section;this.course=a.course;this.fullregion=a.fullregion;if(0<this.section.number){this.getDraggableData=this._getDraggableData}}},{key:"stateReady",value:function stateReady(a){this.configDragDrop(this.id,a,this.fullregion)}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.default);a.default=n;return a.default});
//# sourceMappingURL=sectiontitle.min.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/courseindex/sectiontitle.js"],"names":["Component","descriptor","name","id","section","course","fullregion","number","getDraggableData","_getDraggableData","state","configDragDrop","target","selectors","element","document","getElementById","DndSectionItem"],"mappings":"0NA0BA,uD,+nDAEqBA,CAAAA,C,8HAOVC,C,CAAY,CAEf,KAAKC,IAAL,CAAY,0BAAZ,CAEA,KAAKC,EAAL,CAAUF,CAAU,CAACE,EAArB,CACA,KAAKC,OAAL,CAAeH,CAAU,CAACG,OAA1B,CACA,KAAKC,MAAL,CAAcJ,CAAU,CAACI,MAAzB,CACA,KAAKC,UAAL,CAAkBL,CAAU,CAACK,UAA7B,CAGA,GAA0B,CAAtB,MAAKF,OAAL,CAAaG,MAAjB,CAA6B,CACzB,KAAKC,gBAAL,CAAwB,KAAKC,iBAChC,CACJ,C,8CAqBUC,C,CAAO,CACd,KAAKC,cAAL,CAAoB,KAAKR,EAAzB,CAA6BO,CAA7B,CAAoC,KAAKJ,UAAzC,CACH,C,oCAdWM,C,CAAQC,C,CAAW,CAC3B,MAAO,IAAIb,CAAAA,CAAJ,CAAc,CACjBc,OAAO,CAAEC,QAAQ,CAACC,cAAT,CAAwBJ,CAAxB,CADQ,CAEjBC,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cAlCkCI,S","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Course index section title component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/sectiontitle\n * @class core_courseformat/local/courseindex/sectiontitle\n * @copyright 2021 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';\n\nexport default class Component extends DndSectionItem {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'courseindex_sectiontitle';\n\n this.id = descriptor.id;\n this.section = descriptor.section;\n this.course = descriptor.course;\n this.fullregion = descriptor.fullregion;\n\n // Prevent topic zero from being draggable.\n if (this.section.number > 0) {\n this.getDraggableData = this._getDraggableData;\n }\n }\n\n /**\n * Static method to create a component instance form the mustahce 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 Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configDragDrop(this.id, state, this.fullregion);\n }\n}\n"],"file":"sectiontitle.min.js"}
+1
View File
@@ -54,6 +54,7 @@ function dispatchStateChangedEvent(detail, target) {
* @param {number} courseId the course id
* @param {setup} setup format, page and course settings
* @property {boolean} setup.editing if the page is in edit mode
* @property {boolean} setup.supportscomponents if the format supports components for content
*/
export const setViewFormat = (courseId, setup) => {
const editor = getCourseEditor(courseId);
+230
View File
@@ -0,0 +1,230 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index main component.
*
* @module core_courseformat/local/content
* @class core_courseformat/local/content
* @copyright 2020 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent} from 'core/reactive';
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
import inplaceeditable from 'core/inplace_editable';
// Course actions is needed for actions that are not migrated to components.
import courseActions from 'core_course/actions';
export default class Component extends BaseComponent {
/**
* Constructor hook.
*/
create() {
// Optional component name for debugging.
this.name = 'course_format';
// Default query selectors.
this.selectors = {
SECTION: `[data-for='section']`,
SECTION_ITEM: `[data-for='section_item']`,
SECTION_TITLE: `[data-for='section_title']`,
SECTION_CMLIST: `[data-for='cmlist']`,
COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,
CM: `[data-for='cmitem']`,
};
// Array to save dettached elements during element resorting.
this.dettachedCms = {};
this.dettachedSections = {};
}
/**
* Static method to create a component instance form the mustahce template.
*
* @param {string} target the DOM main element or its ID
* @param {object} selectors optional css selector overrides
* @return {Component}
*/
static init(target, selectors) {
return new Component({
element: document.getElementById(target),
reactive: getCurrentCourseEditor(),
selectors,
});
}
/**
* Return the component watchers.
*
* @returns {Array} of watchers
*/
getWatchers() {
// Check if the course format is compatible with reactive components.
if (!this.reactive.supportComponents) {
return [];
}
return [
// State changes that require to reload some course modules.
{watch: `cm.visible:updated`, handler: this._reloadCm},
// Update section number and title.
{watch: `section.number:updated`, handler: this._refreshSectionNumber},
// Sections and cm sorting.
{watch: `transaction:start`, handler: this._startProcessing},
{watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},
{watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},
];
}
/**
* Reload a course module.
*
* Most course module HTML is still strongly backend dependant.
* Some changes require to get a new version af the module.
*
* @param {Object} update the state update data
*/
_reloadCm({element}) {
const cmitem = this.getElement(this.selectors.CM, element.id);
if (cmitem) {
courseActions.refreshModule(cmitem, element.id);
}
}
/**
* Setup the component to start a transaction.
*
* Some of the course actions replaces the current DOM element with a new one before updating the
* course state. This means the component cannot preload any index properly until the transaction starts.
*
*/
_startProcessing() {
// During a section or cm sorting, some elements could be dettached from the DOM and we
// need to store somewhare in case they are needed later.
this.dettachedCms = {};
this.dettachedSections = {};
}
/**
* Update a course section when the section number changes.
*
* The courseActions module used for most course section tools still depends on css classes and
* section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh
* the
*
* Course formats can override the section title rendering so the frontend depends heavily on backend
* rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.
*
* @param {Object} details the update details.
*/
_refreshSectionNumber({element}) {
// Find the element.
const target = this.getElement(this.selectors.SECTION, element.id);
if (!target) {
throw new Error(`Unkown section with ID ${element.id}`);
}
// Update section numbers in all data, css and YUI attributes.
target.id = `section-${element.number}`;
// YUI uses section number as section id in data-sectionid, in principle if a format use components
// don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin
// use it for legacy purposes.
target.dataset.sectionid = element.number;
// The data-number is the attribute used by components to store the section number.
target.dataset.number = element.number;
// Update title and title inplace editable, if any.
const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_TITLE));
if (inplace) {
// The course content HTML can be modified at any moment, so the function need to do some checkings
// to make sure the inplace editable still represents the same itemid.
const currentvalue = inplace.getValue();
const currentitemid = inplace.getItemId();
// Unnamed sections must be recalculated.
if (inplace.getValue() === '') {
// The value to send can be an empty value if it is a default name.
if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {
inplace.setValue(element.rawtitle);
}
}
}
}
/**
* Refresh a section cm list.
*
* @param {Object} details the update details.
*/
_refreshSectionCmlist({element}) {
const cmlist = element.cmlist ?? [];
const section = this.getElement(this.selectors.SECTION, element.id);
const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);
if (listparent) {
this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms);
}
}
/**
* Refresh the section list.
*
* @param {Object} details the update details.
*/
_refreshCourseSectionlist({element}) {
const sectionlist = element.sectionlist ?? [];
const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);
if (listparent) {
this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections);
}
}
/**
* Fix/reorder the section or cms order.
*
* @param {Element} container the HTML element to reorder.
* @param {Array} neworder an array with the ids order
* @param {string} selector the element selector
* @param {Object} dettachedelements a list of dettached elements
*/
_fixOrder(container, neworder, selector, dettachedelements) {
// Empty lists should not be visible.
if (!neworder.length) {
container.classList.add('hidden');
container.innerHTML = '';
return;
}
// Grant the list is visible (in case it was empty).
container.classList.remove('hidden');
// Move the elements in order at the beginning of the list.
neworder.forEach((itemid, index) => {
const item = this.getElement(selector, itemid) ?? dettachedelements[itemid];
// Get the current elemnt at that position.
const currentitem = container.children[index];
if (currentitem === undefined) {
container.append(item);
return;
}
if (currentitem !== item) {
container.insertBefore(item, currentitem);
}
});
// Remove the remaining elements.
while (container.children.length > neworder.length) {
const lastchild = container.lastChild;
dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;
container.removeChild(lastchild);
}
}
}
@@ -47,6 +47,7 @@ export default class extends Reactive {
// Default view format setup.
this._editing = false;
this._supportscomponents = false;
this.courseId = courseId;
@@ -68,9 +69,11 @@ export default class extends Reactive {
*
* @param {Object} setup format, page and course settings
* @property {boolean} setup.editing if the page is in edit mode
* @property {boolean} setup.supportscomponents if the format supports components for content
*/
setViewFormat(setup) {
this._editing = setup.editing ?? false;
this._supportscomponents = setup.supportscomponents ?? false;
}
/**
@@ -116,6 +119,15 @@ export default class extends Reactive {
return new Exporter(this);
}
/**
* Return if the current course support components to refresh the content.
*
* @returns {boolean} if the current content support components
*/
get supportComponents() {
return this._supportscomponents ?? false;
}
/**
* Dispatch a change in the state.
*
@@ -0,0 +1,113 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index cm component.
*
* This component is used to control specific course modules interactions like drag and drop
* in both course index and course content.
*
* @module core_courseformat/local/courseeditor/dndcmitem
* @class core_courseformat/local/courseeditor/dndcmitem
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent, DragDrop} from 'core/reactive';
export default class extends BaseComponent {
/**
* Configure the component drag and drop.
*
* @param {number} cmid course module id
*/
configDragDrop(cmid) {
this.id = cmid;
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init element drag and drop.
this.dragdrop = new DragDrop(this);
// Save dropzone classes.
this.classes = this.dragdrop.getClasses();
}
}
/**
* Remove all subcomponents dependencies.
*/
destroy() {
if (this.dragdrop !== undefined) {
this.dragdrop.unregister();
}
}
// Drag and drop methods.
/**
* Get the draggable data of this component.
*
* @returns {Object} exported course module drop data
*/
getDraggableData() {
const exporter = this.reactive.getExporter();
return exporter.cmDraggableData(this.reactive.state, this.id);
}
/**
* Validate if the drop data can be dropped over the component.
*
* @param {Object} dropdata the exported drop data.
* @returns {boolean}
*/
validateDropData(dropdata) {
return dropdata?.type === 'cm';
}
/**
* Display the component dropzone.
*
* @param {Object} dropdata the accepted drop data
*/
showDropZone(dropdata) {
// If we are the next cmid of the dragged element we accept the drop because otherwise it
// will get captured by the section. However, we won't trigger any mutation.
if (dropdata.nextcmid != this.id && dropdata.id != this.id) {
this.element.classList.add(this.classes.DROPUP);
}
}
/**
* Hide the component dropzone.
*/
hideDropZone() {
this.element.classList.remove(this.classes.DROPUP);
}
/**
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
*/
drop(dropdata) {
// Call the move mutation if necessary.
if (dropdata.id != this.id && dropdata.nextcmid != this.id) {
this.reactive.dispatch('cmMove', [dropdata.id], null, this.id);
}
}
}
@@ -0,0 +1,146 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section component.
*
* This component is used to control specific course section interactions like drag and drop
* in both course index and course content.
*
* @module core_courseformat/local/courseeditor/dndsection
* @class core_courseformat/local/courseeditor/dndsection
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent, DragDrop} from 'core/reactive';
export default class extends BaseComponent {
/**
* Save some values form the state.
*
* @param {Object} state the current state
*/
configState(state) {
this.id = this.element.dataset.id;
this.section = state.section.get(this.id);
this.course = state.course;
}
/**
* Register state values and the drag and drop subcomponent.
*
* @param {BaseComponent} sectionitem section item component
*/
configDragDrop(sectionitem) {
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init the inner dragable element.
this.sectionitem = sectionitem;
// Init the dropzone.
this.dragdrop = new DragDrop(this);
// Save dropzone classes.
this.classes = this.dragdrop.getClasses();
}
}
/**
* Remove all subcomponents dependencies.
*/
destroy() {
if (this.sectionitem !== undefined) {
this.sectionitem.unregister();
}
if (this.dragdrop !== undefined) {
this.dragdrop.unregister();
}
}
/**
* Get the last CM element of that section.
*
* @returns {element|null} the las course module element of the section.
*/
getLastCm() {
return null;
}
// Drag and drop methods.
/**
* Validate if the drop data can be dropped over the component.
*
* @param {Object} dropdata the exported drop data.
* @returns {boolean}
*/
validateDropData(dropdata) {
// We accept any course module.
if (dropdata?.type === 'cm') {
return true;
}
// We accept any section bu the section 0 or ourself
if (dropdata?.type === 'section') {
const sectionzeroid = this.course.sectionlist[0];
return dropdata?.id != this.id && dropdata?.id != sectionzeroid && this.id != sectionzeroid;
}
return false;
}
/**
* Display the component dropzone.
*
* @param {Object} dropdata the accepted drop data
*/
showDropZone(dropdata) {
if (dropdata.type == 'cm') {
this.getLastCm()?.classList.add(this.classes.DROPDOWN);
}
if (dropdata.type == 'section') {
// The relative move of section depends on the section number.
if (this.section.number > dropdata.number) {
this.element.classList.remove(this.classes.DROPUP);
this.element.classList.add(this.classes.DROPDOWN);
} else {
this.element.classList.add(this.classes.DROPUP);
this.element.classList.remove(this.classes.DROPDOWN);
}
}
}
/**
* Hide the component dropzone.
*/
hideDropZone() {
this.getLastCm()?.classList.remove(this.classes.DROPDOWN);
this.element.classList.remove(this.classes.DROPUP);
this.element.classList.remove(this.classes.DROPDOWN);
}
/**
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
*/
drop(dropdata) {
// Call the move mutation.
if (dropdata.type == 'cm') {
this.reactive.dispatch('cmMove', [dropdata.id], this.id);
}
if (dropdata.type == 'section') {
this.reactive.dispatch('sectionMove', [dropdata.id], this.id);
}
}
}
@@ -0,0 +1,129 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section title draggable component.
*
* This component is used to control specific course section interactions like drag and drop
* in both course index and course content.
*
* @module core_courseformat/local/courseeditor/dndsectionitem
* @class core_courseformat/local/courseeditor/dndsectionitem
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent, DragDrop} from 'core/reactive';
export default class extends BaseComponent {
/**
* Initial state ready method.
*
* @param {number} sectionid the section id
* @param {Object} state the initial state
* @param {Element} fullregion the complete section region to mark as dragged
*/
configDragDrop(sectionid, state, fullregion) {
this.id = sectionid;
if (this.section === undefined) {
this.section = state.section.get(this.id);
}
if (this.course === undefined) {
this.course = state.course;
}
// Prevent topic zero from being draggable.
if (this.section.number > 0) {
this.getDraggableData = this._getDraggableData;
}
this.fullregion = fullregion;
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init the dropzone.
this.dragdrop = new DragDrop(this);
// Save dropzone classes.
this.classes = this.dragdrop.getClasses();
}
}
/**
* Remove all subcomponents dependencies.
*/
destroy() {
if (this.dragdrop !== undefined) {
this.dragdrop.unregister();
}
}
// Drag and drop methods.
/**
* Get the draggable data of this component.
*
* @returns {Object} exported course module drop data
*/
_getDraggableData() {
const exporter = this.reactive.getExporter();
return exporter.sectionDraggableData(this.reactive.state, this.id);
}
/**
* Validate if the drop data can be dropped over the component.
*
* @param {Object} dropdata the exported drop data.
* @returns {boolean}
*/
validateDropData(dropdata) {
// Course module validation.
if (dropdata?.type === 'cm') {
// The first section element is already there so we can ignore it.
const firstcmid = this.section?.cmlist[0];
return dropdata.id !== firstcmid;
}
return false;
}
/**
* Display the component dropzone.
*
* @param {Object} dropdata the accepted drop data
*/
showDropZone() {
this.element.classList.add(this.classes.DROPZONE);
}
/**
* Hide the component dropzone.
*/
hideDropZone() {
this.element.classList.remove(this.classes.DROPZONE);
}
/**
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
*/
drop(dropdata) {
// Call the move mutation.
if (dropdata.type == 'cm') {
this.reactive.dispatch('cmMove', [dropdata.id], this.id, this.section?.cmlist[0]);
}
}
}
@@ -94,4 +94,59 @@ export default class {
};
return cm;
}
/**
* Generate a dragable cm data structure.
*
* This method is used by any draggable course module element to generate drop data
* for its reactive/dragdrop instance.
*
* @param {*} state the state object
* @param {*} cmid the cours emodule id
* @returns {Object|null}
*/
cmDraggableData(state, cmid) {
const cminfo = state.cm.get(cmid);
if (!cminfo) {
return null;
}
// Drop an activity over the next activity is the same as doing anything.
let nextcmid;
const section = state.section.get(cminfo.sectionid);
const currentindex = section?.cmlist.indexOf(cminfo.id);
if (currentindex !== undefined) {
nextcmid = section?.cmlist[currentindex + 1];
}
return {
type: 'cm',
id: cminfo.id,
name: cminfo.name,
nextcmid,
};
}
/**
* Generate a dragable cm data structure.
*
* This method is used by any draggable section element to generate drop data
* for its reactive/dragdrop instance.
*
* @param {*} state the state object
* @param {*} sectionid the cours section id
* @returns {Object|null}
*/
sectionDraggableData(state, sectionid) {
const sectioninfo = state.section.get(sectionid);
if (!sectioninfo) {
return null;
}
return {
type: 'section',
id: sectioninfo.id,
name: sectioninfo.name,
number: sectioninfo.number,
};
}
}
@@ -34,19 +34,68 @@ export default class {
* @param {string} action
* @param {number} courseId
* @param {array} ids
* @param {number} targetSectionId optional target section id (for moving actions)
* @param {number} targetCmId optional target cm id (for moving actions)
*/
async _callEditWebservice(action, courseId, ids) {
async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {
const args = {
action,
courseid: courseId,
ids,
};
if (targetSectionId) {
args.targetsectionid = targetSectionId;
}
if (targetCmId) {
args.targetcmid = targetCmId;
}
let ajaxresult = await ajax.call([{
methodname: 'core_courseformat_update_course',
args: {
action,
courseid: courseId,
ids,
}
args,
}])[0];
return JSON.parse(ajaxresult);
}
/**
* Move course modules to specific course location.
*
* Note that one of targetSectionId or targetCmId should be provided in order to identify the
* new location:
* - targetCmId: the activities will be located avobe the target cm. The targetSectionId
* value will be ignored in this case.
* - targetSectionId: the activities will be appended to the section. In this case
* targetSectionId should not be present.
*
* @param {StateManager} stateManager the current state manager
* @param {array} cmids the list of cm ids to move
* @param {number} targetSectionId the target section id
* @param {number} targetCmId the target course module id
*/
async cmMove(stateManager, cmids, targetSectionId, targetCmId) {
if (!targetSectionId && !targetCmId) {
throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);
}
const course = stateManager.get('course');
const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);
stateManager.processUpdates(updates);
}
/**
* Move course modules to specific course location.
*
* @param {StateManager} stateManager the current state manager
* @param {array} sectionIds the list of section ids to move
* @param {number} targetSectionId the target section id
*/
async sectionMove(stateManager, sectionIds, targetSectionId) {
if (!targetSectionId) {
throw new Error(`Mutation sectionMove requires targetSectionId`);
}
const course = stateManager.get('course');
const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);
stateManager.processUpdates(updates);
}
/**
* Get updated state data related to some cm ids.
*
@@ -24,9 +24,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent} from 'core/reactive';
import DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';
export default class Component extends BaseComponent {
export default class Component extends DndCmItem {
/**
* Constructor hook.
@@ -34,9 +34,6 @@ export default class Component extends BaseComponent {
create() {
// Optional component name for debugging.
this.name = 'courseindex_cm';
// Default query selectors.
this.selectors = {
};
// We need our id to watch specific events.
this.id = this.element.dataset.id;
}
@@ -59,9 +56,14 @@ export default class Component extends BaseComponent {
* Initial state ready method.
*/
stateReady() {
// Activate drag and drop soon.
this.configDragDrop(this.id);
}
/**
* Component watchers.
*
* @returns {Array} of watchers
*/
getWatchers() {
return [
{watch: `cm[${this.id}]:deleted`, handler: this.remove},
@@ -0,0 +1,86 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section component.
*
* This component is used to control specific course section interactions like drag and drop.
*
* @module core_courseformat/local/courseindex/section
* @class core_courseformat/local/courseindex/section
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';
import DndSection from 'core_courseformat/local/courseeditor/dndsection';
export default class Component extends DndSection {
/**
* Constructor hook.
*/
create() {
// Optional component name for debugging.
this.name = 'courseindex_section';
// Default query selectors.
this.selectors = {
SECTION_ITEM: `[data-for='section_item']`,
CM_LAST: `[data-for="cm"]:last-child`,
};
}
/**
* Static method to create a component instance form the mustahce template.
*
* @param {string} target the DOM main element or its ID
* @param {object} selectors optional css selector overrides
* @return {Component}
*/
static init(target, selectors) {
return new Component({
element: document.getElementById(target),
selectors,
});
}
/**
* Initial state ready method.
*
* @param {Object} state the initial state
*/
stateReady(state) {
this.configState(state);
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init the inner dragable element passing the full section as affected region.
const titleitem = new SectionTitle({
...this,
element: this.getElement(this.selectors.SECTION_ITEM),
fullregion: this.element,
});
this.configDragDrop(titleitem);
}
}
/**
* Get the last CM element of that section.
*
* @returns {element|null}
*/
getLastCm() {
return this.getElement(this.selectors.CM_LAST);
}
}
@@ -0,0 +1,73 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section title component.
*
* This component is used to control specific course section interactions like drag and drop.
*
* @module core_courseformat/local/courseindex/sectiontitle
* @class core_courseformat/local/courseindex/sectiontitle
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';
export default class Component extends DndSectionItem {
/**
* Constructor hook.
*
* @param {Object} descriptor
*/
create(descriptor) {
// Optional component name for debugging.
this.name = 'courseindex_sectiontitle';
this.id = descriptor.id;
this.section = descriptor.section;
this.course = descriptor.course;
this.fullregion = descriptor.fullregion;
// Prevent topic zero from being draggable.
if (this.section.number > 0) {
this.getDraggableData = this._getDraggableData;
}
}
/**
* Static method to create a component instance form the mustahce template.
*
* @param {element|string} target the DOM main element or its ID
* @param {object} selectors optional css selector overrides
* @return {Component}
*/
static init(target, selectors) {
return new Component({
element: document.getElementById(target),
selectors,
});
}
/**
* Initial state ready method.
*
* @param {Object} state the initial state
*/
stateReady(state) {
this.configDragDrop(this.id, state, this.fullregion);
}
}
+15
View File
@@ -494,6 +494,21 @@ abstract class base {
return $ajaxsupport;
}
/**
* Returns true if this course format is compatible with content components.
*
* Using components means the content elements can watch the frontend course state and
* react to the changes. Formats with component compatibility can have more interactions
* without refreshing the page, like having drag and drop from the course index to reorder
* sections and activities.
*
* @return bool if the format is compatible with components.
*/
public function supports_components() {
return false;
}
/**
* Custom action after section has been moved in AJAX mode
*
@@ -64,6 +64,7 @@ class section implements renderable {
'section' => $section->section,
'number' => $section->section,
'title' => $format->get_section_name($section),
'rawtitle' => $section->name,
'cmlist' => [],
'visible' => !empty($section->visible),
'sectionurl' => course_get_url($course, $section->section)->out(),
+157
View File
@@ -17,9 +17,13 @@
namespace core_courseformat;
use core_courseformat\stateupdates;
use cm_info;
use section_info;
use stdClass;
use course_modinfo;
use moodle_exception;
use context_module;
use context_course;
/**
* Contains the core course state actions.
@@ -36,6 +40,159 @@ use moodle_exception;
*/
class stateactions {
/**
* Move course modules to another location in the same course.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids the list of affected course module ids
* @param int $targetsectionid optional target section id
* @param int $targetcmid optional target cm id
*/
public function cm_move(
stateupdates $updates,
stdClass $course,
array $ids,
?int $targetsectionid = null,
?int $targetcmid = null
): void {
// Validate target elements.
if (!$targetsectionid && !$targetcmid) {
throw new moodle_exception("Action cm_move requires targetsectionid or targetcmid");
}
$this->validate_cms($course, $ids, __FUNCTION__);
// Check capabilities on every activity context.
foreach ($ids as $cmid) {
$modcontext = context_module::instance($cmid);
require_capability('moodle/course:manageactivities', $modcontext);
}
$modinfo = get_fast_modinfo($course);
// Target cm has more priority than target section.
if (!empty($targetcmid)) {
$this->validate_cms($course, [$targetcmid], __FUNCTION__);
$targetcm = $modinfo->get_cm($targetcmid);
$targetsection = $modinfo->get_section_info_by_id($targetcm->section, MUST_EXIST);
} else {
$this->validate_sections($course, [$targetsectionid], __FUNCTION__);
$targetcm = null;
$targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
}
// The origin sections must be updated as well.
$originalsections = [];
$cms = $this->get_cm_info($modinfo, $ids);
foreach ($cms as $cm) {
$currentsection = $modinfo->get_section_info_by_id($cm->section, MUST_EXIST);
moveto_module($cm, $targetsection, $targetcm);
$updates->add_cm_put($cm->id);
if ($currentsection->id != $targetsection->id) {
$originalsections[$currentsection->id] = true;
}
// If some of the original sections are also target sections, we don't need to update them.
if (array_key_exists($targetsection->id, $originalsections)) {
unset($originalsections[$targetsection->id]);
}
}
// Use section_state to return the full affected section and activities updated state.
$this->cm_state($updates, $course, $ids, $targetsectionid, $targetcmid);
foreach (array_keys($originalsections) as $sectionid) {
$updates->add_section_put($sectionid);
}
}
/**
* Move course sections to another location in the same course.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids the list of affected course module ids
* @param int $targetsectionid optional target section id
* @param int $targetcmid optional target cm id
*/
public function section_move(
stateupdates $updates,
stdClass $course,
array $ids,
?int $targetsectionid = null,
?int $targetcmid = null
): void {
// Validate target elements.
if (!$targetsectionid) {
throw new moodle_exception("Action cm_move requires targetsectionid");
}
$this->validate_sections($course, $ids, __FUNCTION__);
$coursecontext = context_course::instance($course->id);
require_capability('moodle/course:movesections', $coursecontext);
$modinfo = get_fast_modinfo($course);
// Target section.
$this->validate_sections($course, [$targetsectionid], __FUNCTION__);
$targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
$affectedsections = [$targetsection->section => true];
$sections = $this->get_section_info($modinfo, $ids);
foreach ($sections as $section) {
$affectedsections[$section->section] = true;
move_section_to($course, $section->section, $targetsection->section);
}
// Use section_state to return the section and activities updated state.
$this->section_state($updates, $course, $ids, $targetsectionid);
// All course sections can be renamed because of the resort.
$allsections = $modinfo->get_section_info_all();
foreach ($allsections as $section) {
// Ignore the affected sections because they are already in the updates.
if (isset($affectedsections[$section->section])) {
continue;
}
$updates->add_section_put($section->id);
}
// The section order is at a course level.
$updates->add_course_put();
}
/**
* Extract several cm_info from the course_modinfo.
*
* @param course_modinfo $modinfo the course modinfo.
* @param int[] $ids the course modules $ids
* @return cm_info[] the extracted cm_info objects
*/
protected function get_cm_info (course_modinfo $modinfo, array $ids): array {
$cms = [];
foreach ($ids as $cmid) {
$cms[$cmid] = $modinfo->get_cm($cmid);
}
return $cms;
}
/**
* Extract several section_info from the course_modinfo.
*
* @param course_modinfo $modinfo the course modinfo.
* @param int[] $ids the course modules $ids
* @return section_info[] the extracted section_info objects
*/
protected function get_section_info(course_modinfo $modinfo, array $ids): array {
$sections = [];
foreach ($ids as $sectionid) {
$sections[$sectionid] = $modinfo->get_section_info_by_id($sectionid);
}
return $sections;
}
/**
* Add the update messages of the updated version of any cm and section related to the cm ids.
*
+27 -20
View File
@@ -157,25 +157,32 @@
}
}
}}
<h2 class="accesshide">{{{title}}}</h2>
{{{completionhelp}}}
<ul class="{{format}}">
{{#initialsection}}
{{> core_courseformat/local/content/section }}
{{/initialsection}}
{{#sections}}
{{> core_courseformat/local/content/section }}
{{/sections}}
</ul>
{{#hasnavigation}}
<div class="single-section">
{{#sectionnavigation}} {{> core_courseformat/local/content/sectionnavigation }} {{/sectionnavigation}}
<ul class="{{format}}">
{{#singlesection}}
{{> core_courseformat/local/content/section }}
{{/singlesection}}
<div id="{{uniqid}}-course-format">
<h2 class="accesshide">{{{title}}}</h2>
{{{completionhelp}}}
<ul class="{{format}}" data-for="course_sectionlist">
{{#initialsection}}
{{> core_courseformat/local/content/section }}
{{/initialsection}}
{{#sections}}
{{> core_courseformat/local/content/section }}
{{/sections}}
</ul>
{{#sectionselector}} {{> core_courseformat/local/content/sectionselector }} {{/sectionselector}}
{{#hasnavigation}}
<div class="single-section">
{{#sectionnavigation}} {{> core_courseformat/local/content/sectionnavigation }} {{/sectionnavigation}}
<ul class="{{format}}">
{{#singlesection}}
{{> core_courseformat/local/content/section }}
{{/singlesection}}
</ul>
{{#sectionselector}} {{> core_courseformat/local/content/sectionselector }} {{/sectionselector}}
</div>
{{/hasnavigation}}
{{#numsections}} {{> core_courseformat/local/content/addsection}} {{/numsections}}
</div>
{{/hasnavigation}}
{{#numsections}} {{> core_courseformat/local/content/addsection}} {{/numsections}}
{{#js}}
require(['core_courseformat/local/content'], function(component) {
component.init('{{uniqid}}-course-format');
});
{{/js}}
@@ -74,13 +74,17 @@
}
}}
<li id="section-{{num}}"
class="section main {{#onlysummary}} section-summary {{/onlysummary}} clearfix
{{#ishidden}} hidden {{/ishidden}} {{#iscurrent}} current {{/iscurrent}}
{{#isstealth}} orphaned {{/isstealth}}"
role="region"
aria-labelledby="sectionid-{{id}}-title"
data-sectionid="{{num}}"
data-sectionreturnid="{{sectionreturnid}}">
class="section main {{#onlysummary}} section-summary {{/onlysummary}} clearfix
{{#ishidden}} hidden {{/ishidden}} {{#iscurrent}} current {{/iscurrent}}
{{#isstealth}} orphaned {{/isstealth}}"
role="region"
aria-labelledby="sectionid-{{id}}-title"
data-sectionid="{{num}}"
data-sectionreturnid="{{sectionreturnid}}"
data-for="section"
data-id="{{id}}"
data-number="{{num}}"
>
{{#singleheader}} {{> core_courseformat/local/content/section/header }} {{/singleheader}}
<div class="left side">{{#iscurrent}} {{{currentlink}}} {{/iscurrent}}</div>
<div class="right side">
@@ -36,7 +36,12 @@
"extraclasses": "newmessages"
}
}}
<li class="activity {{module}} modtype_{{module}} {{extraclasses}} {{#hasinfo}}hasinfo{{/hasinfo}}" id="module-{{id}}">
<li
class="activity {{module}} modtype_{{module}} {{extraclasses}} {{#hasinfo}}hasinfo{{/hasinfo}}"
id="module-{{id}}"
data-for="cmitem"
data-id="{{id}}"
>
{{#cmformat}}
{{> core_courseformat/local/content/cm}}
{{/cmformat}}
@@ -56,22 +56,20 @@
{{#showmovehere}}
<p>{{movingstr}} (<a href="{{{cancelcopyurl}}}">{{#str}} cancel {{/str}}</a>)</p>
{{/showmovehere}}
{{#hascms}}
<ul class="section img-text">
{{#cms}}
{{#showmovehere}}
<li class="movehere">
<a href="{{{moveurl}}}" title="{{strmovefull}}" class="movehere"></a>
</li>
{{/showmovehere}}
{{#cmitem}}
{{> core_courseformat/local/content/section/cmitem}}
{{/cmitem}}
{{/cms}}
<ul class="section img-text {{#hascms}} d-block {{/hascms}}" data-for="cmlist">
{{#cms}}
{{#showmovehere}}
<li class="movehere">
<a href="{{{moveurl}}}" title="{{strmovefull}}" class="movehere"></a>
</li>
{{/showmovehere}}
{{#cmitem}}
{{> core_courseformat/local/content/section/cmitem}}
{{/cmitem}}
{{/cms}}
{{#showmovehere}}
<li class="movehere">
<a href="{{{movetosectionurl}}}" title="{{strmovefull}}" class="movehere"></a>
</li>
{{/showmovehere}}
{{/showmovehere}}
</ul>
{{/hascms}}
@@ -27,7 +27,7 @@
}
}}
<h3 class="sectionid-{{id}}-title sectionname">
<h3 class="sectionid-{{id}}-title sectionname" data-for="section_title">
{{#url}}
<a href="{{{url}}}" class="{{#ishidden}} dimmed_text {{/ishidden}}">{{name}}</a>
{{/url}}
@@ -56,6 +56,7 @@
{{{name}}}
</span>
{{/url}}
<span class="dragicon ml-auto">{{#pix}}i/dragdrop{{/pix}}</span>
</li>
{{#js}}
require(['core_courseformat/local/courseindex/cm'], function(component) {
@@ -89,6 +89,7 @@
>
{{{title}}}
</a>
<span class="dragicon ml-auto">{{#pix}}i/dragdrop{{/pix}}</span>
</div>
<div id="courseindexcollapse{{number}}"
class="courseindex-item-content collapse {{#isactive}}show{{/isactive}}"
@@ -100,3 +101,8 @@
</ul>
</div>
</div>
{{#js}}
require(['core_courseformat/local/courseindex/section'], function(component) {
component.init('{{uniqid}}-course-index-section-{{id}}');
});
{{/js}}
+4
View File
@@ -159,6 +159,10 @@ class format_topics extends core_courseformat\base {
return $ajaxsupport;
}
public function supports_components() {
return true;
}
/**
* Loads all of the course sections into the navigation.
*
+2 -1
View File
@@ -3,7 +3,8 @@ This files describes API changes for course formats
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
=== 4.0 ===
* New core_courseformat\uses_course_index() to define whether the course format uses course index or not.
* New core_courseformat\base::uses_course_index() to define whether the course format uses course index or not.
* New core_courseformat\base::supports_components() to specify if the format is compatible with reactive components.
=== 3.10 ===
* Added the missing callback supports_ajax() to format_social.
+4
View File
@@ -160,6 +160,10 @@ class format_weeks extends core_courseformat\base {
return $ajaxsupport;
}
public function supports_components() {
return true;
}
/**
* Loads all of the course sections into the navigation
*
+1
View File
@@ -3308,6 +3308,7 @@ function include_course_editor(course_format $format) {
// Edition mode and some format specs must be passed to the init method.
$setup = (object)[
'editing' => $format->show_editor(),
'supportscomponents' => $format->supports_components(),
];
// All the new editor elements will be loaded after the course is presented and
// the initial course state will be generated using core_course_get_state webservice.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core/reactive",["exports","core/local/reactive/basecomponent","core/local/reactive/reactive"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"BaseComponent",{enumerable:!0,get:function get(){return b.default}});Object.defineProperty(a,"Reactive",{enumerable:!0,get:function get(){return c.default}});b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}});
define ("core/reactive",["exports","core/local/reactive/basecomponent","core/local/reactive/reactive","core/local/reactive/dragdrop"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"BaseComponent",{enumerable:!0,get:function get(){return b.default}});Object.defineProperty(a,"Reactive",{enumerable:!0,get:function get(){return c.default}});Object.defineProperty(a,"DragDrop",{enumerable:!0,get:function get(){return d.default}});b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}});
//# sourceMappingURL=reactive.min.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sources":["../src/reactive.js"],"names":[],"mappings":"4WAuBA,OACA,O","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core/reactive\n * @copyright 2021 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\nimport Reactive from 'core/local/reactive/reactive';\n\nexport {Reactive, BaseComponent};\n"],"file":"reactive.min.js"}
{"version":3,"sources":["../src/reactive.js"],"names":[],"mappings":"seAuBA,OACA,OACA,O","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core/reactive\n * @copyright 2021 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\nimport Reactive from 'core/local/reactive/reactive';\nimport DragDrop from 'core/local/reactive/dragdrop';\n\nexport {Reactive, BaseComponent, DragDrop};\n"],"file":"reactive.min.js"}
+295 -242
View File
@@ -27,7 +27,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery',
define(
['jquery',
'core/ajax',
'core/templates',
'core/notification',
@@ -39,41 +40,24 @@ define(['jquery',
],
function($, ajax, templates, notification, str, cfg, url, autocomplete, Pending) {
$('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {
if (e.type === 'keypress' && e.keyCode !== 13) {
return;
}
var editingEnabledPromise = new Pending('autocomplete-start-editing');
e.stopImmediatePropagation();
e.preventDefault();
var target = $(this),
mainelement = target.closest('[data-inplaceeditable]');
var addSpinner = function(element) {
element.addClass('updating');
var spinner = element.find('img.spinner');
if (spinner.length) {
spinner.show();
} else {
spinner = $('<img/>')
.attr('src', url.imageUrl('i/loading_small'))
.addClass('spinner').addClass('smallicon')
;
element.append(spinner);
}
};
var removeSpinner = function(element) {
const removeSpinner = function(element) {
element.removeClass('updating');
element.find('img.spinner').hide();
};
var updateValue = function(mainelement, value) {
/**
* Update an inplace editable value.
*
* @param {Jquery} mainelement the element to update
* @param {string} value the new value
* @param {bool} silent if true the change won't alter the current page focus
*/
const updateValue = function(mainelement, value, silent) {
var pendingId = [
mainelement.attr('data-itemid'),
mainelement.attr('data-component'),
mainelement.attr('data-itemtype'),
].join('-');
mainelement.attr('data-itemid'),
mainelement.attr('data-component'),
mainelement.attr('data-itemtype'),
].join('-');
var pendingPromise = new Pending(pendingId);
addSpinner(mainelement);
@@ -86,91 +70,161 @@ define(['jquery',
value: value,
},
}])[0]
.then(function(data) {
return templates.render('core/inplace_editable', data)
.then(function(html, js) {
var oldvalue = mainelement.attr('data-value');
var newelement = $(html);
templates.replaceNode(mainelement, newelement, js);
newelement.find('[data-inplaceeditablelink]').focus();
newelement.trigger({
type: 'updated',
ajaxreturn: data,
oldvalue: oldvalue,
});
.then(function(data) {
return templates.render('core/inplace_editable', data)
.then(function(html, js) {
var oldvalue = mainelement.attr('data-value');
var newelement = $(html);
templates.replaceNode(mainelement, newelement, js);
if (!silent) {
newelement.find('[data-inplaceeditablelink]').focus();
}
newelement.trigger({
type: 'updated',
ajaxreturn: data,
oldvalue: oldvalue,
});
return;
});
})
.then(function() {
return pendingPromise.resolve();
})
.fail(function(ex) {
var e = $.Event('updatefailed', {
return;
});
})
.then(function() {
return pendingPromise.resolve();
})
.fail(function(ex) {
var e = $.Event('updatefailed', {
exception: ex,
newvalue: value
});
removeSpinner(mainelement);
M.util.js_complete(pendingId);
mainelement.trigger(e);
if (!e.isDefaultPrevented()) {
notification.exception(ex);
removeSpinner(mainelement);
M.util.js_complete(pendingId);
mainelement.trigger(e);
if (!e.isDefaultPrevented()) {
notification.exception(ex);
}
});
};
const addSpinner = function(element) {
element.addClass('updating');
var spinner = element.find('img.spinner');
if (spinner.length) {
spinner.show();
} else {
spinner = $('<img/>')
.attr('src', url.imageUrl('i/loading_small'))
.addClass('spinner').addClass('smallicon')
;
element.append(spinner);
}
};
$('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {
if (e.type === 'keypress' && e.keyCode !== 13) {
return;
}
var editingEnabledPromise = new Pending('autocomplete-start-editing');
e.stopImmediatePropagation();
e.preventDefault();
var target = $(this),
mainelement = target.closest('[data-inplaceeditable]');
var turnEditingOff = function(el) {
el.find('input').off();
el.find('select').off();
el.html(el.attr('data-oldcontent'));
el.removeAttr('data-oldcontent');
el.removeClass('inplaceeditingon');
el.find('[data-inplaceeditablelink]').focus();
};
var turnEditingOffEverywhere = function() {
$('span.inplaceeditable.inplaceeditingon').each(function() {
turnEditingOff($(this));
});
};
var uniqueId = function(prefix, idlength) {
var uniqid = prefix,
i;
for (i = 0; i < idlength; i++) {
uniqid += String(Math.floor(Math.random() * 10));
}
});
};
// Make sure this ID is not already taken by an existing element.
if ($("#" + uniqid).length === 0) {
return uniqid;
}
return uniqueId(prefix, idlength);
};
var turnEditingOff = function(el) {
el.find('input').off();
el.find('select').off();
el.html(el.attr('data-oldcontent'));
el.removeAttr('data-oldcontent');
el.removeClass('inplaceeditingon');
el.find('[data-inplaceeditablelink]').focus();
};
var turnEditingOffEverywhere = function() {
$('span.inplaceeditable.inplaceeditingon').each(function() {
turnEditingOff($(this));
});
};
var uniqueId = function(prefix, idlength) {
var uniqid = prefix,
i;
for (i = 0; i < idlength; i++) {
uniqid += String(Math.floor(Math.random() * 10));
}
// Make sure this ID is not already taken by an existing element.
if ($("#" + uniqid).length === 0) {
return uniqid;
}
return uniqueId(prefix, idlength);
};
var turnEditingOnText = function(el) {
str.get_string('edittitleinstructions').done(function(s) {
var instr = $('<span class="editinstructions">' + s + '</span>').
var turnEditingOnText = function(el) {
str.get_string('edittitleinstructions').done(function(s) {
var instr = $('<span class="editinstructions">' + s + '</span>').
attr('id', uniqueId('id_editinstructions_', 20)),
inputelement = $('<input type="text"/>').
inputelement = $('<input type="text"/>').
attr('id', uniqueId('id_inplacevalue_', 20)).
attr('value', el.attr('data-value')).
attr('aria-describedby', instr.attr('id')).
addClass('ignoredirty').
addClass('form-control'),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>').
attr('for', inputelement.attr('id'));
el.html('').append(instr).append(lbl).append(inputelement);
inputelement.focus();
inputelement.select();
inputelement.on('keyup keypress focusout', function(e) {
if (cfg.behatsiterunning && e.type === 'focusout') {
// Behat triggers focusout too often.
return;
}
if (e.type === 'keypress' && e.keyCode === 13) {
// We need 'keypress' event for Enter because keyup/keydown would catch Enter that was
// pressed in other fields.
var val = inputelement.val();
turnEditingOff(el);
updateValue(el, val);
}
if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
// We need 'keyup' event for Escape because keypress does not work with Escape.
turnEditingOff(el);
}
});
});
};
var turnEditingOnToggle = function(el, newvalue) {
turnEditingOff(el);
updateValue(el, newvalue);
};
var turnEditingOnSelect = function(el, options) {
var i,
inputelement = $('<select></select>').
attr('id', uniqueId('id_inplacevalue_', 20)).
attr('value', el.attr('data-value')).
attr('aria-describedby', instr.attr('id')).
addClass('ignoredirty').
addClass('form-control'),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>').
attr('for', inputelement.attr('id'));
el.html('').append(instr).append(lbl).append(inputelement);
addClass('custom-select'),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
.attr('for', inputelement.attr('id'));
for (i in options) {
inputelement
.append($('<option>')
.attr('value', options[i].key)
.html(options[i].value));
}
inputelement.val(el.attr('data-value'));
el.html('')
.append(lbl)
.append(inputelement);
inputelement.focus();
inputelement.select();
inputelement.on('keyup keypress focusout', function(e) {
inputelement.on('keyup change focusout', function(e) {
if (cfg.behatsiterunning && e.type === 'focusout') {
// Behat triggers focusout too often.
return;
}
if (e.type === 'keypress' && e.keyCode === 13) {
// We need 'keypress' event for Enter because keyup/keydown would catch Enter that was
// pressed in other fields.
if (e.type === 'change') {
var val = inputelement.val();
turnEditingOff(el);
updateValue(el, val);
@@ -180,158 +234,157 @@ define(['jquery',
turnEditingOff(el);
}
});
});
};
};
var turnEditingOnToggle = function(el, newvalue) {
turnEditingOff(el);
updateValue(el, newvalue);
};
var turnEditingOnAutocomplete = function(el, args) {
var i,
inputelement = $('<select></select>').
attr('id', uniqueId('id_inplacevalue_', 20)).
addClass('form-autocomplete-original-select').
addClass('custom-select'),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
.attr('for', inputelement.attr('id')),
options = args.options,
attributes = args.attributes,
saveelement = $('<a href="#"></a>'),
cancelelement = $('<a href="#"></a>');
var turnEditingOnSelect = function(el, options) {
var i,
inputelement = $('<select></select>').
attr('id', uniqueId('id_inplacevalue_', 20)).
addClass('custom-select'),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
.attr('for', inputelement.attr('id'));
for (i in options) {
inputelement
.append($('<option>')
.attr('value', options[i].key)
.html(options[i].value));
}
inputelement.val(el.attr('data-value'));
el.html('')
.append(lbl)
.append(inputelement);
inputelement.focus();
inputelement.select();
inputelement.on('keyup change focusout', function(e) {
if (cfg.behatsiterunning && e.type === 'focusout') {
// Behat triggers focusout too often.
return;
for (i in options) {
inputelement
.append($('<option>')
.attr('value', options[i].key)
.html(options[i].value));
}
if (e.type === 'change') {
var val = inputelement.val();
if (attributes.multiple) {
inputelement.attr('multiple', 'true');
}
inputelement.val(JSON.parse(el.attr('data-value')));
str.get_string('savechanges', 'core').then(function(s) {
return templates.renderPix('e/save', 'core', s);
}).then(function(html) {
saveelement.append(html);
return;
}).fail(notification.exception);
str.get_string('cancel', 'core').then(function(s) {
return templates.renderPix('e/cancel', 'core', s);
}).then(function(html) {
cancelelement.append(html);
return;
}).fail(notification.exception);
el.html('')
.append(lbl)
.append(inputelement)
.append(saveelement)
.append(cancelelement);
inputelement.focus();
inputelement.select();
autocomplete.enhance(inputelement,
attributes.tags,
attributes.ajax,
attributes.placeholder,
attributes.caseSensitive,
attributes.showSuggestions,
attributes.noSelectionString)
.then(function() {
// Focus on the enhanced combobox.
el.find('[role=combobox]').focus();
// Stop eslint nagging.
return;
}).fail(notification.exception);
inputelement.on('keyup', function(e) {
if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
// We need 'keyup' event for Escape because keypress does not work with Escape.
turnEditingOff(el);
}
});
saveelement.on('click', function(e) {
var val = JSON.stringify(inputelement.val());
// We need to empty the node to destroy all event handlers etc.
inputelement.empty();
turnEditingOff(el);
updateValue(el, val);
}
if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
// We need 'keyup' event for Escape because keypress does not work with Escape.
e.preventDefault();
});
cancelelement.on('click', function(e) {
// We need to empty the node to destroy all event handlers etc.
inputelement.empty();
turnEditingOff(el);
e.preventDefault();
});
};
var turnEditingOn = function(el) {
el.addClass('inplaceeditingon');
el.attr('data-oldcontent', el.html());
var type = el.attr('data-type');
var options = el.attr('data-options');
if (type === 'toggle') {
turnEditingOnToggle(el, options);
} else if (type === 'select') {
turnEditingOnSelect(el, $.parseJSON(options));
} else if (type === 'autocomplete') {
turnEditingOnAutocomplete(el, $.parseJSON(options));
} else {
turnEditingOnText(el);
}
});
};
};
var turnEditingOnAutocomplete = function(el, args) {
var i,
inputelement = $('<select></select>').
attr('id', uniqueId('id_inplacevalue_', 20)).
addClass('form-autocomplete-original-select').
addClass('custom-select'),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
.attr('for', inputelement.attr('id')),
options = args.options,
attributes = args.attributes,
saveelement = $('<a href="#"></a>'),
cancelelement = $('<a href="#"></a>');
// Turn editing on for the current element and register handler for Enter/Esc keys.
turnEditingOffEverywhere();
turnEditingOn(mainelement);
editingEnabledPromise.resolve();
for (i in options) {
inputelement
.append($('<option>')
.attr('value', options[i].key)
.html(options[i].value));
}
if (attributes.multiple) {
inputelement.attr('multiple', 'true');
}
inputelement.val(JSON.parse(el.attr('data-value')));
});
str.get_string('savechanges', 'core').then(function(s) {
return templates.renderPix('e/save', 'core', s);
}).then(function(html) {
saveelement.append(html);
return;
}).fail(notification.exception);
str.get_string('cancel', 'core').then(function(s) {
return templates.renderPix('e/cancel', 'core', s);
}).then(function(html) {
cancelelement.append(html);
return;
}).fail(notification.exception);
el.html('')
.append(lbl)
.append(inputelement)
.append(saveelement)
.append(cancelelement);
inputelement.focus();
inputelement.select();
autocomplete.enhance(inputelement,
attributes.tags,
attributes.ajax,
attributes.placeholder,
attributes.caseSensitive,
attributes.showSuggestions,
attributes.noSelectionString)
.then(function() {
// Focus on the enhanced combobox.
el.find('[role=combobox]').focus();
// Stop eslint nagging.
return;
}).fail(notification.exception);
inputelement.on('keyup', function(e) {
if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
// We need 'keyup' event for Escape because keypress does not work with Escape.
turnEditingOff(el);
return {
/**
* Return an object to interact with the current inplace editables at a frontend level.
*
* @param {Element} parent the parent element containing a inplace editable
* @returns {Object|undefined} an object to interact with the inplace element, or undefined
* if no inplace editable is found.
*/
getInplaceEditable: function(parent) {
const element = parent.querySelector(`[data-inplaceeditable]`);
if (!element) {
return undefined;
}
});
saveelement.on('click', function(e) {
var val = JSON.stringify(inputelement.val());
// We need to empty the node to destroy all event handlers etc.
inputelement.empty();
turnEditingOff(el);
updateValue(el, val);
e.preventDefault();
});
cancelelement.on('click', function(e) {
// We need to empty the node to destroy all event handlers etc.
inputelement.empty();
turnEditingOff(el);
e.preventDefault();
});
};
var turnEditingOn = function(el) {
el.addClass('inplaceeditingon');
el.attr('data-oldcontent', el.html());
var type = el.attr('data-type');
var options = el.attr('data-options');
if (type === 'toggle') {
turnEditingOnToggle(el, options);
} else if (type === 'select') {
turnEditingOnSelect(el, $.parseJSON(options));
} else if (type === 'autocomplete') {
turnEditingOnAutocomplete(el, $.parseJSON(options));
} else {
turnEditingOnText(el);
// Return an object to interact with the inplace editable.
return {
element,
/**
* Get the value from the inplace editable.
*
* @returns {string} the current inplace value
*/
getValue: function() {
return this.element.dataset.value;
},
/**
* Force a value change.
*
* @param {string} newvalue the new value
*/
setValue: function(newvalue) {
updateValue($(this.element), newvalue, true);
},
/**
* Return the inplace editable itemid.
*
* @returns {string} the current itemid
*/
getItemId: function() {
return this.element.dataset.itemid;
},
};
}
};
// Turn editing on for the current element and register handler for Enter/Esc keys.
turnEditingOffEverywhere();
turnEditingOn(mainelement);
editingEnabledPromise.resolve();
});
return {};
});
});
+465
View File
@@ -0,0 +1,465 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drag and drop helper component.
*
* This component is used to delegate drag and drop handling.
*
* To delegate the logic to this particular element the component should create a new instance
* passing "this" as param. The component will use all the necessary callbacks and add all the
* necessary listeners to the component element.
*
* Component attributes used by dragdrop module:
* - element: the draggable or dropzone element.
* - (optional) classes: object with alternative CSS classes
* - (optional) fullregion: page element affeted by the elementy dragging. Use this attribute if
* the draggable element affects a bigger region (for example a draggable
* title).
* - (optional) autoconfigDraggable: by default, the component will be draggable if it has a
* getDraggableData method. If this value is false draggable
* property must be defined using setDraggable method.
* - (optional) relativeDrag: by default the drag image is located at point (0,0) relative to the
* mouse position to prevent the mouse from covering it. If this attribute
* is true the drag image will be located at the click offset.
*
* Methods the parent component should have for making it draggable:
*
* - getDraggableData(): Object|data
* Return the data that will be passed to any valid dropzone while it is dragged.
* If the component has this method, the dragdrop module will enable the dragging,
* this is the only required method for dragging.
* If at the dragging moment this method returns a false|null|undefined, the dragging
* actions won't be captured.
*
* - (optional) dragStart(Object dropdata, Event event): void
* - (optional) dragEnd(Object dropdata, Event event): void
* Callbacks dragdrop will call when the element is dragged and getDraggableData
* return some data.
*
* Methods the parent component should have for enabling it as a dropzone:
*
* - validateDropData(Object dropdata): boolean
* If that method exists, the dragdrop module will automathically configure the element as dropzone.
* This method will return true if the dropdata is accepted. In case it returns false, no drag and
* drop event will be listened for this specific dragged dropdata.
*
* - (Optional) showDropZone(Object dropdata, Event event): void
* - (Optional) hideDropZone(Object dropdata, Event event): void
* Methods called when a valid dragged data pass over the element.
*
* - (Optional) drop(Object dropdata, Event event): void
* Called when a valid dragged element is dropped over the element.
*
* Note that none of this methods will be called if validateDropData
* returns a false value.
*
* This module will also add or remove several CSS classes from both dragged elements and dropzones.
* See the "this.classes" in the create method for more details. In case the parent component wants
* to use the same classes, it can use the getClasses method. On the other hand, if the parent
* component has an alternative "classes" attribute, this will override the default drag and drop
* classes.
*
* @module core/local/reactive/dragdrop
* @class core/local/reactive/dragdrop
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import BaseComponent from 'core/local/reactive/basecomponent';
// Map with the dragged element generate by an specific reactive applications.
// Potentially, any component can generate a draggable element to interact with other
// page elements. However, the dragged data is specific and could only interact with
// components of the same reactive instance.
let activeDropData = new Map();
// Drag & Drop API provides the final drop point and incremental movements but we can
// provide also starting points and displacements. Absolute displacements simplifies
// moving components with aboslute position around the page.
let dragStartPoint = {};
export default class extends BaseComponent {
/**
* Constructor hook.
*
* @param {BaseComponent} parent the parent component.
*/
create(parent) {
// Optional component name for debugging.
this.name = `${parent.name ?? 'unkown'}_dragdrop`;
// Default drag and drop classes.
this.classes = Object.assign(
{
// This class indicate a dragging action is active at a page level.
BODYDRAGGING: 'dragging',
// Added when draggable and drop are ready.
DRAGGABLEREADY: 'draggable',
DROPREADY: 'dropready',
// When a valid drag element is over the element.
DRAGOVER: 'dragover',
// When a the component is dragged.
DRAGGING: 'dragging',
// Dropzones classes names.
DROPUP: 'drop-up',
DROPDOWN: 'drop-down',
DROPZONE: 'drop-zone',
// Drag icon class.
DRAGICON: 'dragicon',
},
parent?.classes ?? {}
);
// Add the affected region if any.
this.fullregion = parent.fullregion;
// Keep parent to execute drap and drop handlers.
this.parent = parent;
// Check if parent handle draggable manually.
this.autoconfigDraggable = this.parent.draggable ?? true;
// Drag image relative position.
this.relativeDrag = this.parent.relativeDrag ?? false;
// Sub HTML elements will trigger extra dragEnter and dragOver all the time.
// To prevent that from affecting dropzones, we need to count the enters and leaves.
this.entercount = 0;
// Stores if the droparea is shown or not.
this.dropzonevisible = false;
}
/**
* Return the component drag and drop CSS classes.
*
* @returns {Object} the dragdrop css classes
*/
getClasses() {
return this.classes;
}
/**
* Initial state ready method.
*
* This method will add all the necessary event listeners to the component depending on the
* parent methods.
* - Add drop events to the element if the parent component has validateDropData method.
* - Configure the elements draggable if the parent component has getDraggableData method.
*/
stateReady() {
// Add drop events to the element if the parent component has dropable types.
if (typeof this.parent.validateDropData === 'function') {
this.element.classList.add(this.classes.DROPREADY);
this.addEventListener(this.element, 'dragenter', this._dragEnter);
this.addEventListener(this.element, 'dragleave', this._dragLeave);
this.addEventListener(this.element, 'dragover', this._dragOver);
this.addEventListener(this.element, 'drop', this._drop);
}
// Configure the elements draggable if the parent component has dragable data.
if (this.autoconfigDraggable && typeof this.parent.getDraggableData === 'function') {
this.setDraggable(true);
}
}
/**
* Enable or disable the draggable property.
*
* @param {bool} value the new draggable value
*/
setDraggable(value) {
if (typeof this.parent.getDraggableData !== 'function') {
throw new Error(`Draggable components must have a getDraggableData method`);
}
this.element.setAttribute('draggable', value);
if (value) {
this.addEventListener(this.element, 'dragstart', this._dragStart);
this.addEventListener(this.element, 'dragend', this._dragEnd);
this.element.classList.add(this.classes.DRAGGABLEREADY);
} else {
this.removeEventListener(this.element, 'dragstart', this._dragStart);
this.removeEventListener(this.element, 'dragend', this._dragEnd);
this.element.classList.remove(this.classes.DRAGGABLEREADY);
}
}
/**
* Drag start event handler.
*
* This method will generate the current dropable data. This data is the one used to determine
* if a droparea accepts the dropping or not.
*
* @param {Event} event the event.
*/
_dragStart(event) {
const dropdata = this.parent.getDraggableData();
if (!dropdata) {
return;
}
// Save the starting point.
dragStartPoint = {
pageX: event.pageX,
pageY: event.pageY,
};
// If the drag event is accepted we prevent any other draggable element from interfiering.
event.stopPropagation();
// Save the drop data of the current reactive intance.
activeDropData.set(this.reactive, dropdata);
// Add some CSS classes to indicate the state.
document.body.classList.add(this.classes.BODYDRAGGING);
this.element.classList.add(this.classes.DRAGGING);
this.fullregion?.classList.add(this.classes.DRAGGING);
// Force the drag image. This makes the UX more consistent in case the
// user dragged an internal element like a link or some other element.
let dragImage = this.element;
if (this.parent.setDragImage !== undefined) {
const customImage = this.parent.setDragImage(dropdata, event);
if (customImage) {
dragImage = customImage;
}
}
// Define the image position relative to the mouse.
const position = {x: 0, y: 0};
if (this.relativeDrag) {
position.x = event.offsetX;
position.y = event.offsetY;
}
event.dataTransfer.setDragImage(dragImage, position.x, position.y);
this._callParentMethod('dragStart', dropdata, event);
}
/**
* Drag end event handler.
*
* @param {Event} event the event.
*/
_dragEnd(event) {
const dropdata = activeDropData.get(this.reactive);
if (!dropdata) {
return;
}
// Remove the current dropdata.
activeDropData.delete(this.reactive);
// Remove the dragging classes.
document.body.classList.remove(this.classes.BODYDRAGGING);
this.element.classList.remove(this.classes.DRAGGING);
this.fullregion?.classList.remove(this.classes.DRAGGING);
// We add the total movement to the event in case the component
// wants to move its absolute position.
this._addEventTotalMovement(event);
this._callParentMethod('dragEnd', dropdata, event);
}
/**
* Drag enter event handler.
*
* The JS drag&drop API triggers several dragenter events on the same element because it bubbles the
* child events as well. To prevent this form affecting the dropzones display, this methods use
* "entercount" to determine if it's one extra child event or a valid one.
*
* @param {Event} event the event.
*/
_dragEnter(event) {
const dropdata = this._processEvent(event);
if (dropdata) {
this.entercount++;
this.element.classList.add(this.classes.DRAGOVER);
if (this.entercount == 1 && !this.dropzonevisible) {
this.dropzonevisible = true;
this.element.classList.add(this.classes.DRAGOVER);
this._callParentMethod('showDropZone', dropdata, event);
}
}
}
/**
* Drag over event handler.
*
* We only use dragover event when a draggable action starts inside a valid dropzone. In those cases
* the API won't trigger any dragEnter because the dragged alement was already there. We use the
* dropzonevisible to determine if the component needs to display the dropzones or not.
*
* @param {Event} event the event.
*/
_dragOver(event) {
const dropdata = this._processEvent(event);
if (dropdata && !this.dropzonevisible) {
this.dropzonevisible = true;
this.element.classList.add(this.classes.DRAGOVER);
this._callParentMethod('showDropZone', dropdata, event);
}
}
/**
* Drag over leave handler.
*
* The JS drag&drop API triggers several dragleave events on the same element because it bubbles the
* child events as well. To prevent this form affecting the dropzones display, this methods use
* "entercount" to determine if it's one extra child event or a valid one.
*
* @param {Event} event the event.
*/
_dragLeave(event) {
const dropdata = this._processEvent(event);
if (dropdata) {
this.entercount--;
if (this.entercount == 0 && this.dropzonevisible) {
this.dropzonevisible = false;
this.element.classList.remove(this.classes.DRAGOVER);
this._callParentMethod('hideDropZone', dropdata, event);
}
}
}
/**
* Drop event handler.
*
* This method will call both hideDropZones and drop methods on the parent component.
*
* @param {Event} event the event.
*/
_drop(event) {
const dropdata = this._processEvent(event);
if (dropdata) {
this.entercount = 0;
if (this.dropzonevisible) {
this.dropzonevisible = false;
this._callParentMethod('hideDropZone', dropdata, event);
}
this.element.classList.remove(this.classes.DRAGOVER);
this._callParentMethod('drop', dropdata, event);
// An accepted drop resets the initial position.
// Save the starting point.
dragStartPoint = {};
}
}
/**
* Process a drag and drop event and delegate logic to the parent component.
*
* @param {Event} event the drag and drop event
* @return {Object|false} the dropdata or null if the event should not be processed
*/
_processEvent(event) {
const dropdata = this._getDropData(event);
if (!dropdata) {
return null;
}
if (this.parent.validateDropData(dropdata)) {
// All accepted drag&drop event must prevent bubbling and defaults, otherwise
// parent dragdrop instances could capture it by mistake.
event.preventDefault();
event.stopPropagation();
this._addEventTotalMovement(event);
return dropdata;
}
return null;
}
/**
* Add the total amout of movement to a mouse event.
*
* @param {MouseEvent} event
*/
_addEventTotalMovement(event) {
if (dragStartPoint.pageX === undefined || event.pageX === undefined) {
return;
}
event.fixedMovementX = event.pageX - dragStartPoint.pageX;
event.fixedMovementY = event.pageY - dragStartPoint.pageY;
event.initialPageX = dragStartPoint.pageX;
event.initialPageY = dragStartPoint.pageY;
// The element possible new top.
const current = this.element.getBoundingClientRect();
// Add the new position fixed position.
event.newFixedTop = current.top + event.fixedMovementY;
event.newFixedLeft = current.left + event.fixedMovementX;
// The affected region possible new top.
if (this.fullregion !== undefined) {
const current = this.fullregion.getBoundingClientRect();
event.newRegionFixedxTop = current.top + event.fixedMovementY;
event.newRegionFixedxLeft = current.left + event.fixedMovementX;
}
}
/**
* Convenient method for calling parent component functions if present.
*
* @param {string} methodname the name of the method
* @param {Object} dropdata the current drop data object
* @param {Event} event the original event
*/
_callParentMethod(methodname, dropdata, event) {
if (typeof this.parent[methodname] === 'function') {
this.parent[methodname](dropdata, event);
}
}
/**
* Get the current dropdata for a specific event.
*
* The browser can generate drag&drop events related to several user interactions:
* - Drag a page elements: this case is registered in the activeDropData map
* - Drag some HTML selections: ignored for now
* - Drag a file over the browser: file drag may appear in the future but for now they are ignored.
*
* @param {Event} event the original event.
* @returns {Object|undefined} with the dragged data (or undefined if none)
*/
_getDropData(event) {
if (this._containsFiles(event)) {
return undefined;
}
return activeDropData.get(this.reactive);
}
/**
* Check if the dragged event contains files.
*
* Files dragging does not generate drop data because they came from outsite the page and the component
* must check it before validating the event.
*
* @param {Event} event the original event.
* @returns {boolean} if the drag dataTransfers contains files.
*/
_containsFiles(event) {
if (event.dataTransfer.types) {
for (var i = 0; i < event.dataTransfer.types.length; i++) {
if (event.dataTransfer.types[i] == "Files") {
return true;
}
}
}
return false;
}
}
+2 -1
View File
@@ -23,5 +23,6 @@
import BaseComponent from 'core/local/reactive/basecomponent';
import Reactive from 'core/local/reactive/reactive';
import DragDrop from 'core/local/reactive/dragdrop';
export {Reactive, BaseComponent};
export {Reactive, BaseComponent, DragDrop};
+31
View File
@@ -5,6 +5,8 @@
// want white default colour.
$bg-inverse-link-color: #fff !default;
$dropzone-border: $gray-900 !default;
$font-size-xs: ($font-size-base * .75) !default;
#region-main {
@@ -2784,3 +2786,32 @@ $scrollbar-track: lighten($primary, 40%);
border-right: $border-width solid $white;
}
}
// Generic dropzones and dragging styles.
body.dragging {
.drop-zone {
border: 2px dashed $dropzone-border;
}
.drop-up {
border-top: 2px solid $dropzone-border;
}
.drop-down {
border-bottom: 2px solid $dropzone-border;
}
.dragging {
opacity: .6;
}
}
.dragicon {
visibility: hidden;
}
.draggable:hover .dragicon {
visibility: visible;
cursor: move;
}
+9 -2
View File
@@ -56,8 +56,15 @@ $courseindex-item-current: $primary !default;
}
}
.courseindex-section.current {
border-left: solid 3px $courseindex-item-current;
.courseindex-section {
&.current {
border-left: solid 3px $courseindex-item-current;
}
&.dropready .courseindex-item-content {
/* Extra dropzone space */
padding-bottom: 1em;
}
}
.d-flex-noedit {
+23
View File
@@ -11944,6 +11944,25 @@ input[disabled] {
background-color: #a8d2f8;
border-right: 1px solid #fff; }
body.dragging .drop-zone {
border: 2px dashed #212529; }
body.dragging .drop-up {
border-top: 2px solid #212529; }
body.dragging .drop-down {
border-bottom: 2px solid #212529; }
body.dragging .dragging {
opacity: .6; }
.dragicon {
visibility: hidden; }
.draggable:hover .dragicon {
visibility: visible;
cursor: move; }
.icon {
font-size: 16px;
width: 16px;
@@ -20131,6 +20150,10 @@ div.editor_atto_toolbar button .icon {
.courseindex .courseindex-section.current {
border-left: solid 3px #0f6fc5; }
.courseindex .courseindex-section.dropready .courseindex-item-content {
/* Extra dropzone space */
padding-bottom: 1em; }
.courseindex .d-flex-noedit {
display: none; }
+23
View File
@@ -12165,6 +12165,25 @@ input[disabled] {
background-color: #a8d2f8;
border-right: 1px solid #fff; }
body.dragging .drop-zone {
border: 2px dashed #212529; }
body.dragging .drop-up {
border-top: 2px solid #212529; }
body.dragging .drop-down {
border-bottom: 2px solid #212529; }
body.dragging .dragging {
opacity: .6; }
.dragicon {
visibility: hidden; }
.draggable:hover .dragicon {
visibility: visible;
cursor: move; }
.icon {
font-size: 16px;
width: 16px;
@@ -20323,6 +20342,10 @@ div.editor_atto_toolbar button .icon {
.courseindex .courseindex-section.current {
border-left: solid 3px #0f6fc5; }
.courseindex .courseindex-section.dropready .courseindex-item-content {
/* Extra dropzone space */
padding-bottom: 1em; }
.courseindex .d-flex-noedit {
display: none; }