MDL-54701 message: add message notification popover

This commit is contained in:
Ryan Wyllie 2016-07-08 07:45:04 +00:00 committed by Mark Nelson
parent 607454d6f3
commit 406243381c
14 changed files with 678 additions and 8 deletions

View File

@ -73,6 +73,7 @@ $string['formorethan'] = 'For more than';
$string['guestnoeditmessage'] = 'Guest user can not edit messaging options';
$string['guestnoeditmessageother'] = 'Guest user can not edit other user messaging options';
$string['gotomessages'] = 'Go to messages';
$string['hidemessagewindow'] = 'Hide message window';
$string['hidenotificationwindow'] = 'Hide notification window';
$string['includeblockedusers'] = 'Include blocked users';
$string['incomingcontacts'] = 'Incoming contacts ({$a})';
@ -87,6 +88,7 @@ $string['loggedoffdescription'] = 'When I\'m offline';
$string['managecontacts'] = 'Manage my contacts';
$string['managemessageoutputs'] = 'Manage message outputs';
$string['messageoutputs'] = 'Message outputs';
$string['messagepreferences'] = 'Message preferences';
$string['mostrecent'] = 'Recent messages';
$string['mostrecentconversations'] = 'Recent conversations';
$string['mostrecentnotifications'] = 'Recent notifications';
@ -156,6 +158,8 @@ $string['sendmessagetopopup'] = 'Send message to {$a} - new window';
$string['settings'] = 'Settings';
$string['settingssaved'] = 'Your settings have been saved';
$string['showmessagewindow'] = 'Popup window on new message';
$string['showmessagewindownonew'] = 'Show message window with no new messages';
$string['showmessagewindowwithcount'] = 'Show message window with {$a} new messages';
$string['showallnotifications'] = 'Show all notifications';
$string['shownewnotifications'] = 'Show new notifications';
$string['shownotificationwindownonew'] = 'Show notification window with no new notifications';
@ -165,6 +169,7 @@ $string['thisconversation'] = 'this conversation';
$string['timenosee'] = 'Minutes since I was last seen online';
$string['timesent'] = 'Time sent';
$string['togglenotificationmenu'] = 'Toggle notification menu';
$string['togglemessagemenu'] = 'Toggle message menu';
$string['touserdoesntexist'] = 'You can not send a message to a user id ({$a}) that doesn\'t exist';
$string['unabletomessageuser'] = 'You are not permitted to send a message to that user';
$string['unblockcontact'] = 'Unblock contact';

View File

