Merge branch 'MDL-73355-master-3' of git://github.com/mihailges/moodle

This commit is contained in:
Sara Arjona
2022-02-23 15:58:56 +01:00
11 changed files with 238 additions and 77 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core/menu_navigation",["exports","core/key_codes"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var c={menuitem:"[role=\"menuitem\"]",menu:"[role=\"menu\"]"},d=null,e=function(a,b){if(null!==a){return a}else{return b}},f=function(a){var b;if(a.classList.contains("dropdown-item")){b=a.closest(".dropdown-menu");b.previousElementSibling.classList.add("active")}else{b=a.parentElement.parentElement.querySelector(".dropdown-menu")}Array.prototype.forEach.call(b.children,function(a){var b=a.querySelector(c.menuitem);if(null!==b){b.classList.remove("active");b.setAttribute("aria-current","false")}a.setAttribute("aria-current","false")});a.setAttribute("aria-current","true")},g=function(a){var c=a.srcElement,e=a.currentTarget.firstElementChild,g=l(a.currentTarget);if(c.classList.contains("dropdown-item")){if(a.keyCode===b.arrowRight||a.keyCode===b.arrowLeft){a.preventDefault();if(null!==d){d.parentElement.click()}}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();f(c);if(!c.parentElement.classList.contains("dropdown")){c.click()}}}else{if(a.keyCode===b.arrowRight){a.preventDefault();i(c,e)}if(a.keyCode===b.arrowLeft){a.preventDefault();j(c,g)}if(a.keyCode===b.arrowUp||a.keyCode===b.arrowDown){d=c;a.preventDefault()}if(a.keyCode===b.home){a.preventDefault();c.setAttribute("aria-current","false");k(e)}if(a.keyCode===b.end){a.preventDefault();c.currentNode.setAttribute("aria-current","false");k(g)}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();if(!c.parentElement.classList.contains("dropdown")){c.click()}}}},h=function(a){var b=a.srcElement;f(b)};a.default=function(a){a.removeEventListener("keydown",g);a.removeEventListener("click",h);a.addEventListener("keydown",g);a.addEventListener("click",h)};var i=function(a,b){var d=a.parentElement.nextElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},j=function(a,b){var d=a.parentElement.previousElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},k=function(a){a.querySelector(c.menuitem).focus();a.querySelector(c.menuitem).setAttribute("aria-current","true")},l=function(a){var b=a.lastElementChild;if(!b.classList.contains("d-none")){return a.lastElementChild}else{var c=Array.prototype.map.call(a.children,function(a){return a}).reverse(),d=c.filter(function(a){if(!a.classList.contains("d-none")){return a}});if(0!==d.length){return d[0]}else{return a.firstElementChild}}};return a.default});
define ("core/menu_navigation",["exports","core/key_codes"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var c={menuitem:"[role=\"menuitem\"]",menu:"[role=\"menu\"]"},d=null,e=function(a,b){if(null!==a){return a}else{return b}},f=function(a){var b;if(a.dataset.disableactive){return}if(a.classList.contains("dropdown-item")){b=a.closest(".dropdown-menu");b.previousElementSibling.classList.add("active")}else{b=a.parentElement.parentElement.querySelector(".dropdown-menu")}Array.prototype.forEach.call(b.children,function(a){var b=a.querySelector(c.menuitem);if(null!==b){b.classList.remove("active");b.setAttribute("aria-current","false")}a.setAttribute("aria-current","false")});a.setAttribute("aria-current","true")},g=function(a){var c=a.srcElement,e=a.currentTarget.firstElementChild,g=l(a.currentTarget);if(c.classList.contains("dropdown-item")){if(a.keyCode===b.arrowRight||a.keyCode===b.arrowLeft){a.preventDefault();if(null!==d){d.parentElement.click()}}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();f(c);if(!c.parentElement.classList.contains("dropdown")){c.click()}}}else{if(a.keyCode===b.arrowRight){a.preventDefault();i(c,e)}if(a.keyCode===b.arrowLeft){a.preventDefault();j(c,g)}if(a.keyCode===b.arrowUp||a.keyCode===b.arrowDown){d=c;a.preventDefault()}if(a.keyCode===b.home){a.preventDefault();c.setAttribute("aria-current","false");k(e)}if(a.keyCode===b.end){a.preventDefault();c.currentNode.setAttribute("aria-current","false");k(g)}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();if(!c.parentElement.classList.contains("dropdown")){c.click()}}}},h=function(a){var b=a.srcElement;f(b)};a.default=function(a){a.removeEventListener("keydown",g);a.removeEventListener("click",h);a.addEventListener("keydown",g);a.addEventListener("click",h)};var i=function(a,b){var d=a.parentElement.nextElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},j=function(a,b){var d=a.parentElement.previousElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},k=function(a){a.querySelector(c.menuitem).focus();a.querySelector(c.menuitem).setAttribute("aria-current","true")},l=function(a){var b=a.lastElementChild;if(!b.classList.contains("d-none")){return a.lastElementChild}else{var c=Array.prototype.map.call(a.children,function(a){return a}).reverse(),d=c.filter(function(a){if(!a.classList.contains("d-none")){return a}});if(0!==d.length){return d[0]}else{return a.firstElementChild}}};return a.default});
//# sourceMappingURL=menu_navigation.min.js.map
File diff suppressed because one or more lines are too long
+4
View File
@@ -54,6 +54,10 @@ const clickErrorHandler = (item, fallback) => {
const menuItemHelper = src => {
let parent;
// Do not apply any actions if the selected dropdown item is explicitly instructing to not display an active state.
if (src.dataset.disableactive) {
return;
}
// Handling for dropdown escapes.
// A bulk of the handling is already done by aria.js just add polish.
if (src.classList.contains('dropdown-item')) {
+31
View File
@@ -895,6 +895,37 @@ class navigation_node implements renderable {
return [];
}
/**
* Check whether the node's action is of type action_link.
*
* @return bool
*/
public function is_action_link() {
return $this->action instanceof action_link;
}
/**
* Return an array consisting of the actions for the action link.
*
* @return array Formatted array to parse in a template
*/
public function action_link_actions() {
global $PAGE;
if (!$this->is_action_link()) {
return [];
}
$actionid = $this->action->attributes['id'];
$actionsdata = array_map(function($action) use ($PAGE, $actionid) {
$data = $action->export_for_template($PAGE->get_renderer('core'));
$data->id = $actionid;
return $data;
}, !empty($this->action->actions) ? $this->action->actions : []);
return ['actions' => $actionsdata];
}
/**
* Sets whether the node and its children should be added into a "more" menu whenever possible.
*
+4 -3
View File
@@ -1532,6 +1532,9 @@ class action_link implements renderable {
pix_icon $icon=null) {
$this->url = clone($url);
$this->text = $text;
if (empty($attributes['id'])) {
$attributes['id'] = html_writer::random_id('action_link');
}
$this->attributes = (array)$attributes;
if ($action) {
$this->add_action($action);
@@ -1591,9 +1594,6 @@ class action_link implements renderable {
$data = new stdClass();
$attributes = $this->attributes;
if (empty($attributes['id'])) {
$attributes['id'] = html_writer::random_id('action_link');
}
$data->id = $attributes['id'];
unset($attributes['id']);
@@ -4677,6 +4677,7 @@ class action_menu_filler extends action_link implements renderable {
* Constructs the object.
*/
public function __construct() {
$this->attributes['id'] = html_writer::random_id('action_link');
}
}
+35 -7
View File
@@ -38,7 +38,15 @@
<div class="dropdown-menu" role="menu" id="drop-down-menu-{{moremenuid}}" aria-labelledby="drop-down-{{moremenuid}}">
{{#children}}
{{^divider}}
<a class="dropdown-item" role="menuitem" href="{{{url}}}{{{action}}}">{{{text}}}</a>
{{#is_action_link}}
<a class="dropdown-item" role="menuitem" {{#actionattributes}}{{name}}="{{value}}"{{/actionattributes}} href="{{{url}}}{{{action}}}" data-disableactive="true">{{{text}}}</a>
{{#action_link_actions}}
{{> core/actions }}
{{/action_link_actions}}
{{/is_action_link}}
{{^is_action_link}}
<a class="dropdown-item" role="menuitem" href="{{{url}}}{{{action}}}">{{{text}}}</a>
{{/is_action_link}}
{{/divider}}
{{#divider}}
<div class="dropdown-divider"></div>
@@ -50,14 +58,34 @@
{{^haschildren}}
<li class="nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
{{#tab}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" {{#actionattributes}}{{name}}="{{value}}"{{/actionattributes}} href="{{tab}}" data-toggle="tab" data-text="{{{text}}}" aria-current="false" tabindex="-1">
{{{text}}}
</a>
{{#is_action_link}}
<a role="menuitem" class="nav-link {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" data-text="{{{text}}}" aria-current="false" data-disableactive="true" tabindex="-1">
{{{text}}}
</a>
{{#action_link_actions}}
{{> core/actions }}
{{/action_link_actions}}
{{/is_action_link}}
{{^is_action_link}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" data-text="{{{text}}}" aria-current="false" tabindex="-1">
{{{text}}}
</a>
{{/is_action_link}}
{{/tab}}
{{^tab}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" {{#actionattributes}}{{name}}="{{value}}"{{/actionattributes}} href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
{{{text}}}
</a>
{{#is_action_link}}
<a role="menuitem" class="nav-link {{#classes}}{{.}} {{/classes}}" {{#actionattributes}}{{name}}="{{value}}"{{/actionattributes}} href="{{{url}}}{{{action}}}" data-disableactive="true" tabindex="-1">
{{{text}}}
</a>
{{#action_link_actions}}
{{> core/actions }}
{{/action_link_actions}}
{{/is_action_link}}
{{^is_action_link}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
{{{text}}}
</a>
{{/is_action_link}}
{{/tab}}
</li>
{{/haschildren}}
+89
View File
@@ -597,6 +597,95 @@ class core_navigationlib_testcase extends advanced_testcase {
],
];
}
/**
* Test the is_action_link method.
*
* @param navigation_node $node The sample navigation node
* @param bool $expected Whether the navigation node contains an action link
* @dataProvider test_is_action_link_provider
* @covers navigation_node::is_action_link
*/
public function test_is_action_link(navigation_node $node, bool $expected) {
$this->assertEquals($node->is_action_link(), $expected);
}
/**
* Data provider for the test_is_action_link function.
*
* @return array
*/
public function test_is_action_link_provider(): array {
return [
'The navigation node has an action link.' =>
[
navigation_node::create('Node', new action_link(new moodle_url('/'), '',
new popup_action('click', new moodle_url('/'))), navigation_node::TYPE_SETTING),
true
],
'The navigation node does not have an action link.' =>
[
navigation_node::create('Node', new moodle_url('/'), navigation_node::TYPE_SETTING),
false
],
];
}
/**
* Test the action_link_actions method.
*
* @param navigation_node $node The sample navigation node
* @dataProvider test_action_link_actions_provider
* @covers navigation_node::action_link_actions
*/
public function test_action_link_actions(navigation_node $node) {
// Get the formatted array of action link actions.
$data = $node->action_link_actions();
// The navigation node has an action link.
if ($node->action instanceof action_link) {
if (!empty($node->action->actions)) { // There are actions added to the action link.
$this->assertArrayHasKey('actions', $data);
$this->assertCount(1, $data['actions']);
$expected = (object)[
'id' => $node->action->attributes['id'],
'event' => $node->action->actions[0]->event,
'jsfunction' => $node->action->actions[0]->jsfunction,
'jsfunctionargs' => json_encode($node->action->actions[0]->jsfunctionargs)
];
$this->assertEquals($expected, $data['actions'][0]);
} else { // There are no actions added to the action link.
$this->assertArrayHasKey('actions', $data);
$this->assertEmpty($data['actions']);
}
} else { // The navigation node does not have an action link.
$this->assertEmpty($data);
}
}
/**
* Data provider for the test_action_link_actions function.
*
* @return array
*/
public function test_action_link_actions_provider(): array {
return [
'The navigation node has an action link with an action attached.' =>
[
navigation_node::create('Node', new action_link(new moodle_url('/'), '',
new popup_action('click', new moodle_url('/'))), navigation_node::TYPE_SETTING),
],
'The navigation node has an action link without an action.' =>
[
navigation_node::create('Node', new action_link(new moodle_url('/'), '', null),
navigation_node::TYPE_SETTING),
],
'The navigation node does not have an action link.' =>
[
navigation_node::create('Node', new moodle_url('/'), navigation_node::TYPE_SETTING),
],
];
}
}
+1
View File
@@ -132,6 +132,7 @@ if ($mform->is_cancelled()) {
// Otherwise fill and print the form.
$PAGE->set_title($book->name);
$PAGE->set_heading($course->fullname);
$PAGE->set_secondary_active_tab('modulepage');
if ($chapters = book_preload_chapters($book)) {
book_add_fake_block($chapters, $chapter, $book, $cm);
@@ -8,12 +8,14 @@ Feature: In a book, create chapters and sub chapters
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | section |
| book | Test book | A book about dreams! | C1 | 1 |
@@ -27,6 +29,7 @@ Feature: In a book, create chapters and sub chapters
| Chapter title | Dummy first chapter |
| Content | Dream is the start of a journey |
And I press "Save changes"
And I should not see "No content has been added to this book yet."
And I should see "1. Dummy first chapter" in the "Table of contents" "block"
And I click on "Add new chapter after \"Dummy first chapter\"" "link" in the "Table of contents" "block"
And I should see "Dummy first chapter"
@@ -78,3 +81,8 @@ Feature: In a book, create chapters and sub chapters
And "Delete chapter \"1. Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
And "Hide chapter \"1. Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
And "Add new chapter after \"Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
Scenario: When chapters are not created yet, students can see a notification in the book activity
Given I am on the "Test book" "book activity" page logged in as student1
Then I should see "No content has been added to this book yet." in the ".alert-info" "css_element"
And I should not see "Table of contents"
@@ -8,12 +8,14 @@ Feature: Display the book description in the book and optionally in the course
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| book | Test book | A book about dreams! | C1 | book1 | 1 |
@@ -52,3 +54,13 @@ Feature: Display the book description in the book and optionally in the course
And I press "Save and return to course"
When I am on "Course 1" course homepage
Then I should not see "A book about dreams!"
@javascript
Scenario: Description is displayed in the book for students when there are no chapters added yet
Given I am on "Course 1" course homepage with editing mode on
And I am on the "Test book" "book activity" page
And I click on "Delete chapter \"1. Dummy first chapter\"" "link" in the "Table of contents" "block"
And I click on "Yes" "button" in the "Confirmation" "dialogue"
And I log out
And I am on the "Test book" "book activity" page logged in as student1
Then I should see "A book about dreams!"
+52 -65
View File
@@ -91,78 +91,65 @@ if ($chapterid == '0') { // Go to first chapter if no given.
}
}
$courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
// No content in the book.
if (!$chapterid) {
$PAGE->set_url('/mod/book/view.php', array('id' => $id));
notice(get_string('nocontent', 'mod_book'), $courseurl->out(false));
// Prepare header.
$pagetitle = $book->name;
if ($chapter = $DB->get_record('book_chapters', ['id' => $chapterid, 'bookid' => $book->id])) {
$pagetitle .= ": {$chapter->title}";
}
// Chapter doesnt exist or it is hidden for students
if ((!$chapter = $DB->get_record('book_chapters', array('id' => $chapterid, 'bookid' => $book->id))) or ($chapter->hidden and !$viewhidden)) {
print_error('errorchapter', 'mod_book', $courseurl);
}
$PAGE->set_url('/mod/book/view.php', array('id'=>$id, 'chapterid'=>$chapterid));
// Unset all page parameters.
unset($id);
unset($bid);
unset($chapterid);
// Read standard strings.
$strbooks = get_string('modulenameplural', 'mod_book');
$strbook = get_string('modulename', 'mod_book');
$strtoc = get_string('toc', 'mod_book');
// prepare header
$pagetitle = $book->name . ": " . $chapter->title;
$PAGE->set_title($pagetitle);
$PAGE->set_heading($course->fullname);
$PAGE->add_body_class('limitedwidth');
book_add_fake_block($chapters, $chapter, $book, $cm, $edit);
$renderer = $PAGE->get_renderer('mod_book');
$actionmenu = new \mod_book\output\main_action_menu($cm->id, $chapters, $chapter, $book);
$renderedmenu = $renderer->render($actionmenu);
// We need to discover if this is the last chapter to mark activity as completed.
$islastchapter = $chapter->pagenum + 1 > count($chapters);
book_view($book, $chapter, $islastchapter, $course, $cm, $context);
// =====================================================
// Book display HTML code
// =====================================================
echo $OUTPUT->header();
echo $renderedmenu;
// The chapter itself.
$hidden = $chapter->hidden ? ' dimmed_text' : null;
echo $OUTPUT->box_start('generalbox book_content' . $hidden);
if (!$book->customtitles) {
if (!$chapter->subchapter) {
$currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
echo $OUTPUT->heading($currtitle, 3);
} else {
$currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
$currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
echo $OUTPUT->heading($currtitle, 3);
echo $OUTPUT->heading($currsubtitle, 4);
// No content in the book.
if (!$chapterid) {
$PAGE->set_url('/mod/book/view.php', array('id' => $id));
echo $OUTPUT->header();
echo $OUTPUT->notification(get_string('nocontent', 'mod_book'), 'info', false);
} else {
$PAGE->set_url('/mod/book/view.php', ['id' => $id, 'chapterid' => $chapterid]);
// The chapter doesnt exist or it is hidden for students.
if (!$chapter or ($chapter->hidden and !$viewhidden)) {
$courseurl = new moodle_url('/course/view.php', ['id' => $course->id]);
throw new moodle_exception('errorchapter', 'mod_book', $courseurl);
}
}
$chaptertext = file_rewrite_pluginfile_urls($chapter->content, 'pluginfile.php', $context->id, 'mod_book', 'chapter', $chapter->id);
echo format_text($chaptertext, $chapter->contentformat, array('noclean'=>true, 'overflowdiv'=>true, 'context'=>$context));
// Add the Book TOC block.
book_add_fake_block($chapters, $chapter, $book, $cm, $edit);
// We need to discover if this is the last chapter to mark activity as completed.
$islastchapter = $chapter->pagenum + 1 > count($chapters);
book_view($book, $chapter, $islastchapter, $course, $cm, $context);
echo $OUTPUT->box_end();
echo $OUTPUT->header();
if (core_tag_tag::is_enabled('mod_book', 'book_chapters')) {
echo $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_book', 'book_chapters', $chapter->id), null, 'book-tags');
$renderer = $PAGE->get_renderer('mod_book');
$actionmenu = new \mod_book\output\main_action_menu($cm->id, $chapters, $chapter, $book);
$renderedmenu = $renderer->render($actionmenu);
echo $renderedmenu;
// The chapter itself.
$hidden = $chapter->hidden ? ' dimmed_text' : null;
echo $OUTPUT->box_start('generalbox book_content' . $hidden);
if (!$book->customtitles) {
if (!$chapter->subchapter) {
$currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
echo $OUTPUT->heading($currtitle, 3);
} else {
$currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
$currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
echo $OUTPUT->heading($currtitle, 3);
echo $OUTPUT->heading($currsubtitle, 4);
}
}
$chaptertext = file_rewrite_pluginfile_urls($chapter->content, 'pluginfile.php', $context->id, 'mod_book',
'chapter', $chapter->id);
echo format_text($chaptertext, $chapter->contentformat, ['noclean' => true, 'overflowdiv' => true,
'context' => $context]);
echo $OUTPUT->box_end();
if (core_tag_tag::is_enabled('mod_book', 'book_chapters')) {
echo $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_book', 'book_chapters', $chapter->id), null, 'book-tags');
}
echo $renderedmenu;
}
echo $renderedmenu;
echo $OUTPUT->footer();