Merge branch 'MDL-74800-master' of https://github.com/ferranrecio/moodle

This commit is contained in:
Jun Pataleta 2022-08-08 22:30:01 +08:00
commit 5d90181c6e
38 changed files with 416 additions and 246 deletions

View File

@ -102,7 +102,7 @@ class block_site_main_menu extends block_list {
'contentwithoutlink activity-item activity'
);
} else {
$cmname = new $cmnameclass($format, $cm->get_section_info(), $cm, $isediting);
$cmname = new $cmnameclass($format, $cm->get_section_info(), $cm);
$activitybasis = html_writer::div(
$indent . $courserenderer->render($cmname),
'activity-basis d-flex align-items-center');
@ -214,7 +214,7 @@ class block_site_main_menu extends block_list {
'contentwithoutlink activity-item activity'
);
} else {
$cmname = new $cmnameclass($format, $mod->get_section_info(), $mod, $isediting);
$cmname = new $cmnameclass($format, $mod->get_section_info(), $mod);
$activitybasis = html_writer::div(
$moveaction .
$indent .

View File

@ -99,7 +99,7 @@ class block_social_activities extends block_list {
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
$cmname = new $cmnameclass($format, $cm->get_section_info(), $cm, $isediting);
$cmname = new $cmnameclass($format, $cm->get_section_info(), $cm);
$activitybasis = html_writer::div(
$courserenderer->render($cmname),
'activity-basis d-flex align-items-center');
@ -205,7 +205,7 @@ class block_social_activities extends block_list {
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
$cmname = new $cmnameclass($format, $mod->get_section_info(), $mod, $isediting);
$cmname = new $cmnameclass($format, $mod->get_section_info(), $mod);
$activitybasis = html_writer::div(
$courserenderer->render($cmname) .
$editbuttons,

View File

@ -59,12 +59,6 @@ class cm implements named_templatable, renderable {
/** @var array optional display options */
protected $displayoptions;
/** @var string activity link css classes */
protected $linkclasses = null;
/** @var string text css classes */
protected $textclasses = null;
/** @var string the activity name output class name */
protected $cmnameclass;
@ -88,10 +82,8 @@ class cm implements named_templatable, renderable {
$this->mod = $mod;
// Add extra display options.
$this->load_classes();
$displayoptions['linkclasses'] = $this->get_link_classes();
$displayoptions['textclasses'] = $this->get_text_classes();
$this->displayoptions = $displayoptions;
$this->load_classes();
// Get the necessary classes.
$this->cmnameclass = $format->get_output_classname('content\\cm\\cmname');
@ -148,11 +140,11 @@ class cm implements named_templatable, renderable {
$this->format,
$this->section,
$this->mod,
$this->format->show_editor(),
null,
$this->displayoptions
);
$data->cmname = $cmname->export_for_template($output);
$data->hasname = !empty($data->cmname['displayvalue']);
$data->hasname = $cmname->has_name();
return $data->hasname;
}
@ -323,8 +315,9 @@ class cm implements named_templatable, renderable {
$textclasses .= ' conditionalhidden';
}
}
$this->linkclasses = $linkclasses;
$this->textclasses = $textclasses;
$this->displayoptions['linkclasses'] = $linkclasses;
$this->displayoptions['textclasses'] = $textclasses;
$this->displayoptions['onclick'] = htmlspecialchars_decode($mod->onclick, ENT_QUOTES);;
}
/**
@ -333,7 +326,7 @@ class cm implements named_templatable, renderable {
* @return string the activity link classes.
*/
public function get_link_classes(): string {
return $this->linkclasses;
return $this->displayoptions['linkclasses'] ?? '';
}
/**
@ -342,6 +335,15 @@ class cm implements named_templatable, renderable {
* @return string the activity text classes.
*/
public function get_text_classes(): string {
return $this->textclasses;
return $this->displayoptions['textclasses'] ?? '';
}
/**
* Get the activity onclick code.
*
* @return string the activity onclick.
*/
public function get_onclick_code(): string {
return $this->displayoptions['onclick'];
}
}

View File

@ -25,13 +25,9 @@
namespace core_courseformat\output\local\content\cm;
use cm_info;
use context_module;
use core\output\inplace_editable;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use external_api;
use lang_string;
use renderable;
use section_info;
use stdClass;
@ -43,7 +39,7 @@ use stdClass;
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmname extends inplace_editable implements named_templatable, renderable {
class cmname implements named_templatable, renderable {
use courseformat_named_templatable;
@ -56,9 +52,6 @@ class cmname extends inplace_editable implements named_templatable, renderable {
/** @var cm_info the course module instance */
protected $mod;
/** @var editable if the title is editable */
protected $editable;
/** @var array optional display options */
protected $displayoptions;
@ -71,14 +64,14 @@ class cmname extends inplace_editable implements named_templatable, renderable {
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param bool $editable if it is editable
* @param bool|null $editable if it is editable (not used)
* @param array $displayoptions optional extra display options
*/
public function __construct(
course_format $format,
section_info $section,
cm_info $mod,
bool $editable,
?bool $editable = null,
array $displayoptions = []
) {
$this->format = $format;
@ -86,25 +79,8 @@ class cmname extends inplace_editable implements named_templatable, renderable {
$this->mod = $mod;
$this->displayoptions = $displayoptions;
$this->editable = $editable && has_capability(
'moodle/course:manageactivities',
$mod->context
);
// Get the necessary classes.
$this->titleclass = $format->get_output_classname('content\\cm\\title');
// Setup inplace editable.
parent::__construct(
'core_course',
'activityname',
$mod->id,
$this->editable,
$mod->name,
$mod->name,
new lang_string('edittitle'),
new lang_string('newactivityname', '', $mod->get_formatted_name())
);
}
/**
@ -114,51 +90,49 @@ class cmname extends inplace_editable implements named_templatable, renderable {
* @return stdClass data context for a mustache template
*/
public function export_for_template(\renderer_base $output): array {
global $PAGE;
$mod = $this->mod;
$displayoptions = $this->displayoptions;
// Inplace editable uses core renderer by default. However, course elements require
// the format specific renderer.
$courseoutput = $this->format->get_renderer($PAGE);
if (!$this->has_name()) {
// Nothing to be displayed to the user.
return [];
}
// Inplace editable uses pre-rendered elements and does not allow line beaks in the UI value.
$data = [
'url' => $mod->url,
'icon' => $mod->get_icon_url(),
'modname' => $mod->modname,
'pluginname' => get_string('pluginname', 'mod_' . $mod->modname),
'textclasses' => $displayoptions['textclasses'] ?? '',
'purpose' => plugin_supports('mod', $mod->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),
'activityname' => $this->get_title_data($output),
];
return $data;
}
/**
* Get the title data.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
protected function get_title_data(\renderer_base $output): array {
$title = new $this->titleclass(
$this->format,
$this->section,
$this->mod,
$this->displayoptions
);
$this->displayvalue = str_replace("\n", "", $courseoutput->render($title));
if (trim($this->displayvalue) == '') {
$this->editable = false;
}
$data = parent::export_for_template($output);
return $data;
return (array) $title->export_for_template($output);
}
/**
* Updates course module name
* Return if the activity has a visible name.
*
* @param int $itemid course module id
* @param string $newvalue new name
* @return static
* @return bool if the title is visible.
*/
public static function update($itemid, $newvalue) {
$context = context_module::instance($itemid);
// Check access.
external_api::validate_context($context);
require_capability('moodle/course:manageactivities', $context);
// Trim module name and Update value.
set_coursemodule_name($itemid, trim($newvalue));
$coursemodulerecord = get_coursemodule_from_id('', $itemid, 0, false, MUST_EXIST);
// Return instance.
$modinfo = get_fast_modinfo($coursemodulerecord->course);
$cm = $modinfo->get_cm($itemid);
$section = $modinfo->get_section_info($cm->sectionnum);
$format = course_get_format($cm->course);
return new static($format, $section, $cm, true);
public function has_name(): bool {
return $this->mod->is_visible_on_course_page() && $this->mod->url;
}
}

View File

@ -27,13 +27,16 @@
namespace core_courseformat\output\local\content\cm;
use cm_info;
use core\output\inplace_editable;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use core_text;
use lang_string;
use renderable;
use section_info;
use stdClass;
use external_api;
use context_module;
/**
* Base class to render a course module title inside a course format.
@ -42,9 +45,7 @@ use stdClass;
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class title implements named_templatable, renderable {
use courseformat_named_templatable;
class title extends inplace_editable implements named_templatable, renderable {
/** @var course_format the course format */
protected $format;
@ -58,6 +59,12 @@ class title implements named_templatable, renderable {
/** @var array optional display options */
protected $displayoptions;
/** @var editable if the title is editable */
protected $editable;
/** @var displaytemplate the default display template */
protected $displaytemplate = 'core_courseformat/local/content/cm/title';
/**
* Constructor.
*
@ -65,12 +72,52 @@ class title implements named_templatable, renderable {
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param array $displayoptions optional extra display options
* @param bool|null $editable force editable value
*/
public function __construct(course_format $format, section_info $section, cm_info $mod, array $displayoptions = []) {
public function __construct(
course_format $format,
section_info $section,
cm_info $mod,
array $displayoptions = [],
?bool $editable = null
) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
$this->displayoptions = $displayoptions;
// Usually displayoptions are loaded in the main cm output. However when the user uses the inplace editor
// the cmname output does not calculate the css classes.
$this->displayoptions = $this->load_display_options($displayoptions);
if ($editable === null) {
$editable = $format->show_editor() && has_capability(
'moodle/course:manageactivities',
$mod->context
);
}
$this->editable = $editable;
// Setup inplace editable.
parent::__construct(
'core_course',
'activityname',
$mod->id,
$this->editable,
$mod->name,
$mod->name,
new lang_string('edittitle'),
new lang_string('newactivityname', '', $mod->get_formatted_name())
);
}
/**
* Get the name of the template to use for this templatable.
*
* @param \renderer_base $renderer The renderer requesting the template name
* @return string
*/
public function get_template_name(\renderer_base $renderer): string {
return 'core/inplace_editable';
}
/**
@ -79,41 +126,35 @@ class title implements named_templatable, renderable {
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
public function export_for_template(\renderer_base $output): array {
// Inplace editable uses pre-rendered elements and does not allow line beaks in the UI value.
$this->displayvalue = str_replace("\n", "", $this->get_title_displayvalue());
if (trim($this->displayvalue) == '') {
$this->editable = false;
}
return parent::export_for_template($output);
}
/**
* Return the title template data to be used inside the inplace editable.
*
*/
protected function get_title_displayvalue (): string {
global $PAGE;
// Inplace editable uses core renderer by default. However, course elements require
// the format specific renderer.
$courseoutput = $this->format->get_renderer($PAGE);
$format = $this->format;
$mod = $this->mod;
$displayoptions = $this->displayoptions;
if (!$mod->is_visible_on_course_page() || !$mod->url) {
// Nothing to be displayed to the user.
return new stdClass();
}
// Usually classes are loaded in the main cm output. However when the user uses the inplace editor
// the cmname output does not calculate the css classes.
if (!isset($displayoptions['linkclasses']) || !isset($displayoptions['textclasses'])) {
$cmclass = $format->get_output_classname('content\\cm');
$cmoutput = new $cmclass(
$format,
$this->section,
$mod,
$displayoptions
);
$displayoptions['linkclasses'] = $cmoutput->get_link_classes();
$displayoptions['textclasses'] = $cmoutput->get_text_classes();
}
$data = (object)[
'url' => $mod->url,
'instancename' => $mod->get_formatted_name(),
'uservisible' => $mod->uservisible,
'icon' => $mod->get_icon_url(),
'modname' => $mod->modname,
'pluginname' => get_string('pluginname', 'mod_' . $mod->modname),
'linkclasses' => $displayoptions['linkclasses'],
'textclasses' => $displayoptions['textclasses'],
'purpose' => plugin_supports('mod', $mod->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),
'linkclasses' => $this->displayoptions['linkclasses'],
];
// File type after name, for alphabetic lists (screen reader).
@ -128,6 +169,71 @@ class title implements named_templatable, renderable {
// has already been encoded for display (puke).
$data->onclick = htmlspecialchars_decode($mod->onclick, ENT_QUOTES);
return $data;
return $courseoutput->render_from_template(
$this->displaytemplate,
$data
);
}
/**
* Load the required display options if not present already.
*
* In most cases, display options are provided as a param when creating the
* object. However, inplace_editable and some blocks does not know all of them as it is
* called in a webservice and we need to ensure it is calculated.
*
* @param array $displayoptions the provided dispaly options
* @return array the full display options list
*/
protected function load_display_options(array $displayoptions): array {
$format = $this->format;
$mod = $this->mod;
if (
isset($displayoptions['linkclasses']) &&
isset($displayoptions['textclasses']) &&
isset($displayoptions['onclick'])
) {
return $displayoptions;
}
$cmclass = $format->get_output_classname('content\\cm');
$cmoutput = new $cmclass(
$format,
$this->section,
$mod,
$displayoptions
);
$displayoptions['linkclasses'] = $cmoutput->get_link_classes();
$displayoptions['textclasses'] = $cmoutput->get_text_classes();
$displayoptions['onclick'] = $cmoutput->get_onclick_code();
return $displayoptions;
}
/**
* Updates course module name.
*
* This method is used mainly by inplace_editable webservice.
*
* @param int $itemid course module id
* @param string $newvalue new name
* @return static
*/
public static function update($itemid, $newvalue) {
$context = context_module::instance($itemid);
// Check access.
external_api::validate_context($context);
require_capability('moodle/course:manageactivities', $context);
// Trim module name and Update value.
set_coursemodule_name($itemid, trim($newvalue));
$coursemodulerecord = get_coursemodule_from_id('', $itemid, 0, false, MUST_EXIST);
// Return instance.
$modinfo = get_fast_modinfo($coursemodulerecord->course);
$cm = $modinfo->get_cm($itemid);
$section = $modinfo->get_section_info($cm->sectionnum);
$format = course_get_format($cm->course);
return new static($format, $section, $cm, [], true);
}
}

View File

@ -39,7 +39,7 @@
}
}}
{{#showaddsection}}
<div id="changenumsections" class="mdl-left py-2">
<div class="mdl-left py-2 changenumsections">
{{#increase}}
<a href="{{{url}}}" class="increase-sections">
{{#pix}}t/switch_plus, moodle, {{#str}} increasesections, moodle {{/str}}{{/pix}}

View File

@ -65,9 +65,9 @@
{{/ core_courseformat/local/content/cm/badges }}
{{/hasname}}
{{#cmname}}
{{$ core/inplace_editable }}
{{> core/inplace_editable }}
{{/ core/inplace_editable }}
{{$ core_courseformat/local/content/cm/cmname }}
{{> core_courseformat/local/content/cm/cmname }}
{{/ core_courseformat/local/content/cm/cmname }}
{{/cmname}}
{{#afterlink}}
<div class="afterlink">

View File

@ -23,18 +23,44 @@
Example context (json):
{
"displayvalue" : "<a href=\"#\">Moodle</a>",
"value" : "Moodle",
"itemid" : "1",
"component" : "core_unknown",
"itemtype" : "unknown",
"edithint" : "Edit this",
"editlabel" : "New name for this",
"type" : "text",
"options" : "",
"linkeverything": 0
"url": "#",
"icon": "../../../pix/help.svg",
"pluginname": "File",
"textclasses": "",
"purpose": "content",
"modname": "resource",
"activityname": {
"displayvalue" : "<a href=\"#\">Moodle</a>",
"value" : "Moodle",
"itemid" : "1",
"component" : "core_unknown",
"itemtype" : "unknown",
"edithint" : "Edit this",
"editlabel" : "New name for this",
"type" : "text",
"options" : "",
"linkeverything": 0
}
}
}}
{{$ core/inplace_editable }}
{{> core/inplace_editable }}
{{/ core/inplace_editable }}
{{#url}}
<div class="activity-instance d-flex flex-column">
<div class="activitytitle media {{textclasses}} modtype_{{modname}} position-relative align-self-start">
<div class="activityiconcontainer {{purpose}} courseicon align-self-start mr-3">
<img src="{{{icon}}}" class="activityicon " alt="{{{modname}}} icon">
</div>
<div class="media-body align-self-center">
<div class="text-uppercase small">
{{{pluginname}}}
</div>
<div class="activityname">
{{#activityname}}
{{$ core/inplace_editable }}
{{> core/inplace_editable }}
{{/ core/inplace_editable }}
{{/activityname}}
</div>
</div>
</div>
</div>
{{/url}}

View File

@ -24,39 +24,19 @@
"url": "#",
"instancename": "Activity name",
"uservisible": true,
"icon": "../../../pix/help.svg",
"onclick": "alert('ok')",
"pluginname": "File",
"altname": "PDF file",
"linkclasses": "",
"textclasses": "",
"purpose": "content",
"modname": "resource"
}
}}
{{#url}}
<div class="activity-instance d-flex flex-column">
<div class="activitytitle media {{textclasses}} modtype_{{modname}} position-relative align-self-start">
<div class="activityiconcontainer {{purpose}} courseicon align-self-start mr-3">
<img src="{{{icon}}}" class="activityicon " alt="{{{modname}}} icon">
</div>
<div class="media-body align-self-center">
<div class="text-uppercase small">
{{{pluginname}}}
</div>
<div class="activityname">
{{#uservisible}}
<a href="{{url}}" class="{{linkclasses}} aalink stretched-link" onclick="{{{onclick}}}">
<span class="instancename">{{{instancename}}} {{{altname}}}</span>
</a>
{{/uservisible}}
{{^uservisible}}
<span class="instancename">
{{{instancename}}} {{{altname}}}
</span>
{{/uservisible}}
</div>
</div>
</div>
</div>
{{/url}}
{{#uservisible}}
<a href="{{url}}" class="{{linkclasses}} aalink stretched-link" onclick="{{{onclick}}}">
<span class="instancename">{{{instancename}}} {{{altname}}}</span>
</a>
{{/uservisible}}
{{^uservisible}}
<span class="instancename">
{{{instancename}}} {{{altname}}}
</span>
{{/uservisible}}

View File

@ -3663,7 +3663,7 @@ function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $
*/
function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
if ($itemtype === 'activityname') {
return \core_courseformat\output\local\content\cm\cmname::update($itemid, $newvalue);
return \core_courseformat\output\local\content\cm\title::update($itemid, $newvalue);
}
}

View File

@ -606,14 +606,12 @@ class core_course_renderer extends plugin_renderer_base {
$format,
$mod->get_section_info(),
$mod,
$this->page->user_is_editing(),
null,
$displayoptions
);
$data = $cmname->export_for_template($this->output);
return $this->output->render_from_template('core/inplace_editable', $data) .
$groupinglabel;
$renderer = $format->get_renderer($this->page);
return $renderer->render($cmname) . $groupinglabel;
}
/**

View File

@ -4,6 +4,11 @@ information provided here is intended especially for developers.
=== 4.1 ===
* The function course_modchooser() has been finally deprecated and can not be used anymore. Please use
course_activitychooser() instead.
* A critical accessibility issue is been found (MDL-74800) at the output class
core_courseformat\output\local\content\cm\cmname. To solve the problem this output element is not
rendered anymore using inplace_editable but using a regular named_templatable interface.
Some format plugins that override the deprecated renderer method course_section_cm_name can be affected.
Check the current course_section_cm_name code to see how to render it properly.
=== 4.0 ===
* All activity icons have been replaced with black monochrome icons. The background

View File

@ -108,6 +108,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
'link' => 'link',
'link_or_button' => 'link_or_button',
'list_item' => 'list_item',
'menuitem' => 'menuitem',
'optgroup' => 'optgroup',
'option' => 'option',
'question' => 'question',
@ -201,6 +202,9 @@ XPATH
XPATH
, 'list_item' => <<<XPATH
.//li[contains(normalize-space(.), %locator%) and not(.//li[contains(normalize-space(.), %locator%)])]
XPATH
, 'menuitem' => <<<XPATH
.//*[@role='menuitem'][%titleMatch% or %ariaLabelMatch% or text()[contains(., %locator%)]]
XPATH
, 'question' => <<<XPATH
.//div[contains(concat(' ', normalize-space(@class), ' '), ' que ')]

View File

@ -4577,6 +4577,12 @@ class action_menu implements renderable, templatable {
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
// Assign a role of menubar to this action menu when:
// - it contains 2 or more primary actions; or
// - if it contains a primary action and secondary actions.
if (count($this->primaryactions) > 1 || (!empty($this->primaryactions) && !empty($this->secondaryactions))) {
$this->attributes['role'] = 'menubar';
}
$attributes = $this->attributes;
$attributesprimary = $this->attributesprimary;
$attributessecondary = $this->attributessecondary;
@ -4617,6 +4623,12 @@ class action_menu implements renderable, templatable {
$actionicon = new pix_icon('t/edit_menu', '', 'moodle', $iconattributes);
}
// If the menu trigger is within the menubar, assign a role of menuitem. Otherwise, assign as a button.
$primary->triggerrole = 'button';
if (isset($attributes['role']) && $attributes['role'] === 'menubar') {
$primary->triggerrole = 'menuitem';
}
if ($actionicon instanceof pix_icon) {
$primary->icon = $actionicon->export_for_pix();
if (!empty($actionicon->attributes['alt'])) {

View File

@ -22,11 +22,19 @@
Example context (json):
{
"classes": "",
"instance": "1",
"primary": {
"items": [{"rawhtml": "<p>Item in primary menu</p>"}]
},
"secondary": {
"items": [{"rawhtml": "<p>Item in secondary menu</p>"}]
"attributes": [
{"name": "id", "value": "action-menu-1-menu"}
],
"items": [
{
"rawhtml": "<p>Item in secondary menu</p>"
}
]
}
}
}}

View File

@ -79,7 +79,7 @@
}
}}
<div class="dropdown{{^secondary.items}} hidden{{/secondary.items}}">
<a href="#" tabindex="0" class="{{triggerextraclasses}} dropdown-toggle icon-no-margin" id="action-menu-toggle-{{instance}}" aria-label="{{title}}" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" aria-controls="action-menu-{{instance}}-menu">
<a href="#" tabindex="0" class="{{triggerextraclasses}} dropdown-toggle icon-no-margin" id="action-menu-toggle-{{instance}}" aria-label="{{title}}" data-toggle="dropdown" role="{{triggerrole}}" aria-haspopup="true" aria-expanded="false" aria-controls="action-menu-{{instance}}-menu">
{{{actiontext}}}
{{{menutrigger}}}
{{#icon}}

View File

@ -27,7 +27,8 @@
"edit": 1,
"adminedit": true,
"checked": "",
"string": "Edit on"
"string": "Edit on",
"legacyseturl": "/editmode.php"
}
}}
<div class="divider border-left h-75 align-self-center ml-1 mr-3"></div>
@ -40,10 +41,7 @@
<input type="checkbox"{{!
}} name="setmode"{{!
}}{{#checked}}{{!
}} aria-checked="true" checked{{!
}}{{/checked}}{{!
}}{{^checked}}{{!
}} aria-checked="false"{{!
}} checked{{!
}}{{/checked}}{{!
}} class="custom-control-input"{{!
}} id="{{uniqid}}-editingswitch"{{!

View File

@ -50,7 +50,12 @@ class behat_action_menu extends behat_base {
*/
public function i_open_the_action_menu_in($element, $selectortype) {
// Gets the node based on the requested selector type and locator.
$node = $this->get_node_in_container("css_element", "[role=button][aria-haspopup=true]", $selectortype, $element);
$node = $this->get_node_in_container(
"css_element",
"[role=button][aria-haspopup=true],[role=menuitem][aria-haspopup=true]",
$selectortype,
$element
);
// Check if it is not already opened.
if ($node->getAttribute('aria-expanded') === 'true') {

View File

@ -933,6 +933,9 @@ BLOCKREGION.prototype = {
this.get('node').all('.' + CSS.BLOCK + ' a.' + CSS.EDITINGMOVE).each(function(moveicon) {
moveicon.setStyle('cursor', 'move');
handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
// Dragdrop module assigns this with a button role by default.
// However, the block move icon is part of a menubar, so it should have a menuitem role.
handle.setAttribute('role', 'menuitem');
moveicon.replace(handle);
});
},

File diff suppressed because one or more lines are too long

View File

@ -926,6 +926,9 @@ BLOCKREGION.prototype = {
this.get('node').all('.' + CSS.BLOCK + ' a.' + CSS.EDITINGMOVE).each(function(moveicon) {
moveicon.setStyle('cursor', 'move');
handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
// Dragdrop module assigns this with a button role by default.
// However, the block move icon is part of a menubar, so it should have a menuitem role.
handle.setAttribute('role', 'menuitem');
moveicon.replace(handle);
});
},

View File

@ -100,6 +100,9 @@ BLOCKREGION.prototype = {
this.get('node').all('.' + CSS.BLOCK + ' a.' + CSS.EDITINGMOVE).each(function(moveicon) {
moveicon.setStyle('cursor', 'move');
handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
// Dragdrop module assigns this with a button role by default.
// However, the block move icon is part of a menubar, so it should have a menuitem role.
handle.setAttribute('role', 'menuitem');
moveicon.replace(handle);
});
},

View File

@ -42,11 +42,19 @@
{{$classes}}popover-region-notifications{{/classes}}
{{$attributes}}id="nav-notification-popover-container" data-userid="{{userid}}"{{/attributes}}
{{$togglelabel}}{{#str}} shownotificationwindownonew, message {{/str}}{{/togglelabel}}
{{$togglelabel}}{{!
}}{{^unreadcount}} {{#str}} shownotificationwindownonew, message {{/str}} {{/unreadcount}} {{!
}}{{#unreadcount}} {{#str}} shownotificationwindowwithcount, message, {{.}} {{/str}} {{/unreadcount}} {{!
}}{{/togglelabel}}
{{$togglecontent}}
{{#pix}} i/notifications, core, {{#str}} togglenotificationmenu, message {{/str}} {{/pix}}
<div class="count-container {{^unreadcount}}hidden{{/unreadcount}}" data-region="count-container"
aria-label="{{#str}} unreadnotifications, core_message, {{unreadcount}} {{/str}}">{{unreadcount}}</div>
<div
class="count-container {{^unreadcount}}hidden{{/unreadcount}}"
data-region="count-container"
aria-hidden=true
>
{{unreadcount}}
</div>
{{/togglecontent}}
{{$containerlabel}}{{#str}} notificationwindow, message {{/str}}{{/containerlabel}}
@ -81,9 +89,9 @@
{{/content}}
{{/ core/popover_region }}
{{#js}}
require(['jquery', 'message_popup/notification_popover_controller'], function($, controller) {
require(['jquery', 'message_popup/notification_popover_controller'], function($, Controller) {
var container = $('#nav-notification-popover-container');
var controller = new controller(container);
var controller = new Controller(container);
controller.registerEventListeners();
controller.registerListNavigationEventListeners();
});

View File

@ -100,9 +100,9 @@
<span
class="badge badge-pill badge-primary {{^unreadcount}}hidden{{/unreadcount}}"
data-region="unread-count"
aria-label="{{#str}} unreadmessages, core_message, {{unreadcount}} {{/str}}"
>
{{unreadcount}}
<span aria-hidden="true">{{unreadcount}}</span>
<span class="sr-only">{{#str}} unreadmessages, core_message, {{unreadcount}} {{/str}}</span>
</span>
<div class="text-muted ml-auto">

View File

@ -65,9 +65,10 @@
>
{{#str}} requests {{/str}}
<span class="badge badge-primary bg-primary ml-2 {{^contactrequestcount}}hidden{{/contactrequestcount}}"
data-region="contact-request-count"
aria-label="{{#str}} pendingcontactrequests, core_message, {{contactrequestcount}} {{/str}}">
{{contactrequestcount}}
data-region="contact-request-count"
>
<span aria-hidden="true">{{contactrequestcount}}</span>
<span class="sr-only">{{#str}} pendingcontactrequests, core_message, {{contactrequestcount}} {{/str}}</span>
</span>
</a>
</li>
@ -95,4 +96,4 @@
{{/ core_message/message_drawer_view_contacts_body_section_requests }}
</div>
</div>
</div>
</div>

View File

@ -59,13 +59,17 @@
<div class="w-100 text-truncate ml-2">
<div class="d-flex">
<strong class="m-0 text-truncate">{{name}}</strong>
<span class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary" data-region="favourite-icon-container"
aria-label="{{#str}} favourites, core {{/str}}">
{{#pix}} i/star-rating, core {{/pix}}
<span
class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary"
data-region="favourite-icon-container"
>
{{#pix}} i/star-rating, core, {{#str}} favourites, core {{/str}} {{/pix}}
</span>
<span class="{{^ismuted}}hidden{{/ismuted}} ml-1 text-primary" data-region="muted-icon-container"
aria-label="{{#str}} mutedconversation, core_message {{/str}}">
{{#pix}} i/muted, core {{/pix}}
<span
class="{{^ismuted}}hidden{{/ismuted}} ml-1 text-primary"
data-region="muted-icon-container"
>
{{#pix}} i/muted, core, {{#str}} mutedconversation, core_message {{/str}} {{/pix}}
</span>
</div>
{{#showonlinestatus}}

View File

@ -58,13 +58,17 @@
<div class="w-100 text-truncate ml-2">
<div class="d-flex">
<strong class="m-0 text-truncate">{{name}}</strong>
<span class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary" data-region="favourite-icon-container"
aria-label="{{#str}} favourites, core {{/str}}">
{{#pix}} i/star-rating, core {{/pix}}
<span
class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary"
data-region="favourite-icon-container"
>
{{#pix}} i/star-rating, core, {{#str}} favourites, core {{/str}} {{/pix}}
</span>
<span class="{{^ismuted}}hidden{{/ismuted}} ml-1 text-primary" data-region="muted-icon-container"
aria-label="{{#str}} mutedconversation, core_message {{/str}}">
{{#pix}} i/muted, core {{/pix}}
<span
class="{{^ismuted}}hidden{{/ismuted}} ml-1 text-primary"
data-region="muted-icon-container"
>
{{#pix}} i/muted, core, {{#str}} mutedconversation, core_message {{/str}} {{/pix}}
</span>
</div>
{{#showonlinestatus}}
@ -75,4 +79,4 @@
{{/showonlinestatus}}
</div>
</div>
</div>
</div>

View File

@ -58,13 +58,17 @@
<div class="w-100 text-truncate ml-2">
<div class="d-flex">
<strong class="m-0 text-truncate">{{name}}</strong>
<span class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary"
data-region="favourite-icon-container" aria-label="{{#str}} favourites, core {{/str}}">
{{#pix}} i/star-rating, core {{/pix}}
<span
class="{{^isfavourite}}hidden{{/isfavourite}} ml-1 text-primary"
data-region="favourite-icon-container"
>
{{#pix}} i/star-rating, core, {{#str}} favourites, core {{/str}} {{/pix}}
</span>
<span class="{{^ismuted}}hidden{{/ismuted}} ml-1 text-primary" data-region="muted-icon-container"
aria-label="{{#str}} mutedconversation, core_message {{/str}}">
{{#pix}} i/muted, core {{/pix}}
<span
class="{{^ismuted}}hidden{{/ismuted}} ml-1 text-primary"
data-region="muted-icon-container"
>
{{#pix}} i/muted, core, {{#str}} mutedconversation, core_message {{/str}} {{/pix}}
</span>
</div>
<p class="m-0 text-truncate">{{subname}}</p>

View File

@ -67,10 +67,12 @@
<a href="#" data-route="view-contacts" role="button">
{{#pix}} i/user, core {{/pix}}
{{#str}} contacts, core_message {{/str}}
<span class="badge badge-primary bg-primary ml-2 {{^contactrequestcount}}hidden{{/contactrequestcount}}"
data-region="contact-request-count"
aria-label="{{#str}} pendingcontactrequests, core_message, {{contactrequestcount}} {{/str}}">
{{contactrequestcount}}
<span
class="badge badge-primary bg-primary ml-2 {{^contactrequestcount}}hidden{{/contactrequestcount}}"
data-region="contact-request-count"
>
<span aria-hidden="true">{{contactrequestcount}}</span>
<span class="sr-only">{{#str}} pendingcontactrequests, core_message, {{contactrequestcount}} {{/str}}</span>
</span>
</a>
</div>

View File

@ -54,17 +54,19 @@
{{#pix}} t/expanded, core {{/pix}}
</span>
<span class="font-weight-bold">{{$title}}{{/title}}</span>
<small class="hidden ml-1" data-region="section-total-count-container"
aria-label="{{#str}} totalconversations, core_message, {{count.total}} {{/str}}">
(<span data-region="section-total-count">{{count.total}}</span>)
<small class="hidden ml-1" data-region="section-total-count-container">
(<span aria-hidden="true" data-region="section-total-count">{{count.total}}</span>{{!
}}<span class="sr-only">{{#str}} totalconversations, core_message, {{count.total}} {{/str}}</span>)
</small>
<span class="hidden ml-2" data-region="loading-icon-container">
{{> core/loading }}
</span>
<span class="{{^count.unread}}hidden{{/count.unread}} badge badge-pill badge-primary ml-auto bg-primary"
data-region="section-unread-count"
{{#count.unread}}aria-label="{{#str}} unreadconversations, core_message, {{count.unread}} {{/str}}"{{/count.unread}}>
{{count.unread}}
<span
class="{{^count.unread}}hidden{{/count.unread}} badge badge-pill badge-primary ml-auto bg-primary"
data-region="section-unread-count"
>
<span aria-hidden="true">{{count.unread}}</span>
<span class="sr-only">{{#str}} unreadconversations, core_message, {{count.unread}} {{/str}}</span>
</span>
</button>
</div>

View File

@ -39,8 +39,13 @@
<a id="message-drawer-toggle-{{uniqid}}" class="nav-link popover-region-toggle position-relative icon-no-margin" href="#"
role="button">
{{#pix}} t/message, core, {{#str}} togglemessagemenu, message {{/str}} {{/pix}}
<div class="count-container {{^unreadcount}}hidden{{/unreadcount}}" data-region="count-container"
aria-label="{{#str}} unreadconversations, core_message, {{unreadcount}} {{/str}}">{{unreadcount}}</div>
<div
class="count-container {{^unreadcount}}hidden{{/unreadcount}}"
data-region="count-container"
>
<span aria-hidden="true">{{unreadcount}}</span>
<span class="sr-only">{{#str}} unreadconversations, core_message, {{unreadcount}} {{/str}}</span>
</div>
</a>
{{> core_message/message_jumpto }}
</div>

View File

@ -34,7 +34,7 @@ Feature: Star and unstar conversations
And "Group 1" "core_message > Message" should exist
And I select "Group 1" conversation in messaging
And I open contact menu
And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
And I click on "Star conversation" "link" in the "conversation-actions-menu" "region"
And I go back in "view-conversation" message drawer
And I open the "Starred" conversations list
And I should see "Group 1" in the "favourites" "core_message > Message list area"
@ -48,13 +48,13 @@ Feature: Star and unstar conversations
And "Group 1" "core_message > Message" should exist
And I select "Group 1" conversation in messaging
And I open contact menu
And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
And I click on "Star conversation" "link" in the "conversation-actions-menu" "region"
And I go back in "view-conversation" message drawer
And I open the "Starred" conversations list
And I should see "Group 1" in the "favourites" "core_message > Message list area"
And I select "Group 1" conversation in messaging
And I open contact menu
And I click on "Unstar" "link" in the "//div[@data-region='header-container']" "xpath_element"
And I click on "Unstar" "link" in the "conversation-actions-menu" "region"
And I go back in "view-conversation" message drawer
And I open the "Starred" conversations list
And I should not see "Group 1" in the "favourites" "core_message > Message list area"
@ -71,7 +71,7 @@ Feature: Star and unstar conversations
And "Student 2" "core_message > Message" should exist
And I select "Student 2" conversation in messaging
And I open contact menu
And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
And I click on "Star conversation" "link" in the "conversation-actions-menu" "region"
And I go back in "view-conversation" message drawer
And I open the "Starred" conversations list
And I should see "Student 2" in the "favourites" "core_message > Message list area"
@ -90,7 +90,7 @@ Feature: Star and unstar conversations
And I should see "Student 2" in the "favourites" "core_message > Message list area"
And I select "Student 2" conversation in messaging
And I open contact menu
And I click on "Unstar" "link" in the "//div[@data-region='header-container']" "xpath_element"
And I click on "Unstar" "link" in the "conversation-actions-menu" "region"
And I go back in "view-conversation" message drawer
And I open the "Starred" conversations list
And I should not see "Group 1" in the "favourites" "core_message > Message list area"

View File

@ -50,7 +50,7 @@ Feature: Message send messages
Then "Group 1" "core_message > Message" should exist
And I select "Group 1" conversation in the "group-messages" conversations list
And I open contact menu
And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
And I click on "Star conversation" "link" in the "conversation-actions-menu" "region"
And I go back in "view-conversation" message drawer
And I open the "Starred" conversations list
And I should see "Group 1"

View File

@ -38,7 +38,7 @@ Feature: Mute and unmute conversations
And I select "Group 1" conversation in messaging
And "muted" "icon_container" in the "Group 1" "core_message > Message header" should not be visible
And I open contact menu
And I click on "Mute" "link" in the "[data-region='header-container']" "css_element"
And I click on "Mute" "link" in the "conversation-actions-menu" "region"
And "muted" "icon_container" in the "Group 1" "core_message > Message header" should be visible
And I go back in "view-conversation" message drawer
And "muted" "icon_container" in the "Group 1" "core_message > Message" should be visible
@ -53,7 +53,7 @@ Feature: Mute and unmute conversations
And I select "Student 2" conversation in messaging
And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should not be visible
And I open contact menu
And I click on "Mute" "link" in the "[data-region='header-container']" "css_element"
And I click on "Mute" "link" in the "conversation-actions-menu" "region"
And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should be visible
And I go back in "view-conversation" message drawer
And "muted" "icon_container" in the "Student 2" "core_message > Message" should be visible
@ -70,7 +70,7 @@ Feature: Mute and unmute conversations
And I select "Group 1" conversation in messaging
And "muted" "icon_container" in the "Group 1" "core_message > Message header" should be visible
And I open contact menu
And I click on "Unmute" "link" in the "[data-region='header-container']" "css_element"
And I click on "Unmute" "link" in the "conversation-actions-menu" "region"
And "muted" "icon_container" in the "Group 1" "core_message > Message header" should not be visible
And I go back in "view-conversation" message drawer
And "muted" "icon_container" in the "Group 1" "core_message > Message" should not be visible
@ -88,7 +88,7 @@ Feature: Mute and unmute conversations
And I select "Student 2" conversation in messaging
And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should be visible
And I open contact menu
And I click on "Unmute" "link" in the "[data-region='header-container']" "css_element"
And I click on "Unmute" "link" in the "conversation-actions-menu" "region"
And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should not be visible
And I go back in "view-conversation" message drawer
And "muted" "icon_container" in the "Student 2" "core_message > Message" should not be visible

View File

@ -101,14 +101,14 @@ Feature: Run tests over my courses.
| Default region | Right |
And I press "Save changes"
And I should see "This is visible on all pages"
And "Move Text on all pages block" "button" should exist in the "Text on all pages" "block"
And "Move Text on all pages block" "menuitem" should exist in the "Text on all pages" "block"
When I am on the "My courses" page
# Check blocks visible but are "locked" in place.
Then "Course overview" "text" should exist in the "region-main" "region"
And I should not see "Add a block"
And I should see "This is visible on all pages"
And "Move Text on all pages block" "button" should not exist in the "Text on all pages" "block"
And "Move Course overview block" "button" should not exist in the "Course overview" "block"
And "Move Text on all pages block" "menuitem" should not exist in the "Text on all pages" "block"
And "Move Course overview block" "menuitem" should not exist in the "Course overview" "block"
And I click on "Actions menu" "icon" in the "Course overview" "block"
And I should not see "Delete Course overview block"

View File

@ -1258,7 +1258,7 @@ $activity-add-hover: theme-color-level('primary', -10) !default;
width: 100%;
}
#changenumsections {
.changenumsections {
border-top: $border-width solid $primary-light-border;
}
@ -1437,9 +1437,16 @@ $activity-add-hover: theme-color-level('primary', -10) !default;
flex: 0 1 auto;
max-width: 100%;
}
.inplaceeditable .quickeditlink,
.afterlink {
margin: 1.5rem 0 0.2rem 0.5rem;
/* Prevent bootstrap strech-link from covering the inplace editable button using z-index. */
.activityname {
.afterlink {
margin-left: 0.5rem;
}
.inplaceeditable .quickeditlink {
z-index: 2;
margin-left: 0.5rem;
}
}
.action-menu-item {

View File

@ -14731,7 +14731,7 @@ span.editinstructions {
.block-add:hover .activity-add-text {
text-decoration: underline; }
#changenumsections {
.changenumsections {
border-top: 1px solid #3584c9; }
.section-collapsemenu .collapseall {
@ -14821,7 +14821,8 @@ span.editinstructions {
.activity-item {
position: relative;
border-radius: 0.5rem; }
border-radius: 0.5rem;
/* Prevent bootstrap strech-link from covering the inplace editable button using z-index. */ }
.activity-item:not(.activityinline) {
border: 1px solid #dee2e6;
padding: 1rem; }
@ -14841,9 +14842,11 @@ span.editinstructions {
display: flex;
flex: 0 1 auto;
max-width: 100%; }
.activity-item .inplaceeditable .quickeditlink,
.activity-item .afterlink {
margin: 1.5rem 0 0.2rem 0.5rem; }
.activity-item .activityname .afterlink {
margin-left: 0.5rem; }
.activity-item .activityname .inplaceeditable .quickeditlink {
z-index: 2;
margin-left: 0.5rem; }
.activity-item .action-menu-item {
display: flex;
align-items: center; }

View File

@ -14731,7 +14731,7 @@ span.editinstructions {
.block-add:hover .activity-add-text {
text-decoration: underline; }
#changenumsections {
.changenumsections {
border-top: 1px solid #3584c9; }
.section-collapsemenu .collapseall {
@ -14821,7 +14821,8 @@ span.editinstructions {
.activity-item {
position: relative;
border-radius: 0.25rem; }
border-radius: 0.25rem;
/* Prevent bootstrap strech-link from covering the inplace editable button using z-index. */ }
.activity-item:not(.activityinline) {
border: 1px solid #dee2e6;
padding: 1rem; }
@ -14841,9 +14842,11 @@ span.editinstructions {
display: flex;
flex: 0 1 auto;
max-width: 100%; }
.activity-item .inplaceeditable .quickeditlink,
.activity-item .afterlink {
margin: 1.5rem 0 0.2rem 0.5rem; }
.activity-item .activityname .afterlink {
margin-left: 0.5rem; }
.activity-item .activityname .inplaceeditable .quickeditlink {
z-index: 2;
margin-left: 0.5rem; }
.activity-item .action-menu-item {
display: flex;
align-items: center; }