@ -199,6 +199,16 @@ define(['jquery', 'core/str', 'core/custom_interaction_events'],
this.menuToggle.focus();
};
/**
* Check if a content item has focus.
*
* @method contentItemHasFocus
* @return bool
*/
MdlPopoverController.prototype.contentItemHasFocus = function() {
return this.getContentItemWithFocus().length > 0;
};
/**
* Return the currently focused content item.
*
@ -318,7 +328,11 @@ define(['jquery', 'core/str', 'core/custom_interaction_events'],
this.openMenu();
this.focusFirstContentItem();
} else {
this.focusNextContentItem();
if (this.contentItemHasFocus()) {
this.focusNextContentItem();
} else {
this.focusFirstContentItem();
}
}
}.bind(this));

View File

@ -3215,6 +3215,27 @@ EOD;
}
}
/**
* Returns the message menu
*
* @return string HTML for the message menu
*/
public function message_menu() {
global $USER;
if (isloggedin()) {
$context = [
'userid' => $USER->id,
'urls' => [
'preferences' => (new moodle_url('/message/edit.php', ['id' => $USER->id]))->out(),
],
];
return $this->render_from_template('message/message_popover', $context);
} else {
return '';
}
}
/**
* Construct a user menu, returning HTML that can be echoed out by a
* layout file.

View File

@ -0,0 +1,261 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the message popover in the nav bar.
*
* See template: message/message_menu
*
* @module message/message_popover_controller
* @class message_popover_controller
* @package message
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define(['jquery', 'theme_bootstrapbase/bootstrap', 'core/ajax', 'core/templates', 'core/str',
'core/notification', 'core/custom_interaction_events', 'core/mdl_popover_controller',
'message/message_repository'],
function($, bootstrap, ajax, templates, str, debugNotification, customEvents,
PopoverController, messageRepo) {
var SELECTORS = {
MARK_ALL_READ_BUTTON: '.mark-all-read-button',
USER_ID: 'data-userid',
MODE_TOGGLE: '.mdl-popover-header-actions .fancy-toggle',
CONTENT: '.messages',
CONTENT_ITEM_CONTAINER: '.content-item-container',
EMPTY_MESSAGE: '.empty-message',
};
/**
* Constructor for the MessagePopoverController.
* Extends MdlPopoverController.
*
* @param element jQuery object root element of the popover
* @return object MessagePopoverController
*/
var MessagePopoverController = function(element) {
// Initialise base class.
PopoverController.call(this, element);
this.markAllReadButton = this.root.find(SELECTORS.MARK_ALL_READ_BUTTON);
this.content = this.root.find(SELECTORS.CONTENT);
this.userId = this.root.attr(SELECTORS.USER_ID);
this.limit = 20;
this.offset = 0;
this.loadedAll = false;
this.initialLoad = false;
// Let's find out how many unread messages there are.
this.loadUnreadMessageCount();
this.root.find('[data-toggle="tooltip"]').tooltip();
};
/**
* Clone the parent prototype.
*/
MessagePopoverController.prototype = Object.create(PopoverController.prototype);
/**
* Get the element holding the messages.
*
* @method getContent
* @return jQuery element
*/
MessagePopoverController.prototype.getContent = function() {
return this.content;
};
/**
* Increment the offset.
*
* @method incrementOffset
*/
MessagePopoverController.prototype.incrementOffset = function() {
this.offset += this.limit;
};
/**
* Set the correct aria label on the menu toggle button to be read out by screen
* readers. The message will indicate the state of the unread notifications.
*
* @method updateButtonAriaLabel
*/
MessagePopoverController.prototype.updateButtonAriaLabel = function() {
if (this.isMenuOpen()) {
str.get_string('hidemessagewindow', 'message').done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
} else {
if (this.unreadCount) {
str.get_string('showmessagewindowwithcount', 'message', this.unreadCount).done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
} else {
str.get_string('showmessagewindownonew', 'message').done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
}
}
};
/**
* Show the unread notification count badge on the menu toggle if there
* are unread notifications, otherwise hide it.
*
* @method renderUnreadCount
*/
MessagePopoverController.prototype.renderUnreadCount = function() {
var element = this.root.find('.count-container');
if (this.unreadCount) {
element.text(this.unreadCount);
element.removeClass('hidden');
} else {
element.addClass('hidden');
}
};
/**
* Hide the unread notification count badge on the menu toggle.
*
* @method hideUnreadCount
*/
MessagePopoverController.prototype.hideUnreadCount = function() {
this.root.find('.count-container').addClass('hidden');
};
/**
* Ask the server how many unread notifications are left, render the value
* as a badge on the menu toggle and update the aria labels on the menu
* toggle.
*
* @method loadUnreadMessageCount
*/
MessagePopoverController.prototype.loadUnreadMessageCount = function() {
messageRepo.countUnread({useridto: this.userId}).then(function(count) {
this.unreadCount = count;
this.renderUnreadCount();
this.updateButtonAriaLabel();
}.bind(this));
};
/**
* Render the notification data with the appropriate template and add it to the DOM.
*
* @method renderMessages
* @param messages array message data
* @param container jQuery object the container to append the rendered messages
* @return jQuery promise that is resolved when all notifications have been
* rendered and added to the DOM
*/
MessagePopoverController.prototype.renderMessages = function(messages, container) {
var promises = [];
if (messages.length) {
$.each(messages, function(index, message) {
var promise = templates.render('message/message_content_item', message);
promise.then(function(html, js) {
container.append(html);
templates.runTemplateJS(js);
}.bind(this));
promises.push(promise);
}.bind(this));
}
return $.when.apply($.when, promises);
};
/**
* Send a request for more messages from the server, if we aren't already
* loading some and haven't already loaded all of them.
*
* @method loadMoreMessages
* @return jQuery promise that is resolved when notifications have been
* retrieved and added to the DOM
*/
MessagePopoverController.prototype.loadMoreMessages = function() {
if (this.isLoading || this.loadedAll) {
return $.Deferred().resolve();
}
this.startLoading();
var request = {
userid: this.userId,
limit: this.limit,
offset: this.offset,
};
var container = this.getContent();
var promise = messageRepo.query(request).then(function(result) {
var messages = result.contacts;
this.loadedAll = !messages.length || messages.length < this.limit;
this.initialLoad = true;
this.updateButtonAriaLabel();
if (messages.length) {
this.incrementOffset();
return this.renderMessages(messages, container);
}
}.bind(this))
.always(function() { this.stopLoading(); }.bind(this));
return promise;
};
/**
* Send a request to the server to mark all unread notifications as read and update
* the unread count and unread notification elements appropriately.
*
* @method markAllAsRead
*/
MessagePopoverController.prototype.markAllAsRead = function() {
this.markAllReadButton.addClass('loading');
return messageRepo.markAllAsRead({useridto: this.userId})
.then(function() {
this.unreadCount = 0;
this.clearUnreadNotifications();
}.bind(this))
.always(function() { this.markAllReadButton.removeClass('loading'); }.bind(this));
};
/**
* Add all of the required event listeners for this notification popover.
*
* @method registerEventListeners
*/
MessagePopoverController.prototype.registerEventListeners = function() {
// Update the notification information when the menu is opened.
this.root.on(this.events().menuOpened, function() {
this.hideUnreadCount();
this.updateButtonAriaLabel();
if (!this.initialLoad) {
this.loadMoreMessages();
}
}.bind(this));
// Update the notification information when the menu is opened.
this.root.on(this.events().menuClosed, function() {
this.renderUnreadCount();
this.updateButtonAriaLabel();
}.bind(this));
};
return MessagePopoverController;
});

