Merge branch 'MDL-72569-master' of https://github.com/ferranrecio/moodle
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -607,6 +607,32 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the focused element path in an activity if any.
|
||||
*
|
||||
* This method is used to restore focus when the activity HTML is refreshed.
|
||||
* Only the main course editor elements can be refocused as they are always present
|
||||
* even if the activity content changes.
|
||||
*
|
||||
* @param {String} id the element id the activity element
|
||||
* @return {String|undefined} the inner path of the focused element or undefined
|
||||
*/
|
||||
const getActivityFocusedElement = function(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element || !element.contains(document.activeElement)) {
|
||||
return undefined;
|
||||
}
|
||||
// Check if the actions menu toggler is focused.
|
||||
if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {
|
||||
return `${SELECTOR.ACTIONAREA} [tabindex="0"]`;
|
||||
}
|
||||
// Return the current element id if any.
|
||||
if (document.activeElement.id) {
|
||||
return `#${document.activeElement.id}`;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).
|
||||
*
|
||||
@@ -616,10 +642,18 @@ define(
|
||||
$('<div>' + activityHTML + '</div>').find(SELECTOR.ACTIVITYLI).each(function() {
|
||||
// Extract id from the new activity html.
|
||||
var id = $(this).attr('id');
|
||||
// Check if the current focused element is inside the activity.
|
||||
let focusedPath = getActivityFocusedElement(id);
|
||||
// Find the existing element with the same id and replace its contents with new html.
|
||||
$(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);
|
||||
// Initialise action menu.
|
||||
initActionMenu(id);
|
||||
// Re-focus the previous elements.
|
||||
if (focusedPath) {
|
||||
const newItem = document.getElementById(id);
|
||||
newItem.querySelector(focusedPath)?.focus();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+1
-1
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/contenttree",["exports","jquery","core/tree","core/normalise"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=e(b);c=e(c);function e(a){return a&&a.__esModule?a:{default:a}}function f(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){f=function(a){return typeof a}}else{f=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return f(a)}function g(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function h(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 i(a,b,c){if(b)h(a.prototype,b);if(c)h(a,c);return a}function j(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)k(a,b)}function k(a,b){k=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return k(a,b)}function l(a){return function(){var b=p(a),c;if(o()){var d=p(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return m(this,c)}}function m(a,b){if(b&&("object"===f(b)||"function"==typeof b)){return b}return n(a)}function n(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function o(){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 p(a){p=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return p(a)}var q=function(a){j(c,a);var e=l(c);function c(a,b,d){var f,h;g(this,c);h=e.call(this,a);h.selectors={SECTION:b.SECTION,TOGGLER:b.TOGGLER,COLLAPSE:b.COLLAPSE,ENTER:null!==(f=b.ENTER)&&void 0!==f?f:b.TOGGLER};if(d){h._getVisibleItems=h.getVisibleItems;h.getVisibleItems=function(){h.refreshVisibleItemsCache();return h._getVisibleItems()}}h.treeRoot.on("hidden.bs.collapse shown.bs.collapse",function(){h.refreshVisibleItemsCache()});h.registerEnterCallback(h.enterCallback.bind(n(h)));return h}i(c,[{key:"getActiveItem",value:function getActiveItem(){var a=this.treeRoot.data("activeItem");if(a){return(0,d.getList)(a)[0]}}},{key:"enterCallback",value:function enterCallback(a){var b=(0,d.getList)(a)[0];if(this.isGroupItem(a)){var c=b.querySelector(this.selectors.ENTER);if("#"!==c.getAttribute("href")){window.location.href=c.getAttribute("href")}c.click()}else{var e=b.querySelector("a");if("#"!==e.getAttribute("href")){window.location.href=e.getAttribute("href")}else{e.click()}}}},{key:"isGroupCollapsed",value:function isGroupCollapsed(a){var b=(0,d.getList)(a)[0],c=b.querySelector("[aria-expanded]");return"false"===c.getAttribute("aria-expanded")}},{key:"toggleGroup",value:function toggleGroup(a){var c,d=a.find(this.selectors.COLLAPSE),e=null!==(c=d.data("target"))&&void 0!==c?c:d.attr("href");if(!e){return}e=e.replace("#","");var f=(0,b.default)("#".concat(e));if(f.length){(0,b.default)("#".concat(e)).collapse("toggle")}}},{key:"expandGroup",value:function expandGroup(a){if(this.isGroupCollapsed(a)){this.toggleGroup(a)}}},{key:"collapseGroup",value:function collapseGroup(a){if(!this.isGroupCollapsed(a)){this.toggleGroup(a)}}},{key:"expandAllGroups",value:function expandAllGroups(){var a=this,c=(0,d.getList)(this.treeRoot)[0].querySelectorAll(this.selectors.SECTION);c.forEach(function(c){a.expandGroup((0,b.default)(c))})}}]);return c}(c.default);a.default=q;return a.default});
|
||||
//# sourceMappingURL=contenttree.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
@@ -1,2 +0,0 @@
|
||||
define ("core_courseformat/local/courseindex/keyboardnav",["exports","jquery","core/tree"],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){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function g(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 h(a,b,c){if(b)g(a.prototype,b);if(c)g(a,c);return a}function i(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)j(a,b)}function j(a,b){j=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return j(a,b)}function k(a){return function(){var b=p(a),c;if(n()){var d=p(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return l(this,c)}}function l(a,b){if(b&&("object"===e(b)||"function"==typeof b)){return b}return m(a)}function m(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function n(){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 p(a){p=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return p(a)}var q=function(a){i(c,a);var d=k(c);function c(a){var b;f(this,c);b=d.call(this,a.element);b.selectors=a.selectors;if(a.reactive.isEditing){b._getVisibleItems=b.getVisibleItems;b.getVisibleItems=function(){b.refreshVisibleItemsCache();return b._getVisibleItems()}}b.treeRoot.on("hidden.bs.collapse shown.bs.collapse",function(){b.refreshVisibleItemsCache()});b.registerEnterCallback(b.enterCallback.bind(m(b)));return b}h(c,[{key:"getActiveItem",value:function getActiveItem(){var a=this.treeRoot.data("activeItem");if(a){return a.get(0)}}},{key:"enterCallback",value:function enterCallback(a){if(this.isGroupItem(a)){window.location.href=a.find(this.selectors.TOGGLER).first().attr("href");a.find(this.selectors.TOGGLER).get(0).click()}else{window.location.href=a.find("a").first().attr("href")}}},{key:"isGroupCollapsed",value:function isGroupCollapsed(a){var b=a.find("[aria-expanded]");return"false"===b.attr("aria-expanded")}},{key:"toggleGroup",value:function toggleGroup(a){var c,d=a.find(this.selectors.COLLAPSE),e=null!==(c=d.data("target"))&&void 0!==c?c:d.attr("href");if(!e){return}e=e.replace("#","");(0,b.default)("#".concat(e)).collapse("toggle")}},{key:"expandGroup",value:function expandGroup(a){if(this.isGroupCollapsed(a)){this.toggleGroup(a)}}},{key:"collapseGroup",value:function collapseGroup(a){if(!this.isGroupCollapsed(a)){this.toggleGroup(a)}}},{key:"expandAllGroups",value:function expandAllGroups(){var a=this,c=this.treeRoot.find(this.selectors.SECTION);c.each(function(c,d){a.expandGroup((0,b.default)(d))})}}]);return c}(c.default);a.default=q;return a.default});
|
||||
//# sourceMappingURL=keyboardnav.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -33,6 +33,10 @@ import {prefetchStrings} from 'core/prefetch';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import {getList} from 'core/normalise';
|
||||
import * as CourseEvents from 'core_course/events';
|
||||
import Pending from 'core/pending';
|
||||
import ContentTree from 'core_courseformat/local/courseeditor/contenttree';
|
||||
// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-79179 is integrated.
|
||||
import jQuery from 'jquery';
|
||||
|
||||
// Load global strings.
|
||||
prefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);
|
||||
@@ -48,11 +52,15 @@ export default class extends BaseComponent {
|
||||
// Default query selectors.
|
||||
this.selectors = {
|
||||
ACTIONLINK: `[data-action]`,
|
||||
// Move modal selectors.
|
||||
SECTIONLINK: `[data-for='section']`,
|
||||
CMLINK: `[data-for='cm']`,
|
||||
SECTIONNODE: `[data-for='sectionnode']`,
|
||||
TOGGLER: `[data-toggle='collapse']`,
|
||||
MODALTOGGLER: `[data-toggle='collapse']`,
|
||||
ADDSECTION: `[data-action='addSection']`,
|
||||
CONTENTTREE: `#destination-selector`,
|
||||
ACTIONMENU: `.action-menu`,
|
||||
ACTIONMENUTOGGLER: `[data-toggle="dropdown"]`,
|
||||
};
|
||||
// Component css classes.
|
||||
this.classes = {
|
||||
@@ -145,6 +153,9 @@ export default class extends BaseComponent {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// The section edit menu to refocus on end.
|
||||
const editTools = this._getClosestActionMenuToogler(target);
|
||||
|
||||
// Collect section information from the state.
|
||||
const exporter = this.reactive.getExporter();
|
||||
const data = exporter.course(this.reactive.state);
|
||||
@@ -170,6 +181,17 @@ export default class extends BaseComponent {
|
||||
const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);
|
||||
this._disableLink(generalSection);
|
||||
|
||||
// Setup keyboard navigation.
|
||||
new ContentTree(
|
||||
modalBody.querySelector(this.selectors.CONTENTTREE),
|
||||
{
|
||||
SECTION: this.selectors.SECTIONNODE,
|
||||
TOGGLER: this.selectors.MODALTOGGLER,
|
||||
COLLAPSE: this.selectors.MODALTOGGLER,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Capture click.
|
||||
modalBody.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
@@ -181,7 +203,7 @@ export default class extends BaseComponent {
|
||||
}
|
||||
event.preventDefault();
|
||||
this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);
|
||||
modal.destroy();
|
||||
this._destroyModal(modal, editTools);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,6 +223,9 @@ export default class extends BaseComponent {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// The section edit menu to refocus on end.
|
||||
const editTools = this._getClosestActionMenuToogler(target);
|
||||
|
||||
// Collect section information from the state.
|
||||
const exporter = this.reactive.getExporter();
|
||||
const data = exporter.course(this.reactive.state);
|
||||
@@ -224,8 +249,27 @@ export default class extends BaseComponent {
|
||||
let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);
|
||||
this._disableLink(currentElement);
|
||||
|
||||
// Open the cm section node if possible.
|
||||
currentElement.closest(this.selectors.SECTIONNODE)?.querySelector(this.selectors.TOGGLER)?.click();
|
||||
// Setup keyboard navigation.
|
||||
new ContentTree(
|
||||
modalBody.querySelector(this.selectors.CONTENTTREE),
|
||||
{
|
||||
SECTION: this.selectors.SECTIONNODE,
|
||||
TOGGLER: this.selectors.MODALTOGGLER,
|
||||
COLLAPSE: this.selectors.MODALTOGGLER,
|
||||
ENTER: this.selectors.SECTIONLINK,
|
||||
}
|
||||
);
|
||||
|
||||
// Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).
|
||||
// All jQuery int this code can be replaced when MDL-79179 is integrated.
|
||||
const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);
|
||||
const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);
|
||||
let collapsibleId = toggler.data('target') ?? toggler.attr('href');
|
||||
if (collapsibleId) {
|
||||
// We cannot be sure we have # in the id element name.
|
||||
collapsibleId = collapsibleId.replace('#', '');
|
||||
jQuery(`#${collapsibleId}`).collapse('toggle');
|
||||
}
|
||||
|
||||
// Capture click.
|
||||
modalBody.addEventListener('click', (event) => {
|
||||
@@ -252,7 +296,7 @@ export default class extends BaseComponent {
|
||||
}
|
||||
|
||||
this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);
|
||||
modal.destroy();
|
||||
this._destroyModal(modal, editTools);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -349,6 +393,7 @@ export default class extends BaseComponent {
|
||||
_modalBodyRenderedPromise(modalParams) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ModalFactory.create(modalParams).then((modal) => {
|
||||
modal.setRemoveOnClose(true);
|
||||
// Handle body loading event.
|
||||
modal.getRoot().on(ModalEvents.bodyRendered, () => {
|
||||
resolve(modal);
|
||||
@@ -364,4 +409,38 @@ export default class extends BaseComponent {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide and later destroy a modal.
|
||||
*
|
||||
* Behat will fail if we remove the modal while some boostrap collapse is executing.
|
||||
*
|
||||
* @param {Modal} modal
|
||||
* @param {HTMLElement} element the dom element to focus on.
|
||||
*/
|
||||
_destroyModal(modal, element) {
|
||||
modal.hide();
|
||||
const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
setTimeout(() =>{
|
||||
modal.destroy();
|
||||
pendingDestroy.resolve();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest actions menu toggler to an action element.
|
||||
*
|
||||
* @param {HTMLElement} element the action link element
|
||||
* @returns {HTMLElement|undefined}
|
||||
*/
|
||||
_getClosestActionMenuToogler(element) {
|
||||
const actionMenu = element.closest(this.selectors.ACTIONMENU);
|
||||
if (!actionMenu) {
|
||||
return undefined;
|
||||
}
|
||||
return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);
|
||||
}
|
||||
}
|
||||
|
||||
+44
-22
@@ -27,33 +27,41 @@
|
||||
*/
|
||||
|
||||
// The core/tree uses jQuery to expand all nodes.
|
||||
import $ from 'jquery';
|
||||
import jQuery from 'jquery';
|
||||
import Tree from 'core/tree';
|
||||
import {getList} from 'core/normalise';
|
||||
|
||||
export default class extends Tree {
|
||||
|
||||
/**
|
||||
* Setup the core/tree keyboard navigation.
|
||||
*
|
||||
* @param {CourseIndex} parent the parent component
|
||||
* @param {Element|undefined} mainElement an alternative main element in case it is not from the parent component
|
||||
* @param {Object|undefined} selectors alternative selectors
|
||||
* @param {boolean} preventcache if the elements cache must be disabled.
|
||||
*/
|
||||
constructor(parent) {
|
||||
constructor(mainElement, selectors, preventcache) {
|
||||
// Init this value with the parent DOM element.
|
||||
super(parent.element);
|
||||
super(mainElement);
|
||||
|
||||
// Get selectors from parent.
|
||||
this.selectors = parent.selectors;
|
||||
this.selectors = {
|
||||
SECTION: selectors.SECTION,
|
||||
TOGGLER: selectors.TOGGLER,
|
||||
COLLAPSE: selectors.COLLAPSE,
|
||||
ENTER: selectors.ENTER ?? selectors.TOGGLER,
|
||||
};
|
||||
|
||||
// The core/tree library saves the visible elements cache inside the main tree node.
|
||||
// However, in edit mode content can change suddenly so we need to refresh caches when needed.
|
||||
if (parent.reactive.isEditing) {
|
||||
if (preventcache) {
|
||||
this._getVisibleItems = this.getVisibleItems;
|
||||
this.getVisibleItems = () => {
|
||||
this.refreshVisibleItemsCache();
|
||||
return this._getVisibleItems();
|
||||
};
|
||||
}
|
||||
// Add jQuery events to detect boostrap collapse and uncollapse.
|
||||
// All jQuery events can be replaced when MDL-79179 is integrated.
|
||||
this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => {
|
||||
this.refreshVisibleItemsCache();
|
||||
});
|
||||
@@ -69,7 +77,7 @@ export default class extends Tree {
|
||||
getActiveItem() {
|
||||
const activeItem = this.treeRoot.data('activeItem');
|
||||
if (activeItem) {
|
||||
return activeItem.get(0);
|
||||
return getList(activeItem)[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -77,16 +85,25 @@ export default class extends Tree {
|
||||
/**
|
||||
* Handle enter key on a collpasible node.
|
||||
*
|
||||
* @param {JQuery} item the jQuery object
|
||||
* @param {JQuery} jQueryItem the jQuery object
|
||||
*/
|
||||
enterCallback(item) {
|
||||
if (this.isGroupItem(item)) {
|
||||
enterCallback(jQueryItem) {
|
||||
const item = getList(jQueryItem)[0];
|
||||
if (this.isGroupItem(jQueryItem)) {
|
||||
// Group elements is like clicking a topic but without loosing the focus.
|
||||
window.location.href = item.find(this.selectors.TOGGLER).first().attr('href');
|
||||
item.find(this.selectors.TOGGLER).get(0).click();
|
||||
const enter = item.querySelector(this.selectors.ENTER);
|
||||
if (enter.getAttribute('href') !== '#') {
|
||||
window.location.href = enter.getAttribute('href');
|
||||
}
|
||||
enter.click();
|
||||
} else {
|
||||
// Activity links just follow the link href.
|
||||
window.location.href = item.find('a').first().attr('href');
|
||||
const link = item.querySelector('a');
|
||||
if (link.getAttribute('href') !== '#') {
|
||||
window.location.href = link.getAttribute('href');
|
||||
} else {
|
||||
link.click();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -94,12 +111,13 @@ export default class extends Tree {
|
||||
/**
|
||||
* Check if a gorup item is collapsed.
|
||||
*
|
||||
* @param {JQuery} item the jQuery object
|
||||
* @param {JQuery} jQueryItem the jQuery object
|
||||
* @returns {boolean} if the element is collapsed
|
||||
*/
|
||||
isGroupCollapsed(item) {
|
||||
const toggler = item.find(`[aria-expanded]`);
|
||||
return toggler.attr('aria-expanded') === 'false';
|
||||
isGroupCollapsed(jQueryItem) {
|
||||
const item = getList(jQueryItem)[0];
|
||||
const toggler = item.querySelector(`[aria-expanded]`);
|
||||
return toggler.getAttribute('aria-expanded') === 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +126,7 @@ export default class extends Tree {
|
||||
* @param {JQuery} item the jQuery object
|
||||
*/
|
||||
toggleGroup(item) {
|
||||
// All jQuery in this segment of code can be replaced when MDL-79179 is integrated.
|
||||
const toggler = item.find(this.selectors.COLLAPSE);
|
||||
let collapsibleId = toggler.data('target') ?? toggler.attr('href');
|
||||
if (!collapsibleId) {
|
||||
@@ -116,7 +135,10 @@ export default class extends Tree {
|
||||
collapsibleId = collapsibleId.replace('#', '');
|
||||
|
||||
// Bootstrap 4 uses jQuery to interact with collapsibles.
|
||||
$(`#${collapsibleId}`).collapse('toggle');
|
||||
const collapsible = jQuery(`#${collapsibleId}`);
|
||||
if (collapsible.length) {
|
||||
jQuery(`#${collapsibleId}`).collapse('toggle');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,9 +167,9 @@ export default class extends Tree {
|
||||
* Expand all groups.
|
||||
*/
|
||||
expandAllGroups() {
|
||||
const togglers = this.treeRoot.find(this.selectors.SECTION);
|
||||
togglers.each((index, item) => {
|
||||
this.expandGroup($(item));
|
||||
const togglers = getList(this.treeRoot)[0].querySelectorAll(this.selectors.SECTION);
|
||||
togglers.forEach(item => {
|
||||
this.expandGroup(jQuery(item));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
import {BaseComponent} from 'core/reactive';
|
||||
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
|
||||
import jQuery from 'jquery';
|
||||
import TreeNav from 'core_courseformat/local/courseindex/keyboardnav';
|
||||
import ContentTree from 'core_courseformat/local/courseeditor/contenttree';
|
||||
|
||||
export default class Component extends BaseComponent {
|
||||
|
||||
@@ -90,7 +90,7 @@ export default class Component extends BaseComponent {
|
||||
});
|
||||
|
||||
// Configure Aria Tree.
|
||||
this.treeNav = new TreeNav(this);
|
||||
this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing);
|
||||
}
|
||||
|
||||
getWatchers() {
|
||||
@@ -187,7 +187,7 @@ export default class Component extends BaseComponent {
|
||||
|
||||
// Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to
|
||||
// interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because
|
||||
// it does not require jQuery anymore.
|
||||
// it does not require jQuery anymore (when MDL-79179 is integrated).
|
||||
const togglerValue = (forceValue) ? 'show' : 'hide';
|
||||
jQuery(collapsible).collapse(togglerValue);
|
||||
}
|
||||
|
||||
@@ -77,9 +77,11 @@
|
||||
|
||||
}}
|
||||
<p data-for="sectionname">{{#str}} movefull, moodle, {{cmname}} {{/str}}:</p>
|
||||
<nav class="collapse-list" id="destination-selector">
|
||||
<nav class="collapse-list" id="destination-selector" role="tree">
|
||||
{{#sections}}
|
||||
<div data-for="sectionnode">
|
||||
<div data-for="sectionnode"
|
||||
role="treeitem"
|
||||
>
|
||||
<div class="collapse-list-item d-flex"
|
||||
id="movemodalsection{{number}}"
|
||||
data-for="section_item"
|
||||
@@ -101,7 +103,7 @@
|
||||
<span class="sr-only">{{#str}} collapse, core {{/str}}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a href="{{{sectionurl}}}"
|
||||
<a href="#"
|
||||
class="collapse-list-link text-truncate"
|
||||
data-for="section"
|
||||
data-id="{{id}}"
|
||||
@@ -113,12 +115,14 @@
|
||||
</div>
|
||||
<div id="movemodalcollapse{{number}}"
|
||||
class="collapse-list-item-content collapse"
|
||||
aria-labelledby="movemodalsection{{number}}">
|
||||
aria-labelledby="movemodalsection{{number}}"
|
||||
role="group"
|
||||
>
|
||||
<ul class="unlist" data-for="cmlist" data-id="{{id}}">
|
||||
{{#cms}}
|
||||
<li class="collapse-list-item d-flex">
|
||||
<li class="collapse-list-item d-flex" role="treeitem">
|
||||
<a class="collapse-list-link text-truncate"
|
||||
href="{{{url}}}"
|
||||
href="#"
|
||||
data-for="cm"
|
||||
data-id="{{id}}"
|
||||
>
|
||||
|
||||
@@ -43,12 +43,13 @@
|
||||
|
||||
}}
|
||||
<p data-for="sectionname">{{#str}} movefull, moodle, {{sectiontitle}} {{/str}}:</p>
|
||||
<nav class="collapse-list" id="destination-selector">
|
||||
<nav class="collapse-list" id="destination-selector" role="tree">
|
||||
{{#sections}}
|
||||
<div
|
||||
class="collapse-list-item"
|
||||
role="treeitem"
|
||||
>
|
||||
<a href="{{{sectionurl}}}"
|
||||
<a href="#"
|
||||
class="collapse-list-link text-truncate"
|
||||
data-for="section"
|
||||
data-id="{{id}}"
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
@core @core_course @core_courseformat
|
||||
Feature: Move activity using keyboard
|
||||
In order to move activities without a mouse
|
||||
As a user
|
||||
I need to select the activity destination with the keyboard.
|
||||
|
||||
Background:
|
||||
Given the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| enablecompletion | 1 |
|
||||
| numsections | 4 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section |
|
||||
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
|
||||
| book | Activity sample 2 | Test book description | C1 | sample2 | 2 |
|
||||
| choice | Activity sample 3 | Test choice description | C1 | sample3 | 3 |
|
||||
| choice | Other sample 3 | Test choice description | C1 | sample31 | 3 |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
|
||||
@javascript
|
||||
Scenario: Move activity to another section selecting the section name
|
||||
Given I open "Activity sample 3" actions menu
|
||||
And I click on "Move" "link" in the "Activity sample 3" activity
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Select the section 2.
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 2" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Move activity to another section selecting an inner activity
|
||||
Given I open "Activity sample 3" actions menu
|
||||
And I click on "Move" "link" in the "Activity sample 3" activity
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Open section 2.
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the right key
|
||||
# Select first activity.
|
||||
And I press the down key
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 2" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Close a section in the move modal
|
||||
Given I open "Activity sample 3" actions menu
|
||||
And I click on "Move" "link" in the "Activity sample 3" activity
|
||||
And I should see "Activity sample 3" in the ".modal-body" "css_element"
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Close section 3.
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the left key
|
||||
# Move to section 4.
|
||||
And I press the down key
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 4" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Move activity using open all sections
|
||||
Given I open "Activity sample 3" actions menu
|
||||
And I click on "Move" "link" in the "Activity sample 3" activity
|
||||
And I should see "Activity sample 3" in the ".modal-body" "css_element"
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Open all sections.
|
||||
And I press the multiply key
|
||||
# Move down to section 4
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 4" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Move activity using go to the last element
|
||||
Given I open "Activity sample 3" actions menu
|
||||
And I click on "Move" "link" in the "Activity sample 3" activity
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Go to the last section.
|
||||
And I press the end key
|
||||
# Move down to section 4
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 4" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Move activity using go to the first element
|
||||
Given I open "Activity sample 3" actions menu
|
||||
And I click on "Move" "link" in the "Activity sample 3" activity
|
||||
And I should see "Activity sample 3" in the ".modal-body" "css_element"
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Move some sections down.
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
# Go to the first section.
|
||||
And I press the home key
|
||||
# Move down to general section
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "General" "section"
|
||||
@@ -0,0 +1,65 @@
|
||||
@core @core_course @core_courseformat
|
||||
Feature: Move a section using keyboard
|
||||
In order to move sections without a mouse
|
||||
As a user
|
||||
I need to select the section destination with the keyboard.
|
||||
|
||||
Background:
|
||||
Given the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| enablecompletion | 1 |
|
||||
| numsections | 4 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section |
|
||||
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
|
||||
| book | Activity sample 2 | Test book description | C1 | sample2 | 2 |
|
||||
| choice | Activity sample 3 | Test choice description | C1 | sample3 | 3 |
|
||||
| choice | Other sample 3 | Test choice description | C1 | sample31 | 3 |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
|
||||
@javascript
|
||||
Scenario: Move section above another section
|
||||
Given I open section "3" edit menu
|
||||
And I click on "Move" "link" in the "Topic 3" "section"
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Select the section 2.
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 2" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Move section using go to the last element
|
||||
Given I open section "2" edit menu
|
||||
And I click on "Move" "link" in the "Topic 2" "section"
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Go to the last section.
|
||||
And I press the end key
|
||||
# Move down to section 4
|
||||
And I press enter
|
||||
Then I should see "Activity sample 2" in the "Topic 4" "section"
|
||||
|
||||
@javascript
|
||||
Scenario: Move section using go to the first element
|
||||
Given I open section "3" edit menu
|
||||
And I click on "Move" "link" in the "Topic 3" "section"
|
||||
# Focus on the modal content tree.
|
||||
When I press the tab key
|
||||
And I press the tab key
|
||||
# Move some sections down.
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
And I press the down key
|
||||
# Go to the first section.
|
||||
And I press the home key
|
||||
# Move down to Topic 1
|
||||
And I press the down key
|
||||
And I press enter
|
||||
Then I should see "Activity sample 3" in the "Topic 1" "section"
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -288,7 +288,14 @@ export default class {
|
||||
}
|
||||
|
||||
const listener = (event) => {
|
||||
// Prevent any watcher from losing the page focus.
|
||||
const currentFocus = document.activeElement;
|
||||
// Execute watcher.
|
||||
handler.apply(component, [event.detail]);
|
||||
// Restore focus in case it is lost.
|
||||
if (document.activeElement === document.body && document.body.contains(currentFocus)) {
|
||||
currentFocus.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// Save the listener information in case the component must be unregistered later.
|
||||
|
||||
+1
-1
@@ -355,7 +355,7 @@ define(['jquery'], function($) {
|
||||
// eslint-disable-next-line complexity
|
||||
Tree.prototype.handleKeyDown = function(e) {
|
||||
var item = $(e.target);
|
||||
var currentIndex = this.getVisibleItems().index(item);
|
||||
var currentIndex = this.getVisibleItems()?.index(item);
|
||||
|
||||
if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {
|
||||
// Do nothing.
|
||||
|
||||
Reference in New Issue
Block a user