View File

@ -0,0 +1,66 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Retrieves notifications from the server.
*
* @module message/message_repository
* @class message_repository
* @package message
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) {
var query = function(args) {
/*
if (typeof args.limit === 'undefined') {
args.limit = 20;
}
if (typeof args.offset === 'undefined') {
args.offset = 0;
}
*/
delete args.limit;
delete args.offset;
var request = {
methodname: 'core_message_data_for_messagearea_conversations',
args: args
};
var promise = ajax.call([request])[0];
promise.fail(notification.exception);
return promise;
};
var countUnread = function() {
return $.Deferred();
};
var markAllAsRead = function() {
return $.Deferred();
};
return {
query: query,
countUnread: countUnread,
markAllAsRead: markAllAsRead,
};
});

View File

@ -32,7 +32,7 @@ define(['jquery', 'theme_bootstrapbase/bootstrap', 'core/ajax', 'core/templates'
popoverController, notificationRepo) {
var SELECTORS = {
MARK_ALL_READ_BUTTON: '#mark-all-read-button',
MARK_ALL_READ_BUTTON: '.mark-all-read-button',
USER_ID: 'data-userid',
MODE_TOGGLE: '.mdl-popover-header-actions .fancy-toggle',
UNREAD_NOTIFICATIONS_CONTAINER: '.unread-notifications',

View File

@ -0,0 +1,51 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template message/message_content_item
This template will render the message content item for the
navigation bar message menu.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* userid the logged in user id
Example context (json):
{
}
}}
<div class="content-item-container"
role="listitem"
aria-expanded="false"
aria-label=""
tabindex="0"
{{#contexturl}}data-context-url="{{{.}}}"{{/contexturl}}>
<div class="content-item">
{{{picture}}}
<div class="content-item-body">
<h3>{{name}}</h3>
<p>{{lastmessage}}</p>
</div>
</div>
</div>

View File

@ -0,0 +1,79 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template message/message_popover
This template will render the notification popover for the navigation bar.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* userid the logged in user id
Example context (json):
{
}
}}
{{< core/mdl_popover }}
{{$classes}}mdl-popover-messages{{/classes}}
{{$attributes}}id="nav-message-popover-container" data-userid="{{userid}}"{{/attributes}}
{{$togglelabel}}{{#str}} showmessagewindownonew, message {{/str}}{{/togglelabel}}
{{$togglecontent}}
{{#pix}} t/message, core, {{#str}} togglemessagemenu, message {{/str}} {{/pix}}
<div class="count-container hidden"></div>
{{/togglecontent}}
{{$containerlabel}}{{#str}} notificationwindow, message {{/str}}{{/containerlabel}}
{{$headertext}}{{#str}} messages, message {{/str}}{{/headertext}}
{{$headeractions}}
<a class="mark-all-read-button"
href="#"
data-toggle="tooltip"
data-placement="top"
data-original-title="{{#str}} markallread {{/str}}"
aria-role="button">
<span class="normal-icon">{{#pix}} t/markasread, core, {{#str}} markallread {{/str}} {{/pix}}</span>
<span class="loading-icon">{{#pix}} y/loading, core, {{#str}} loading, mod_assign {{/str}} {{/pix}}</span>
</a>
<a href="{{{urls.preferences}}}"
data-toggle="tooltip"
data-placement="top"
data-original-title="{{#str}} messagepreferences, message {{/str}}">
{{#pix}} i/settings, core, {{#str}} messagepreferences, message {{/str}} {{/pix}}
</a>
{{/headeractions}}
{{$content}}
<div class="messages" role="log" aria-busy="false" aria-atomic="false" aria-relevant="additions"></div>
<div class="empty-message" tabindex="0">{{#str}} nomessagesfound, message {{/str}}</div>
{{/content}}
{{/ core/mdl_popover }}
{{#js}}
require(['jquery', 'message/message_popover_controller'], function($, controller) {
var container = $('#nav-message-popover-container');
var controller = new controller(container);
controller.registerEventListeners();
controller.registerListNavigationEventListeners();
});
{{/js}}

View File

@ -52,7 +52,7 @@
<div class="off-text">{{#str}} all {{/str}}</div>
<div class="on-text">{{#str}} new {{/str}}</div>
</div>
<a id="mark-all-read-button"
<a class="mark-all-read-button"
href="#"
data-toggle="tooltip"
data-placement="top"

View File

@ -76,6 +76,9 @@
.loading-icon {
display: block;
}
.empty-message {
display: none;
}
}
}
}
@ -101,6 +104,11 @@
padding-left: 10px;
padding-right: 10px;
img {
height: 16px;
width: 16px;
}
.count-container {
padding: 2px;
border-radius: 2px;
@ -231,7 +239,7 @@
}
}
#mark-all-read-button {
.mark-all-read-button {
.normal-icon {
display: inline-block;
}
@ -521,6 +529,101 @@
}
}
.mdl-popover-messages {
&.mdl-popover {
.mdl-popover-container {
.mdl-popover-header-container {
.mdl-popover-header-actions {
.mark-all-read-button {
.normal-icon {
display: inline-block;
}
.loading-icon {
display: none;
height: 12px;
width: 12px;
}
&.loading {
.normal-icon {
display: none;
}
.loading-icon {
display: inline-block;
}
}
}
}
}
.mdl-popover-content-container {
.mdl-popover-content {
.messages {
&:empty + .empty-message {
display: block;
}
}
.content-item-container {
width: 100%;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
padding: 5px;
height: 85px;
&:hover {
opacity: 0.8;
}
&[data-context-url] {
cursor: pointer;
}
.content-item {
height: 100%;
width: 100%;
img {
display: inline-block;
vertical-align: middle;
}
.content-item-body {
display: inline-block;
box-sizing: border-box;
vertical-align: middle;
width: ~"calc(100% - 68px)";
height: 100%;
h3 {
font-size: 14px;
line-height: 14px;
margin: 0;
width: 100%;
}
p {
margin: 0;
}
}
}
&:last-child {
border-bottom: none;
}
}
}
&.loading {
.mdl-popover-content {
.messages {
&:empty + .empty-message {
display: none;
}
}
}
}
}
}
}
}
.dir-rtl {
.mdl-popover {
.mdl-popover-container {

View File

@ -7104,6 +7104,9 @@ body.path-question-type .mform fieldset.hidden {
.mdl-popover .mdl-popover-container .mdl-popover-content-container.loading .loading-icon {
display: block;
}
.mdl-popover .mdl-popover-container .mdl-popover-content-container.loading .empty-message {
display: none;
}
.mdl-popover.collapsed .mdl-popover-container {
height: 0;
overflow: hidden;
@ -7120,6 +7123,10 @@ body.path-question-type .mform fieldset.hidden {
padding-left: 10px;
padding-right: 10px;
}
.navbar .mdl-popover .mdl-popover-toggle img {
height: 16px;
width: 16px;
}
.navbar .mdl-popover .mdl-popover-toggle .count-container {
padding: 2px;
border-radius: 2px;
@ -7230,18 +7237,18 @@ body.path-question-type .mform fieldset.hidden {
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container .mdl-popover-header-actions .fancy-toggle.off .off-text {
display: block;
}
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container #mark-all-read-button .normal-icon {
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container .mark-all-read-button .normal-icon {
display: inline-block;
}
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container #mark-all-read-button .loading-icon {
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container .mark-all-read-button .loading-icon {
display: none;
height: 12px;
width: 12px;
}
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container #mark-all-read-button.loading .normal-icon {
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container .mark-all-read-button.loading .normal-icon {
display: none;
}
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container #mark-all-read-button.loading .loading-icon {
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-header-container .mark-all-read-button.loading .loading-icon {
display: inline-block;
}
.mdl-popover-notifications.mdl-popover .mdl-popover-container .mdl-popover-content-container.loading .mdl-popover-content .unread-notifications:empty + .empty-message,
@ -7444,6 +7451,66 @@ body.path-question-type .mform fieldset.hidden {
.mdl-popover-notifications.mdl-popover.unread-only .mdl-popover-container .mdl-popover-content-container.loading .mdl-popover-content .all-notifications + .empty-message {
display: none;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-header-container .mdl-popover-header-actions .mark-all-read-button .normal-icon {
display: inline-block;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-header-container .mdl-popover-header-actions .mark-all-read-button .loading-icon {
display: none;
height: 12px;
width: 12px;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-header-container .mdl-popover-header-actions .mark-all-read-button.loading .normal-icon {
display: none;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-header-container .mdl-popover-header-actions .mark-all-read-button.loading .loading-icon {
display: inline-block;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .messages:empty + .empty-message {
display: block;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container {
width: 100%;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
padding: 5px;
height: 85px;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container:hover {
opacity: 0.8;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container[data-context-url] {
cursor: pointer;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container .content-item {
height: 100%;
width: 100%;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container .content-item img {
display: inline-block;
vertical-align: middle;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container .content-item .content-item-body {
display: inline-block;
box-sizing: border-box;
vertical-align: middle;
width: calc(100% - 68px);
height: 100%;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container .content-item .content-item-body h3 {
font-size: 14px;
line-height: 14px;
margin: 0;
width: 100%;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container .content-item .content-item-body p {
margin: 0;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container .mdl-popover-content .content-item-container:last-child {
border-bottom: none;
}
.mdl-popover-messages.mdl-popover .mdl-popover-container .mdl-popover-content-container.loading .mdl-popover-content .messages:empty + .empty-message {
display: none;
}
.dir-rtl .mdl-popover .mdl-popover-container {
left: 0;
right: auto;

View File

@ -44,6 +44,7 @@ echo $OUTPUT->doctype() ?>
<?php echo $OUTPUT->navbar_home(); ?>
<?php echo $OUTPUT->navbar_button(); ?>
<?php echo $OUTPUT->user_menu(); ?>
<?php echo $OUTPUT->message_menu(); ?>
<?php echo $OUTPUT->notification_menu(); ?>
<?php echo $OUTPUT->search_box(); ?>
<div class="nav-collapse collapse">

View File

@ -47,6 +47,7 @@ echo $OUTPUT->doctype() ?>
<?php echo $OUTPUT->navbar_home(); ?>
<?php echo $OUTPUT->navbar_button(); ?>
<?php echo $OUTPUT->user_menu(); ?>
<?php echo $OUTPUT->message_menu(); ?>
<?php echo $OUTPUT->notification_menu(); ?>
<?php echo $OUTPUT->search_box(); ?>
<div class="nav-collapse collapse">

View File

@ -55,6 +55,7 @@ echo $OUTPUT->doctype() ?>
<?php echo $OUTPUT->navbar_home(); ?>
<?php echo $OUTPUT->navbar_button(); ?>
<?php echo $OUTPUT->user_menu(); ?>
<?php echo $OUTPUT->message_menu(); ?>
<?php echo $OUTPUT->notification_menu(); ?>
<?php echo $OUTPUT->search_box(); ?>
<div class="nav-collapse collapse">